<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dinesh Kumar Ramasamy</title>
    <description>The latest articles on DEV Community by Dinesh Kumar Ramasamy (@dramasamy).</description>
    <link>https://dev.to/dramasamy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1742184%2F00693172-e0aa-4545-998f-d582eb173fbf.jpeg</url>
      <title>DEV Community: Dinesh Kumar Ramasamy</title>
      <link>https://dev.to/dramasamy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dramasamy"/>
    <language>en</language>
    <item>
      <title>From API to GPU, Week 7: The Training Loop, Written by Hand</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Tue, 08 Sep 2026 03:51:40 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-7-the-training-loop-written-by-hand-3pk2</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-7-the-training-loop-written-by-hand-3pk2</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 2 of 8: Enough ML to understand inference. Week 7 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt; train the same temperature converter while following each step:&lt;br&gt;
make a prediction, measure its error, work out which way to adjust the weight&lt;br&gt;
and bias, then try again. Think of adjusting two knobs and checking whether&lt;br&gt;
the answer gets closer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Week 6 trained a tiny model to convert Celsius to Fahrenheit using PyTorch's&lt;br&gt;
ready-made layers and optimizer. That is a normal way to write a training loop,&lt;br&gt;
and it remains useful beyond this example. We explained the prediction formula&lt;br&gt;
and worked through the first update by hand. This week traces how&lt;br&gt;
&lt;code&gt;loss.backward()&lt;/code&gt; calculates the gradients that tell that update which way to go:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Call in Week 6&lt;/th&gt;
&lt;th&gt;Already explained&lt;/th&gt;
&lt;th&gt;What Week 7 adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nn.Linear(1, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Weight, bias, multiply and add&lt;/td&gt;
&lt;td&gt;Plain trainable tensors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optimizer.step()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subtract learning rate times gradient&lt;/td&gt;
&lt;td&gt;Write the update directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loss.backward()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computes gradients&lt;/td&gt;
&lt;td&gt;Trace how they are calculated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I replace the layer and optimizer with plain tensors and a hand-written update&lt;br&gt;
as a learning and debugging exercise. PyTorch still computes the gradients;&lt;br&gt;
I check them against slopes worked out by hand. This goes deeper into Week 6's&lt;br&gt;
loop, rather than replacing the normal PyTorch workflow.&lt;/p&gt;

&lt;p&gt;The problem is the same as Week 6 on purpose. Same six examples, same raw Celsius&lt;br&gt;
inputs, same learning rate. We can compare where the two loops end up.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Follow one prediction to its error
&lt;/h2&gt;

&lt;p&gt;Start with one prediction, before training anything. I chose &lt;code&gt;w=2.0&lt;/code&gt;, &lt;code&gt;b=1.0&lt;/code&gt;,&lt;br&gt;
and &lt;code&gt;x=3.0&lt;/code&gt; by hand: the prediction is 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;7&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, against a target of 10.&lt;br&gt;
The squared loss is 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;7&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;9&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. With &lt;code&gt;w=3.0&lt;/code&gt;, the prediction would already be&lt;br&gt;
10, with zero loss and zero gradients. I use &lt;code&gt;2.0&lt;/code&gt; so there is an error to explain.&lt;br&gt;
The lab checks both choices; training later uses random starts.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;To work out how each parameter caused that error, PyTorch keeps a record of the&lt;br&gt;
calculation, like a trail of intermediate results in a debugger. That record is&lt;br&gt;
the &lt;strong&gt;computational graph&lt;/strong&gt;. Setting &lt;code&gt;requires_grad=True&lt;/code&gt; on the weight and bias&lt;br&gt;
asks PyTorch to record operations it can differentiate, so it can calculate their&lt;br&gt;
gradients. This automatic gradient calculation is called &lt;strong&gt;autograd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, look at the record. Each result of a tracked operation carries a&lt;br&gt;
&lt;code&gt;grad_fn&lt;/code&gt;, an object naming the operation that produced it:&lt;/p&gt;

&lt;p&gt;This code runs on Spark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
# Create a one-value weight, shape (1,), with gradient tracking enabled.
w = torch.tensor([2.0], requires_grad=True)
# Make a separate one-value bias with tracking enabled too.
b = torch.tensor([1.0], requires_grad=True)
# Omitted requires_grad defaults to False: this one-value input is fixed data.
x = torch.tensor([3.0])
# Compute the model prediction from input, weight, and bias.
pred = w * x + b
# Choose 10 as this tiny example target, unrelated to the Fahrenheit task.
# Score the squared prediction error against the target: (7 - 10)**2 = 9.
loss = (pred - 10.0)**2
# Show the prediction and loss with their recorded operations.
print("pred:", pred)
print("loss:", loss)
# Compare gradient tracking for the fixed input and trainable weight.
print("x.requires_grad:", x.requires_grad, " w.requires_grad:", w.requires_grad)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pred: tensor([7.], grad_fn=&amp;lt;AddBackward0&amp;gt;)
loss: tensor([9.], grad_fn=&amp;lt;PowBackward0&amp;gt;)
x.requires_grad: False  w.requires_grad: True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two things to read here. &lt;code&gt;pred&lt;/code&gt; was built with a multiply and an add, so its&lt;br&gt;
&lt;code&gt;grad_fn&lt;/code&gt; is &lt;code&gt;AddBackward0&lt;/code&gt; (the last step). &lt;code&gt;loss&lt;/code&gt; was built with a power, so its&lt;br&gt;
&lt;code&gt;grad_fn&lt;/code&gt; is &lt;code&gt;PowBackward0&lt;/code&gt;. Each &lt;code&gt;grad_fn&lt;/code&gt; names only the tensor's immediate&lt;br&gt;
producing step, but that object links back to the steps that fed it, so together&lt;br&gt;
they form the full chain PyTorch will walk in reverse:&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;x.requires_grad&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;, because I never asked it to be tracked. Inputs&lt;br&gt;
are fixed data, so they do not need gradients; only the parameters I want to&lt;br&gt;
train do.&lt;/p&gt;

&lt;p&gt;For now, follow the weight and bias: &lt;code&gt;backward()&lt;/code&gt; will put their gradients in&lt;br&gt;
&lt;code&gt;w.grad&lt;/code&gt; and &lt;code&gt;b.grad&lt;/code&gt;. The optional gradient-storage check&lt;br&gt;
explains which tensors receive stored gradients by default.&lt;/p&gt;
&lt;h2&gt;
  
  
  Work out which way to adjust the parameters
&lt;/h2&gt;

&lt;p&gt;A quick reminder from Week 6, part 2: a &lt;strong&gt;gradient&lt;/strong&gt; tells us the slope of the&lt;br&gt;
loss for each parameter. If increasing a parameter slightly lowers the loss,&lt;br&gt;
its gradient is negative; if it raises the loss, its gradient is positive.&lt;br&gt;
The size tells us how steeply the loss changes at the current values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backpropagation&lt;/strong&gt; is how we calculate those gradients: start at the loss and&lt;br&gt;
follow the recorded calculation backward to the weight and bias. In PyTorch,&lt;br&gt;
&lt;code&gt;loss.backward()&lt;/code&gt; does this work. It calculates the slopes; the separate update&lt;br&gt;
step uses them to adjust the parameters.&lt;/p&gt;

&lt;p&gt;Take the tiny example above: the loss is 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 with 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
,&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, so the prediction is 7. Here is how fast the loss changes if I nudge each&lt;br&gt;
parameter a tiny amount from the current point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nudging the prediction up changes the squared loss at a rate of

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;pred&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;7&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 loss units per prediction unit.&lt;/li&gt;
&lt;li&gt;Nudging the weight up changes the prediction at a rate of 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
.&lt;/li&gt;
&lt;li&gt;Multiply those two rates: 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mord"&gt;18&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. That is the weight's slope.&lt;/li&gt;
&lt;li&gt;Nudging the bias up changes the prediction at a rate of 1, so its slope is

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;−&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multiplying the connected rates along the chain is the &lt;strong&gt;chain rule&lt;/strong&gt;. Written&lt;br&gt;
in calculus notation, those two slopes are:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;The symbol 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;span class="mord"&gt;/&lt;/span&gt;&lt;span class="mord"&gt;∂&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 just means "how much the loss 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 changes&lt;br&gt;
when 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 changes." I ran &lt;code&gt;backward()&lt;/code&gt; and compared it to those hand values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
# Create one-value parameters, shape (1,), with gradient tracking enabled.
w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)  # Bias also needs a gradient.
x = torch.tensor([3.0])  # Fixed input; requires_grad defaults to False.
# Compute the model prediction from input, weight, and bias.
pred = w * x + b
# 10 is the deliberately chosen toy target, not a Fahrenheit label.
loss = (pred - 10.0)**2  # Score the squared prediction error against the target.
loss.backward()  # Follow the graph to fill w.grad and b.grad.
print("w.grad:", w.grad, " b.grad:", b.grad)
# Apply the chain-rule formulas using the input and prediction values.
print("hand dL/dw:", 2*(pred.item()-10)*x.item(),
      " hand dL/db:", 2*(pred.item()-10))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;w.grad: tensor([-18.])  b.grad: tensor([-6.])
hand dL/dw: -18.0  hand dL/db: -6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They match. &lt;code&gt;backward()&lt;/code&gt; filled &lt;code&gt;w.grad&lt;/code&gt; and &lt;code&gt;b.grad&lt;/code&gt; with the same numbers I got&lt;br&gt;
from the derivative by hand. That is autograd's core job, just for graphs far too&lt;br&gt;
large to differentiate by hand.&lt;/p&gt;

&lt;p&gt;Next we use the same idea on the six temperature examples. Their loss is the&lt;br&gt;
average squared error, so the gradient averages their contributions too. The&lt;br&gt;
training transcript compares autograd with that hand calculation; the optional&lt;br&gt;
six-example check shows every&lt;br&gt;
contribution.&lt;/p&gt;
&lt;h2&gt;
  
  
  The training loop, without the helpers
&lt;/h2&gt;

&lt;p&gt;Now the real thing. This script trains Celsius to Fahrenheit with no &lt;code&gt;nn.Linear&lt;/code&gt;&lt;br&gt;
and no optimizer. The weight and bias are plain tensors with&lt;br&gt;
&lt;code&gt;requires_grad=True&lt;/code&gt;. The update &lt;code&gt;param -= lr * param.grad&lt;/code&gt; is written out. It&lt;br&gt;
also prints the weight and bias before and after training, and checks the first&lt;br&gt;
gradient against the hand-computed derivative.&lt;/p&gt;

&lt;p&gt;The inputs and targets have shape &lt;code&gt;(6, 1)&lt;/code&gt;: six examples, one value per example.&lt;br&gt;
Each parameter has shape &lt;code&gt;(1,)&lt;/code&gt;. Broadcasting reuses that one weight and bias&lt;br&gt;
for every row, giving predictions of shape &lt;code&gt;(6, 1)&lt;/code&gt;. The comments mark those&lt;br&gt;
shapes in the code. Element-wise &lt;code&gt;weight * celsius + bias&lt;/code&gt; works for this&lt;br&gt;
one-input, one-output model; a general &lt;code&gt;nn.Linear&lt;/code&gt; uses matrix multiplication&lt;br&gt;
and then adds its bias.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Here is the core loop, an &lt;strong&gt;exact excerpt, not a standalone program&lt;/strong&gt;, from the&lt;br&gt;
lab's training script.&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;br&gt;
The complete script is at the end;&lt;br&gt;
the next section runs that full version. Read this excerpt as predict and score,&lt;br&gt;
calculate gradients, update, then clear. The two safeguards, &lt;code&gt;no_grad()&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;zero_()&lt;/code&gt;, get their own small checks after the training result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Each epoch uses all six examples for one parameter update.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Compute the model prediction from input, weight, and bias.
&lt;/span&gt;        &lt;span class="c1"&gt;# The forward pass produces six predictions, shape (6, 1).
&lt;/span&gt;        &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;
        &lt;span class="c1"&gt;# Score the squared prediction error against each target, then average.
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Backward pass fills weight.grad and bias.grad.
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Move each parameter opposite its gradient, scaled by the learning
&lt;/span&gt;        &lt;span class="c1"&gt;# rate. This is the mathematical plain-SGD rule, using the full batch.
&lt;/span&gt;        &lt;span class="c1"&gt;# no_grad keeps the in-place update out of the gradient graph.
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# Adjust the weight using its gradient and the learning rate.
&lt;/span&gt;            &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
            &lt;span class="c1"&gt;# Apply the same update rule to the bias, using its own gradient.
&lt;/span&gt;            &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
        &lt;span class="c1"&gt;# Clear the gradients, or the next backward() would add onto these.
&lt;/span&gt;        &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Both accumulators must be cleared, not just the weight's.
&lt;/span&gt;        &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop is four steps: predict and score, calculate gradients, update, and clear&lt;br&gt;
the gradients. The full program also sets up the data and parameters,&lt;br&gt;
checks the first gradient, reports progress, and saves and reloads the result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Watching the hand loop run
&lt;/h2&gt;

&lt;p&gt;The output below is the recorded verification run from the lab. The script's&lt;br&gt;
comments have since been clarified; its calculations and reporting are unchanged.&lt;/p&gt;

&lt;p&gt;From the parent repository root on my control Mac, I run the code on Spark in a&lt;br&gt;
fresh directory to preserve earlier checkpoints. Keep &lt;code&gt;run_dir&lt;/code&gt; for the later&lt;br&gt;
load command. For a public-only clone, drop &lt;code&gt;public/&lt;/code&gt; from the local source path.&lt;br&gt;
I expect loss to fall and the parameters to approach 1.8 and 32:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;run_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ssh spark &lt;span class="s1"&gt;'mktemp -d /tmp/week07-reviewed.XXXXXXXX'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$run_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ssh spark &lt;span class="s2"&gt;"cd '&lt;/span&gt;&lt;span class="nv"&gt;$run_dir&lt;/span&gt;&lt;span class="s2"&gt;' &amp;amp;&amp;amp; ~/venvs/w1/bin/python - &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    --epochs 20000 --lr 0.0003 --seed 0 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    --checkpoint manual_celsius_to_fahrenheit.pt --output results.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt; public/week-07-autograd-training-loop/train_manual.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/tmp/week07-reviewed.RXvW4Ygo
device=cpu
before training: weight=1.540996 bias=-0.293429
loss.grad_fn type=MeanBackward0
first grad backward(): weight=-2314.6404 bias=-73.8247
first grad by hand   : weight=-2314.6404 bias=-73.8247
epoch     1 loss=    965.8369 weight=2.2354 bias=-0.2713
epoch    50 loss=    849.9656 weight=2.0501 bias=0.5317
epoch  1000 loss=    319.4210 weight=1.9533 bias=12.7090
epoch  5000 loss=      5.1848 weight=1.8195 bias=29.5422
epoch 10000 loss=      0.0300 weight=1.8015 bias=31.8129
epoch 20000 loss=      0.0000 weight=1.8000 bias=31.9981
after training:  weight=1.8000 bias=31.9981 (true weight 1.8, bias 32)
final loss (post-update, unrounded)=2.9371251457632752e-06
saved checkpoint to manual_celsius_to_fahrenheit.pt
inference: 25 C -&amp;gt; 76.9985 F (true 77)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your temporary directory name will differ. The &lt;code&gt;grad_fn&lt;/code&gt; type is &lt;code&gt;MeanBackward0&lt;/code&gt;,&lt;br&gt;
the final mean operation in the loss. The last table row prints &lt;code&gt;0.0000&lt;/code&gt; because&lt;br&gt;
it rounds to four decimal places; the raw final loss is about&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.0000029371&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, not zero. JSON now preserves raw values returned by PyTorch.&lt;br&gt;
The reported epoch losses are measured &lt;strong&gt;after&lt;/strong&gt; each selected update, unlike&lt;br&gt;
the loss used by &lt;code&gt;backward()&lt;/code&gt;, which was calculated before that update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gradient check passes on the six-example loss.&lt;/strong&gt; &lt;code&gt;backward()&lt;/code&gt; reported a&lt;br&gt;
first weight gradient of -2314.6404, and the hand-computed derivative gave the&lt;br&gt;
same -2314.6404. Same for the bias, -73.8247 from both. So on the real training&lt;br&gt;
loss, not just the toy one, the analytical derivative and autograd agree to&lt;br&gt;
every displayed digit. These computations use floating-point arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before and after, spelled out.&lt;/strong&gt; The weight started at 1.540996 and ended at&lt;br&gt;
1.8000. The bias started at -0.293429 and ended at 31.9981. That is the roadmap&lt;br&gt;
deliverable: each parameter shown before and after training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same answer as Week 6.&lt;/strong&gt; Week 6's &lt;code&gt;nn.Linear&lt;/code&gt; plus &lt;code&gt;optim.SGD&lt;/code&gt; ended at weight&lt;br&gt;
1.8000, bias 31.9981, and predicted 76.9985 F for 25 C. This hand-written loop&lt;br&gt;
ended at the same weight 1.8000, bias 31.9981, and predicted the same 76.9985 F,&lt;br&gt;
to every displayed digit. The starting weights differ because a seed makes a&lt;br&gt;
random draw reproducible, but &lt;code&gt;nn.Linear&lt;/code&gt; and &lt;code&gt;torch.randn&lt;/code&gt; use different rules to&lt;br&gt;
turn that draw into numbers, so seed 0 does not give the same start. Both runs&lt;br&gt;
still end near 1.8 and 32 because they minimize the same loss on the same&lt;br&gt;
straight-line data. Testing numerical agreement of the updates would require&lt;br&gt;
identical starting parameters and comparing intermediate results with a stated&lt;br&gt;
tolerance. Equivalent mathematical rules can round differently; this experiment&lt;br&gt;
shows matching displayed endpoints, not bit-for-bit identical updates.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two lines that look optional but are not
&lt;/h2&gt;

&lt;p&gt;The loop has two calls that a newcomer might drop. Both matter, and both are easy&lt;br&gt;
to show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the update is inside &lt;code&gt;torch.no_grad()&lt;/code&gt;.&lt;/strong&gt; The line&lt;br&gt;
&lt;code&gt;weight -= lr * weight.grad&lt;/code&gt; is itself a tensor operation on a tracked tensor. The&lt;br&gt;
graph should hold only the operations that produced the current loss; the update&lt;br&gt;
just changes the starting values for the next loop, so it must not be recorded.&lt;br&gt;
An &lt;strong&gt;in-place&lt;/strong&gt; change modifies the existing tensor instead of creating a new&lt;br&gt;
one. Without &lt;code&gt;torch.no_grad()&lt;/code&gt;, PyTorch refuses this in-place change and errors; with&lt;br&gt;
it, the update succeeds and the weight is still a trainable parameter afterward:&lt;/p&gt;

&lt;p&gt;The error calls &lt;code&gt;w&lt;/code&gt; a &lt;strong&gt;leaf&lt;/strong&gt;: here that means a trainable tensor created&lt;br&gt;
directly, rather than the result of another tracked calculation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
w = torch.tensor([2.0], requires_grad=True)  # One tracked value, shape (1,).
loss = (w * 3.0 - 1.0)**2  # Score squared prediction error for input 3, target 1.
loss.backward()  # Compute the gradient and store it in w.grad.
try:  # Demonstrate the failure when updating a tracked leaf without no_grad.
    w -= 0.1 * w.grad  # Attempt an in-place gradient update with learning rate 0.1.
except RuntimeError as e:
    print("without no_grad:")  # Label the failed attempt.
    print(str(e))  # Show PyTorch's reason for rejecting the update.
with torch.no_grad():  # Keep the parameter update out of the gradient graph.
    w -= 0.1 * w.grad  # The same update is now allowed.
print("with no_grad, new w:", w.item())
# Verify the weight still requires gradients for future training steps.
print("w.requires_grad still:", w.requires_grad)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;without no_grad:
a leaf Variable that requires grad is being used in an in-place operation.
with no_grad, new w: -1.0
w.requires_grad still: True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;no_grad()&lt;/code&gt; keeps the update out of the graph. It does the same job during&lt;br&gt;
inference: suppressing gradient recording for the operations inside its block.&lt;br&gt;
It leaves the parameter's &lt;code&gt;requires_grad=True&lt;/code&gt; flag intact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why gradients must be cleared.&lt;/strong&gt; &lt;code&gt;backward()&lt;/code&gt; adds new gradients onto whatever&lt;br&gt;
is already sitting in &lt;code&gt;.grad&lt;/code&gt;. It does not overwrite. Run a fresh forward and&lt;br&gt;
&lt;code&gt;backward()&lt;/code&gt; twice without clearing and the gradient doubles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
w = torch.tensor([2.0], requires_grad=True)  # One tracked value, shape (1,).
# Run two backward passes without clearing gradients or updating the weight.
for i in range(1, 3):
    loss = w * 3.0  # Use a simple loss whose weight gradient is always 3.
    loss.backward()  # Add the new gradient to whatever w.grad already holds.
    print("backward", i, "-&amp;gt; w.grad:", w.grad.item())
w.grad.zero_()  # Clear the accumulated gradient before the next backward pass.
loss = w * 3.0  # Make a fresh forward calculation with the unchanged weight.
loss.backward()  # Accumulate onto zero this time.
print("after zero_ and fresh backward -&amp;gt; w.grad:", w.grad.item())
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backward 1 -&amp;gt; w.grad: 3.0
backward 2 -&amp;gt; w.grad: 6.0
after zero_ and fresh backward -&amp;gt; w.grad: 3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The true gradient is 3.0, but the second &lt;code&gt;backward()&lt;/code&gt; without clearing reads 6.0.&lt;br&gt;
That is why the loop calls &lt;code&gt;weight.grad.zero_()&lt;/code&gt; and &lt;code&gt;bias.grad.zero_()&lt;/code&gt; every&lt;br&gt;
step; &lt;code&gt;zero_()&lt;/code&gt; fills the existing gradient tensor with zeros. In Week 6 the&lt;br&gt;
optimizer's &lt;code&gt;zero_grad()&lt;/code&gt; did this for me; here I do it directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Train mode versus evaluation mode
&lt;/h2&gt;

&lt;p&gt;Our tensor-only model has no &lt;code&gt;.train()&lt;/code&gt; or &lt;code&gt;.eval()&lt;/code&gt; method; those belong to&lt;br&gt;
&lt;code&gt;nn.Module&lt;/code&gt;, PyTorch's base class for model layers. Week 6's plain linear layer&lt;br&gt;
behaved the same in either mode. But&lt;br&gt;
some layers do behave differently, and the clearest example is dropout, a layer&lt;br&gt;
that randomly zeros some values during training so the model does not lean too&lt;br&gt;
hard on any single value. During evaluation it must stop doing that. You switch&lt;br&gt;
with &lt;code&gt;.train()&lt;/code&gt; and &lt;code&gt;.eval()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
from torch import nn
torch.manual_seed(0)  # Repeat this environment's random dropout choices.
# Give each input value a 50% chance of being zeroed during training.
drop = nn.Dropout(p=0.5)
x = torch.ones(8)  # Eight ones, shape (8,); requires_grad defaults to False.
drop.train()  # Enable random dropping and scaling of surviving values.
print("train mode :", drop(x))  # Show which values survived and their scaling.
drop.eval()  # Switch that same layer to evaluation behavior.
print("eval  mode :", drop(x))  # Run it again on the unchanged input.
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train mode : tensor([0., 0., 2., 0., 0., 0., 2., 2.])
eval  mode : tensor([1., 1., 1., 1., 1., 1., 1., 1.])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In train mode dropout zeroed several values (five of the eight here) and scaled&lt;br&gt;
the survivors up. Each value independently has a 50% chance of being dropped&lt;br&gt;
because &lt;code&gt;p=0.5&lt;/code&gt;, and survivors are multiplied by 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1/&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 so the average&lt;br&gt;
output stays about the same across many dropout samples. In eval mode it passed&lt;br&gt;
all eight values through unchanged, doing neither the dropping nor the scaling.&lt;br&gt;
That is why real inference code calls &lt;code&gt;.eval()&lt;/code&gt; first: you do not want random&lt;br&gt;
dropout altering predictions. My Week 6 &lt;code&gt;eval()&lt;/code&gt; call was setting exactly this&lt;br&gt;
mode, it just had nothing to change for a plain linear layer.&lt;/p&gt;
&lt;h3&gt;
  
  
  Mode and gradient recording are separate switches
&lt;/h3&gt;

&lt;p&gt;Use evaluation mode when you want a module's inference behavior. Use &lt;code&gt;no_grad()&lt;/code&gt;&lt;br&gt;
when you do not need a gradient graph, such as ordinary prediction or our manual&lt;br&gt;
parameter update. For inference with a module, normally use both.&lt;sup id="fnref6"&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Changes&lt;/th&gt;
&lt;th&gt;Leaves alone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.train()&lt;/code&gt; / &lt;code&gt;.eval()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Mode-sensitive layer behavior&lt;/td&gt;
&lt;td&gt;Gradient recording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torch.no_grad()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recording inside the block&lt;/td&gt;
&lt;td&gt;Module mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;param.grad.zero_()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stored gradient values&lt;/td&gt;
&lt;td&gt;Parameters and module mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these calls updates the parameters. The optional&lt;br&gt;
combined check tests the switches together&lt;br&gt;
and follows what happens to the graph and stored gradients between steps.&lt;/p&gt;
&lt;h2&gt;
  
  
  The checkpoint is just the learned tensors
&lt;/h2&gt;

&lt;p&gt;The training program saves a dictionary: the keys &lt;code&gt;weight&lt;/code&gt; and &lt;code&gt;bias&lt;/code&gt; name its&lt;br&gt;
two learned tensors. &lt;code&gt;weight.detach()&lt;/code&gt; shares the weight's storage but has no&lt;br&gt;
gradient history and does not require gradients; the original weight remains&lt;br&gt;
trainable. This is a minimal inference parameter checkpoint, not a full training&lt;br&gt;
restart: it contains no model code, optimizer state, epoch, or module mode.&lt;/p&gt;

&lt;p&gt;The printed directory and checkpoint filename locate the actual file:&lt;br&gt;
&lt;code&gt;/tmp/week07-reviewed.RXvW4Ygo/manual_celsius_to_fahrenheit.pt&lt;/code&gt; in my run.&lt;br&gt;
&lt;code&gt;run_dir&lt;/code&gt; still holds that directory in my control shell. This next SSH command&lt;br&gt;
starts a &lt;strong&gt;fresh Python process&lt;/strong&gt; there. It loads only the checkpoint, selects&lt;br&gt;
the two named tensors, creates a new input, and applies the prediction formula.&lt;br&gt;
I expect the same prediction as the training script's reload check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s2"&gt;"cd '&lt;/span&gt;&lt;span class="nv"&gt;$run_dir&lt;/span&gt;&lt;span class="s2"&gt;' &amp;amp;&amp;amp; ~/venvs/w1/bin/python -"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch

# Load the saved parameter dictionary, restricting loaded types.
# Only load a checkpoint you trust.
state = torch.load("manual_celsius_to_fahrenheit.pt", weights_only=True)
# Verify the checkpoint contains the expected weight and bias entries.
print(f"keys={list(state.keys())}")
# Inspect each saved parameter's shape and gradient flag.
for name, value in state.items():
    print(f"{name}: shape={tuple(value.shape)} requires_grad={value.requires_grad}")
with torch.no_grad():  # Prediction needs no gradient graph.
    # One new Celsius input, shape (1, 1).
    test_c = torch.tensor([[25.0]])
    # Compute the model prediction from input, weight, and bias.
    # Use the checkpoint's learned tensors rather than training-process variables.
    prediction = state["weight"] * test_c + state["bias"]
# Verify the prediction's shape and that it has no gradient tracking.
print(f"prediction: shape={tuple(prediction.shape)} "
      f"requires_grad={prediction.requires_grad}")
# Compare the reloaded model's prediction with the known answer.
print(f"inference: 25 C -&amp;gt; {prediction.item()} F (true 77)")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keys=['weight', 'bias']
weight: shape=(1,) requires_grad=False
bias: shape=(1,) requires_grad=False
prediction: shape=(1, 1) requires_grad=False
inference: 25 C -&amp;gt; 76.99851989746094 F (true 77)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is 76.9985 F at the training transcript's precision, from disk rather than&lt;br&gt;
variables left over from training. The loader supplies the formula; the file&lt;br&gt;
supplies its learned numbers. &lt;code&gt;weights_only=True&lt;/code&gt; restricts loading to tensors&lt;br&gt;
and supported simple data types. Only load checkpoints you trust.&lt;sup id="fnref7"&gt;7&lt;/sup&gt;&lt;br&gt;
These detached tensors already have tracking disabled; the &lt;code&gt;no_grad()&lt;/code&gt; block&lt;br&gt;
also makes the inference intent explicit. No &lt;code&gt;.eval()&lt;/code&gt; call is needed because&lt;br&gt;
there is no module here. Temporary directories may be cleaned up by the system;&lt;br&gt;
the lab shows how to retrieve results without replacing a captured baseline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is Week 7 in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;Celsius to Fahrenheit, F = C * 1.8 + 32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;two plain tensors, no &lt;code&gt;nn.Linear&lt;/code&gt;, no optimizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before training (weight / bias)&lt;/td&gt;
&lt;td&gt;1.540996 / -0.293429&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First gradient, backward()&lt;/td&gt;
&lt;td&gt;weight -2314.6404, bias -73.8247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First gradient, by hand&lt;/td&gt;
&lt;td&gt;weight -2314.6404, bias -73.8247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After training (weight / bias)&lt;/td&gt;
&lt;td&gt;1.8000 / 31.9981&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference: 25 C&lt;/td&gt;
&lt;td&gt;76.9985 F (rounds to 77)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning rate / epochs&lt;/td&gt;
&lt;td&gt;0.0003 / 20000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final loss, unrounded rerun&lt;/td&gt;
&lt;td&gt;2.9371251457632752e-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Week 6 comparison&lt;/td&gt;
&lt;td&gt;Same displayed endpoint, not a stepwise test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;I knew &lt;code&gt;backward()&lt;/code&gt; computed gradients, but I did not expect the hand-computed&lt;br&gt;
derivative to match it to every printed digit on the six-example loss. Seeing&lt;br&gt;
-2314.6404 twice made autograd feel concrete instead of mysterious. It is not&lt;br&gt;
estimating slopes by trying small changes; it computes the exact chain-rule&lt;br&gt;
derivative, in floating-point numbers.&lt;/p&gt;

&lt;p&gt;The other surprise was how similar the result is with and without &lt;code&gt;nn.Linear&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;optim.SGD&lt;/code&gt;. Removing both changed the starting weights but not the endpoint. The&lt;br&gt;
loop underneath is four steps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;The first trap is forgetting &lt;code&gt;torch.no_grad()&lt;/code&gt; around the update. Without it,&lt;br&gt;
PyTorch tries to track the subtraction and you get errors about modifying a tensor&lt;br&gt;
that requires grad in place. The fix is to wrap the update, which is exactly what&lt;br&gt;
an optimizer does internally.&lt;/p&gt;

&lt;p&gt;The second trap is forgetting to clear gradients, which silently mixes gradients&lt;br&gt;
from different steps. They can reinforce or cancel each other. The&lt;br&gt;
accumulation demo above is the quickest way to see it.&lt;/p&gt;

&lt;p&gt;The third is expecting &lt;code&gt;celsius.grad&lt;/code&gt; to exist. Our inputs have&lt;br&gt;
&lt;code&gt;requires_grad=False&lt;/code&gt;; the weight and bias are the values we ask PyTorch to&lt;br&gt;
differentiate. The leaf-tensor check shows their&lt;br&gt;
gradient fields side by side.&lt;/p&gt;
&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;Production training usually reaches for an optimizer like &lt;code&gt;optim.SGD&lt;/code&gt; or a&lt;br&gt;
higher-level trainer instead of updating each parameter by hand, but they run this&lt;br&gt;
same cycle underneath with more parameters and smarter update rules. When a&lt;br&gt;
training run diverges, or a gradient goes to zero, or a fine-tune does nothing,&lt;br&gt;
the thing to picture is these four steps: forward, backward, update, clear.&lt;br&gt;
Knowing that &lt;code&gt;backward()&lt;/code&gt; is plain calculus and that gradients accumulate unless&lt;br&gt;
cleared is enough to debug a surprising number of training problems.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 8 leaves the training loop and starts on how models turn text into numbers:&lt;br&gt;
token IDs, embedding vectors, the embedding table, and cosine similarity. That is&lt;br&gt;
the first step toward understanding how a language model represents meaning, and&lt;br&gt;
it connects back to the token IDs I met in Week 2.&lt;sup id="fnref8"&gt;8&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 7 lab has the manual training script, the captured run,&lt;br&gt;
observations, and troubleshooting notes.&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;I reused the Spark environment from Week 1. Before the experiments, I checked&lt;br&gt;
the machine architecture and PyTorch version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'uname -m; ~/venvs/w1/bin/python -c "import torch; print(torch.__version__)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aarch64
2.13.0+cu130
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;aarch64&lt;/code&gt; is ARM64. This is a CUDA-enabled PyTorch build, but the tiny training&lt;br&gt;
program uses the CPU. Seeded outputs are tied to this environment, not a&lt;br&gt;
promise that every PyTorch version or machine produces identical values.&lt;/p&gt;
&lt;h2&gt;
  
  
  Optional: a closer look
&lt;/h2&gt;

&lt;p&gt;The main loop is complete. These checks look more closely at where gradients&lt;br&gt;
are stored, how the six examples contribute, and how mode and recording interact.&lt;br&gt;
Read whichever answers a question you still have, or go straight to the full script.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where gradients are stored
&lt;/h3&gt;

&lt;p&gt;A trainable &lt;strong&gt;leaf tensor&lt;/strong&gt; has &lt;code&gt;requires_grad=True&lt;/code&gt; and was not produced by a&lt;br&gt;
recorded operation. Our directly created weight and bias are leaves; &lt;code&gt;pred&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;loss&lt;/code&gt; are recorded results. Tensors with &lt;code&gt;requires_grad=False&lt;/code&gt; are also called&lt;br&gt;
leaves by convention, even when calculated from other untracked tensors.&lt;br&gt;
By default, &lt;code&gt;backward()&lt;/code&gt; accumulates gradients in &lt;code&gt;.grad&lt;/code&gt; for leaves that require&lt;br&gt;
gradients and contribute to the loss. Here is that distinction in this model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
torch.manual_seed(0)  # Repeat the random start in this PyTorch environment.
# Six Celsius inputs, one temperature per row: shape (6, 1).
C = torch.tensor([[-40.],[-10.],[0.],[20.],[37.],[100.]])
F = C * 1.8 + 32  # Build the six target Fahrenheit answers, shape (6, 1).
# Draw a one-value weight, shape (1,), with gradient tracking enabled.
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)  # A separate random bias.
# Compute the model prediction from input, weight, and bias.
pred = w * C + b  # Reuse each parameter across all six rows, shape (6, 1).
# Score the squared prediction error against each target, then average.
loss = ((pred - F) ** 2).mean()
loss.backward()  # Compute gradients and accumulate them in leaf .grad fields.
# Compare which leaves require gradients and actually received them.
for name, t in [("weight ", w), ("bias   ", b), ("celsius", C)]:
    print(name, "is_leaf", t.is_leaf, "req_grad", t.requires_grad, "grad", t.grad)
# Contrast those leaves with the prediction's recorded producing operation.
print("pred   ", "is_leaf", pred.is_leaf, "grad_fn", type(pred.grad_fn).__name__)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weight  is_leaf True req_grad True grad tensor([-2314.6404])
bias    is_leaf True req_grad True grad tensor([-73.8247])
celsius is_leaf True req_grad False grad None
pred    is_leaf False grad_fn AddBackward0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The weight and bias are leaves with &lt;code&gt;requires_grad=True&lt;/code&gt;, so they get gradients.&lt;br&gt;
&lt;code&gt;celsius&lt;/code&gt; is a leaf but has &lt;code&gt;requires_grad=False&lt;/code&gt;, so its gradient is &lt;code&gt;None&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;pred&lt;/code&gt; is not a leaf at all; it carries a &lt;code&gt;grad_fn&lt;/code&gt; instead. Those weight and bias&lt;br&gt;
gradients, -2314.6404 and -73.8247, are the same ones the training run reports&lt;br&gt;
above.&lt;/p&gt;
&lt;h3&gt;
  
  
  How six examples contribute one gradient
&lt;/h3&gt;

&lt;p&gt;The six-example loss works the same way, one more step. The loss averages the&lt;br&gt;
squared error over six examples, so each example contributes its own&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;pred&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;target&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 term and the weight gradient is their mean.&lt;br&gt;
Here are the six contributions and their average, at the random starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
# Repeat this environment's random starting parameters.
torch.manual_seed(0)
# Six Celsius inputs, one temperature per row: shape (6, 1).
C = torch.tensor([[-40.],[-10.],[0.],[20.],[37.],[100.]])
F = C * 1.8 + 32  # Build the six target Fahrenheit answers, shape (6, 1).
w = torch.randn(1, requires_grad=True)  # One random weight, tracking enabled.
b = torch.randn(1, requires_grad=True)  # One random bias, tracking enabled.
# Compute the model prediction from input, weight, and bias.
pred = w * C + b  # The shape-(1,) parameters serve all six rows.
# Calculate each example's weight-gradient contribution: 2 * error * input.
contrib = (2.0 * (pred - F) * C)
print("per-example weight-grad contributions:")
# detach() drops tracking for this view; flatten() lays six rows out as (6,).
print(contrib.detach().flatten())
# Average the contributions to compare with the full-batch weight gradient.
print("mean =", contrib.mean().item())
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per-example weight-grad contributions:
tensor([  1754.6619,    594.0678,     -0.0000,  -1498.9403,  -3098.8667,
        -11638.7637])
mean = -2314.640380859375
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mean of the six contributions is -2314.6404, exactly the weight gradient the&lt;br&gt;
training run reports. So the full gradient is not a mystery number; it is the&lt;br&gt;
chain-rule slope averaged over the six examples. The bias works the same way with&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 replaced by 1, so each contribution is 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;pred&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;target&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, and&lt;br&gt;
their mean is -73.8247, the reported bias gradient. (The &lt;code&gt;.detach()&lt;/code&gt; in the&lt;br&gt;
command just drops gradient tracking so the numbers print cleanly; there is more&lt;br&gt;
on &lt;code&gt;detach()&lt;/code&gt; in the checkpoint section above.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Mode switches and fresh graphs
&lt;/h3&gt;

&lt;p&gt;This checks &lt;code&gt;.eval()&lt;/code&gt;, &lt;code&gt;no_grad()&lt;/code&gt;, and gradient clearing from the main table.&lt;br&gt;
None of these calls performs a parameter update. Each forward pass builds a new&lt;br&gt;
graph when recording is enabled. &lt;code&gt;backward()&lt;/code&gt; uses it and normally releases the&lt;br&gt;
saved intermediate values needed for another backward pass. The leaf &lt;code&gt;.grad&lt;/code&gt;&lt;br&gt;
buffers remain. Clearing those buffers changes their numbers, not the parameters&lt;br&gt;
or the graph. Think of a fresh calculation with a separate accumulator for its&lt;br&gt;
answer.&lt;/p&gt;

&lt;p&gt;Here is a direct check. I set dropout to &lt;code&gt;p=1.0&lt;/code&gt; so it drops every value in train&lt;br&gt;
mode, making the comparison deterministic. I expect eval mode to preserve the&lt;br&gt;
input's gradient flag, and &lt;code&gt;no_grad()&lt;/code&gt; to leave the module in train mode. Then&lt;br&gt;
two fresh forward passes should each produce gradient 36, with zero between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import torch
from torch import nn

# Drop every value during training to make the mode comparison deterministic.
drop = nn.Dropout(p=1.0)
x = torch.ones(3, requires_grad=True)  # Three ones, shape (3,), tracking on.
drop.eval()  # Switch the layer to evaluation behavior.
y = drop(x)  # Evaluation passes the input through unchanged.
# Check module mode and gradient tracking separately.
print("eval:", "training=", drop.training, "requires_grad=", y.requires_grad)
print("values:", y)  # Verify evaluation preserved the input values.
drop.train()  # Switch the same layer back to training behavior.
with torch.no_grad():  # Disable gradient recording without changing module mode.
    y = drop(x)  # Training dropout still zeros the same input.
# Check that no_grad left training mode enabled but suppressed tracking.
print("train + no_grad:", "training=", drop.training,
      "requires_grad=", y.requires_grad)
print("values:", y)  # Show whether training behavior still zeroed the values.

w = torch.tensor([2.0], requires_grad=True)  # One tracked value, shape (1,).
# Repeat with the same weight, clearing gradients between fresh graphs.
for step in range(1, 3):
    loss = (w * 3.0) ** 2  # Build a fresh graph for the same squared loss.
    print("forward", step, "grad_fn:", type(loss.grad_fn).__name__)
    loss.backward()  # Compute and accumulate the new weight gradient.
    print("after backward:", w.grad.item())  # Report the newly computed gradient.
    w.grad.zero_()  # Clear the gradient in place, without changing w.
    print("after zero_:", w.grad.item(), "w:", w.item())  # Show both values.
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eval: training= False requires_grad= True
values: tensor([1., 1., 1.], requires_grad=True)
train + no_grad: training= True requires_grad= False
values: tensor([0., 0., 0.])
forward 1 grad_fn: PowBackward0
after backward: 36.0
after zero_: 0.0 w: 2.0
forward 2 grad_fn: PowBackward0
after backward: 36.0
after zero_: 0.0 w: 2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eval left &lt;code&gt;requires_grad=True&lt;/code&gt;; &lt;code&gt;no_grad()&lt;/code&gt; left &lt;code&gt;training=True&lt;/code&gt; and dropout&lt;br&gt;
still zeroed the inputs. In the second check, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, so the weight gradient&lt;br&gt;
at 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mord mathnormal"&gt;w&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;36&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. Both passes report 36&lt;br&gt;
because I clear the accumulator between them. The weight stays 2 throughout:&lt;br&gt;
there is no update in this check. The equal &lt;code&gt;grad_fn&lt;/code&gt; type names describe the&lt;br&gt;
operation, not reuse of the same graph object.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reference: the complete training script
&lt;/h2&gt;

&lt;p&gt;This is the full lab program used by the training command above. The core loop&lt;br&gt;
is unchanged; here you can also follow setup, the gradient check, reporting,&lt;br&gt;
checkpoint saving, and reloading.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 7 - the training loop written by hand for Celsius to Fahrenheit.

Week 6 used nn.Linear and torch.optim.SGD. This version removes both. The weight
and bias are plain tensors with requires_grad=True, and the parameter update is
one line I write myself: param -= lr * param.grad. It uses the same mathematical
forward rule and plain-SGD update as Week 6, from a different random start.
This is not a claim of bitwise-identical floating-point results.

The problem is identical to Week 6: learn F = C * 1.8 + 32 from six examples,
with raw Celsius inputs and a small learning rate.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Configure the training run and where to save its checkpoint and results.
&lt;/span&gt;    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--epochs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--lr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3e-4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--seed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manual_celsius_to_fahrenheit.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Make the random starting parameters repeatable in this environment.
&lt;/span&gt;    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Six Celsius inputs, one temperature per row: shape (6, 1).
&lt;/span&gt;    &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;40.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;37.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="c1"&gt;# Labels are the six correct answers, also shape (6, 1). Only label creation
&lt;/span&gt;    &lt;span class="c1"&gt;# uses the known formula; training receives inputs and labels, not the rule.
&lt;/span&gt;    &lt;span class="c1"&gt;# Inputs and labels are fixed data; neither needs gradients.
&lt;/span&gt;    &lt;span class="n"&gt;fahrenheit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;

    &lt;span class="c1"&gt;# Each parameter has shape (1,): one randomly drawn number. requires_grad
&lt;/span&gt;    &lt;span class="c1"&gt;# asks PyTorch to track operations so backward() can compute derivatives.
&lt;/span&gt;    &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requires_grad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Draw a separate one-value bias with tracking enabled too.
&lt;/span&gt;    &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requires_grad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# item() extracts a one-element tensor as a Python number for reporting.
&lt;/span&gt;    &lt;span class="n"&gt;init_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;init_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before training: weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# One manual forward and backward to show two things:
&lt;/span&gt;    &lt;span class="c1"&gt;#   1. loss carries a grad_fn, so it remembers how it was built.
&lt;/span&gt;    &lt;span class="c1"&gt;#   2. backward() and a hand-written derivative can be compared numerically.
&lt;/span&gt;    &lt;span class="c1"&gt;# Compute the model prediction from input, weight, and bias.
&lt;/span&gt;    &lt;span class="c1"&gt;# Broadcasting reuses each shape-(1,) parameter across all six input rows.
&lt;/span&gt;    &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;
    &lt;span class="c1"&gt;# Score the squared prediction error against each target, then average
&lt;/span&gt;    &lt;span class="c1"&gt;# all six errors into one scalar loss, shape ().
&lt;/span&gt;    &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loss.grad_fn type=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad_fn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Follow the recorded graph to fill the two parameters' shape-(1,) .grad.
&lt;/span&gt;    &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Store the starting loss as a Python number for JSON, before any updates.
&lt;/span&gt;    &lt;span class="n"&gt;first_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Check the MSE derivatives without building another gradient graph:
&lt;/span&gt;    &lt;span class="c1"&gt;# dloss/dweight = mean(2 * error * input), dloss/dbias = mean(2 * error).
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# Average the six weight contributions, then extract the scalar number.
&lt;/span&gt;        &lt;span class="n"&gt;hand_w_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Bias contributions omit the input multiplier; average those too.
&lt;/span&gt;        &lt;span class="n"&gt;hand_b_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first grad backward(): weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first grad by hand   : weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hand_w_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hand_b_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Save both gradients before clearing them; .grad holds a tensor.
&lt;/span&gt;    &lt;span class="n"&gt;first_w_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_b_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Discard this demonstration's gradients before the actual training loop.
&lt;/span&gt;    &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Keep selected post-update measurements, not every training step.
&lt;/span&gt;    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# Report progress at these epochs, including the final requested epoch.
&lt;/span&gt;    &lt;span class="n"&gt;log_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# Each epoch uses all six examples for one parameter update.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Compute the model prediction from input, weight, and bias.
&lt;/span&gt;        &lt;span class="c1"&gt;# The forward pass produces six predictions, shape (6, 1).
&lt;/span&gt;        &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;
        &lt;span class="c1"&gt;# Score the squared prediction error against each target, then average.
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Backward pass fills weight.grad and bias.grad.
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Move each parameter opposite its gradient, scaled by the learning
&lt;/span&gt;        &lt;span class="c1"&gt;# rate. This is the mathematical plain-SGD rule, using the full batch.
&lt;/span&gt;        &lt;span class="c1"&gt;# no_grad keeps the in-place update out of the gradient graph.
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# Adjust the weight using its gradient and the learning rate.
&lt;/span&gt;            &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
            &lt;span class="c1"&gt;# Apply the same update rule to the bias, using its own gradient.
&lt;/span&gt;            &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
        &lt;span class="c1"&gt;# Clear the gradients, or the next backward() would add onto these.
&lt;/span&gt;        &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Both accumulators must be cleared, not just the weight's.
&lt;/span&gt;        &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Capture progress only at the selected reporting epochs.
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Measure again after the update, using the new weight and bias.
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;post_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                             &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epoch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; loss=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post_loss&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;12.4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# Format stdout for reading, but retain unrounded floats in JSON.
&lt;/span&gt;            &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epoch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;post_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;

    &lt;span class="n"&gt;final_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;final_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# The loop's loss precedes its last update. Recompute the final measurement
&lt;/span&gt;    &lt;span class="c1"&gt;# from the saved parameters, without changing those parameters.
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;final_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after training:  weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(true weight 1.8, bias 32)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Show the raw final loss so display rounding does not make it look zero.
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final loss (post-update, unrounded)=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_loss&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save a dictionary with two named shape-(1,) tensors. detach() removes
&lt;/span&gt;    &lt;span class="c1"&gt;# gradient tracking; the checkpoint contains values, not the training graph.
&lt;/span&gt;    &lt;span class="c1"&gt;# torch.save writes this dictionary to the path supplied by --checkpoint.
&lt;/span&gt;    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detach&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detach&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;
               &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;saved checkpoint to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Tiny same-process reload smoke test. The README also tests a fresh process.
&lt;/span&gt;    &lt;span class="c1"&gt;# weights_only restricts loading to tensors and supported simple data types.
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Do not record operations for this prediction-only check.
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# One row, one input: shape (1, 1). The result also has one element.
&lt;/span&gt;        &lt;span class="n"&gt;test_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
        &lt;span class="c1"&gt;# Predict Fahrenheit using the reloaded weight and bias, not training state.
&lt;/span&gt;        &lt;span class="n"&gt;predicted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;test_c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inference: 25 C -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;predicted&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; F (true 77)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Optionally save the run settings and measurements for later comparison.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Keep Python float values as returned by item(), with no extra rounding.
&lt;/span&gt;        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epochs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;init_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;init_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_weight_grad_backward&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_w_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_weight_grad_by_hand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hand_w_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_bias_grad_backward&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_b_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_bias_grad_by_hand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hand_b_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learned_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learned_bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inference_25c_f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;predicted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Week 6 companion lab and recorded training results:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Toy weight comparison, command and actual Spark output:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/week-07-autograd-training-loop/system-report.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/week-07-autograd-training-loop/system-report.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;PyTorch autograd mechanics:&lt;br&gt;
&lt;a href="https://docs.pytorch.org/docs/stable/notes/autograd.html" rel="noopener noreferrer"&gt;https://docs.pytorch.org/docs/stable/notes/autograd.html&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;PyTorch Linear layer and its matrix formula:&lt;br&gt;
&lt;a href="https://docs.pytorch.org/docs/stable/generated/torch.nn.Linear.html" rel="noopener noreferrer"&gt;https://docs.pytorch.org/docs/stable/generated/torch.nn.Linear.html&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Week 7 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-07-autograd-training-loop" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-07-autograd-training-loop&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;PyTorch gradient modes and evaluation mode:&lt;br&gt;
&lt;a href="https://docs.pytorch.org/docs/stable/notes/autograd.html#locally-disabling-gradient-computation" rel="noopener noreferrer"&gt;https://docs.pytorch.org/docs/stable/notes/autograd.html#locally-disabling-gradient-computation&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;PyTorch checkpoint loading and security warning:&lt;br&gt;
&lt;a href="https://docs.pytorch.org/docs/stable/generated/torch.load.html" rel="noopener noreferrer"&gt;https://docs.pytorch.org/docs/stable/generated/torch.load.html&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;Week 8 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-08.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-08.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 6 (Part 2): Watching a Neural Network Learn</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:11:51 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-6-part-2-watching-a-neural-network-learn-2pef</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-6-part-2-watching-a-neural-network-learn-2pef</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 2 of 8: Enough ML to understand inference. Week 6 of 32, part 2 of 2.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Part 1 built a one-neuron model that converts Celsius to Fahrenheit, made it&lt;br&gt;
predict, and scored how wrong it was with a single number, the loss. With random&lt;br&gt;
starting values the loss was 10352.21, because every prediction was nonsense. This&lt;br&gt;
post takes that one number and turns it into learning: the weight and bias climb&lt;br&gt;
from random noise to the true 1.8 and 32, and I watch each step happen on screen.&lt;/p&gt;

&lt;p&gt;Training is one loop repeated many times, and each pass through it does four&lt;br&gt;
things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Forward pass:&lt;/strong&gt; run the inputs through the model to get predictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loss:&lt;/strong&gt; score how wrong those predictions are, as a single number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradient:&lt;/strong&gt; work out which direction to move each parameter to lower the loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimizer step:&lt;/strong&gt; nudge each parameter a small amount in that direction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Part 1 covered the first two, prediction and loss. This post covers the last two,&lt;br&gt;
the steps that actually change the weight and bias, and then the full loop that&lt;br&gt;
repeats all four until the model has learned. So the two new ideas here are the&lt;br&gt;
&lt;strong&gt;gradient&lt;/strong&gt; (step 3) and the &lt;strong&gt;optimizer&lt;/strong&gt; (step 4).&lt;/p&gt;
&lt;h2&gt;
  
  
  From error to a direction: the gradient
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;gradient&lt;/strong&gt; is the slope of the loss for each parameter: which direction, and&lt;br&gt;
how steeply, the loss changes if that parameter moves. If nudging the weight up&lt;br&gt;
makes the loss go down, the gradient is what tells the optimizer to move it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backpropagation&lt;/strong&gt; computes those gradients for every parameter in one pass. In&lt;br&gt;
PyTorch it is the single call &lt;code&gt;loss.backward()&lt;/code&gt;. For our one-neuron model there&lt;br&gt;
are only two gradients to compute, one for the weight and one for the bias, but&lt;br&gt;
the same call scales to the billions of parameters in a large model.&lt;/p&gt;
&lt;h2&gt;
  
  
  The optimizer and the learning rate
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;optimizer&lt;/strong&gt; nudges each parameter using its gradient. The gradient points in&lt;br&gt;
the direction that increases the loss, so the optimizer subtracts it:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;new&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;old&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;learning&amp;nbsp;rate&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;gradient&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;That minus sign is why subtracting a negative gradient makes a parameter grow. I&lt;br&gt;
use plain SGD. The class is named &lt;code&gt;SGD&lt;/code&gt; (stochastic gradient descent), but&lt;br&gt;
"stochastic" normally means updating from random subsets of the data. This run&lt;br&gt;
uses all six examples for every update, so it is really full-batch gradient descent&lt;br&gt;
using the &lt;code&gt;SGD&lt;/code&gt; class.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;learning rate&lt;/strong&gt; is how big each nudge is. Too small learns slowly, so it&lt;br&gt;
needs more updates; too big overshoots and blows up, which I show below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Epoch and batch
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;epoch&lt;/strong&gt; is one pass over all the training data. A &lt;strong&gt;batch&lt;/strong&gt; is how many&lt;br&gt;
examples the model sees before each update. Here the dataset is six&lt;br&gt;
Celsius/Fahrenheit pairs and I use all of them at once, so one batch equals one&lt;br&gt;
epoch.&lt;/p&gt;

&lt;p&gt;Putting the four moves together, the loop is: forward pass, compute loss,&lt;br&gt;
backpropagate to get gradients, optimizer step, repeat. That loop is &lt;strong&gt;training&lt;/strong&gt;.&lt;br&gt;
Using the finished model to predict without changing it is &lt;strong&gt;inference&lt;/strong&gt;, which is&lt;br&gt;
what the earlier text-generation weeks were doing with pretrained models.&lt;/p&gt;
&lt;h2&gt;
  
  
  The training script
&lt;/h2&gt;

&lt;p&gt;Here is the whole thing. It builds the data, defines the one-layer model, shows the&lt;br&gt;
first predictions and the first gradient before any learning, runs the loop, and&lt;br&gt;
saves a checkpoint (a copy of the trained numbers on disk). A separate &lt;code&gt;predict.py&lt;/code&gt;&lt;br&gt;
loads that checkpoint for inference, shown right after this run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 6 - train the smallest useful neural network: Celsius to Fahrenheit.

The true rule is F = C * 1.8 + 32. A single linear layer y = w*x + b should
learn a weight near 1.8 and a bias near 32. The point is not the model. It is to
watch the weight and bias start random, the loss fall, the gradients drive the
change, and the trained parameters get saved to a .pt checkpoint. A separate
predict.py loads that checkpoint to run inference.

The inputs are raw Celsius, so the printed weight is the model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s real parameter.
Because the raw inputs are large, the first gradient is large too, which is why
the learning rate has to be small (0.0003). A large learning rate diverges.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--epochs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--lr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3e-4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# learning rate 0.0003
&lt;/span&gt;    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--seed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celsius_to_fahrenheit.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Six training inputs in Celsius. Each inner [ ] is one example with one
&lt;/span&gt;    &lt;span class="c1"&gt;# value, so the shape is 6 rows by 1 column (six examples, one feature).
&lt;/span&gt;    &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;40.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;37.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="c1"&gt;# The correct answer for each input. This is the only place the true rule
&lt;/span&gt;    &lt;span class="c1"&gt;# appears, and it only builds the labels; the model never sees it.
&lt;/span&gt;    &lt;span class="n"&gt;fahrenheit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;

    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# one layer: prediction = weight * C + bias
&lt;/span&gt;    &lt;span class="n"&gt;loss_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MSELoss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# scores how wrong the predictions are
&lt;/span&gt;    &lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SGD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# updates w, b
&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;init_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;init_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Show where the first loss comes from: predict, then average the
&lt;/span&gt;    &lt;span class="c1"&gt;# squared errors across the six examples.
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;first_preds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial predictions vs targets:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;first_preds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                       &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  C=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  pred=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  target=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# One manual step to expose the first gradient before the optimizer moves.
&lt;/span&gt;    &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_loss_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;first_loss_t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_loss_t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;w_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;b_grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first loss=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first_loss&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight_grad=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias_grad=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first weight update: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; * &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;= &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;init_w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w_grad&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# log_at picks the handful of epochs to print, so the output stays short.
&lt;/span&gt;    &lt;span class="n"&gt;log_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# One pass of the four steps from the intro (plus a housekeeping reset):
&lt;/span&gt;        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reset: clear gradients left from the last step
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# steps 1-2: predict, then score
&lt;/span&gt;        &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# step 3: backprop fills each parameter's gradient
&lt;/span&gt;        &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# step 4: nudge weight and bias down the loss
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# This block only logs progress; the learning already happened above.
&lt;/span&gt;            &lt;span class="c1"&gt;# Recompute the loss after the step so the printed loss and the
&lt;/span&gt;            &lt;span class="c1"&gt;# printed weight and bias all describe the same post-update model.
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;post_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# current weight, as a plain Python float
&lt;/span&gt;            &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# current bias
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epoch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; loss=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post_loss&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;12.4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# Keep this snapshot so results.json can chart the run later.
&lt;/span&gt;            &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epoch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

    &lt;span class="c1"&gt;# Training is finished. Read the final learned weight and bias, and the
&lt;/span&gt;    &lt;span class="c1"&gt;# loss of the finished model, which is now tiny.
&lt;/span&gt;    &lt;span class="n"&gt;final_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;final_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;final_loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loss_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;learned weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_loss=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_loss&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (true weight 1.8, bias 32)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state_dict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;saved state_dict to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;epochs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epochs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_weight_grad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_bias_grad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b_grad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learned_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learned_bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final_loss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details are worth calling out before running it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;celsius * 1.8 + 32.0&lt;/code&gt; line is the only place the true rule appears, and it&lt;br&gt;
only builds the answer labels. That gives pairs like &lt;code&gt;(-40, -40)&lt;/code&gt;, &lt;code&gt;(0, 32)&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;(100, 212)&lt;/code&gt;. The model receives the Celsius inputs and those Fahrenheit answers,&lt;br&gt;
never the formula, so recovering &lt;code&gt;1.8&lt;/code&gt; and &lt;code&gt;32&lt;/code&gt; is real learning, not copying.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;optimizer.zero_grad()&lt;/code&gt; clears the gradient buffers before each new&lt;br&gt;
forward/backward cycle. PyTorch adds new gradients onto whatever is already&lt;br&gt;
stored, so without clearing, the next cycle's gradients would pile on top of the&lt;br&gt;
last one. It does not touch the weight or bias, and it does not perform an update.&lt;/p&gt;

&lt;p&gt;The inputs are raw Celsius, so the printed weight is the model's actual parameter,&lt;br&gt;
with no rescaling. The catch is that raw inputs up to 100 make the first gradient&lt;br&gt;
large, which forces a small learning rate. That trade-off is the subject of its own&lt;br&gt;
section below.&lt;/p&gt;

&lt;p&gt;The script also uses &lt;code&gt;torch.no_grad()&lt;/code&gt; and saves a &lt;code&gt;state_dict&lt;/code&gt;; the &lt;code&gt;eval()&lt;/code&gt; call&lt;br&gt;
lives in &lt;code&gt;predict.py&lt;/code&gt;. All three are explained in a deferred section so they do not&lt;br&gt;
clutter the main story.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Watching it learn
&lt;/h2&gt;

&lt;p&gt;I ran the exact command below on the Spark. The &lt;code&gt;&amp;lt; ... train_tiny.py&lt;/code&gt; part reads&lt;br&gt;
the script file on my control Mac and pipes it in; Python and the training run on&lt;br&gt;
the Spark. The path starts with &lt;code&gt;public/&lt;/code&gt; because I run from the private parent&lt;br&gt;
repo; if you cloned only the public companion repo, drop that prefix and use&lt;br&gt;
&lt;code&gt;week-06-neural-network-basics/train_tiny.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt; public/week-06-neural-network-basics/train_tiny.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;device=cpu
initial weight=-0.007487 bias=0.536444
initial predictions vs targets:
  C= -40.0  pred=   0.836  target=  -40.0
  C= -10.0  pred=   0.611  target=   14.0
  C=   0.0  pred=   0.536  target=   32.0
  C=  20.0  pred=   0.387  target=   68.0
  C=  37.0  pred=   0.259  target=   98.6
  C= 100.0  pred=  -0.212  target=  212.0
first loss=10352.2070 weight_grad=-9237.2129 bias_grad=-127.3941
first weight update: -0.007487 - 0.0003 * -9237.2129 = 2.763677
epoch     1 loss=   1992.1444 weight=2.7637 bias=0.5747
epoch    50 loss=    806.2087 weight=2.0436 bias=1.3524
epoch   200 loss=    690.7745 weight=2.0255 bias=3.6312
epoch  1000 loss=    302.9773 weight=1.9493 bias=13.2121
epoch  5000 loss=      4.9179 weight=1.8190 bias=29.6063
epoch 10000 loss=      0.0285 weight=1.8014 bias=31.8178
epoch 20000 loss=      0.0000 weight=1.8000 bias=31.9981

learned weight=1.8000 bias=31.9981 final_loss=2.9334e-06 (true weight 1.8, bias 32)
saved state_dict to celsius_to_fahrenheit.pt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the whole point of the week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The start is random and wrong.&lt;/strong&gt; The initial weight is about -0.007 and the bias&lt;br&gt;
is about 0.54. Those come straight from &lt;code&gt;torch.manual_seed(0)&lt;/code&gt;. Every one of the&lt;br&gt;
six initial predictions is far from its target: at 100 C the model guesses -0.212&lt;br&gt;
instead of 212. Squaring those six errors and averaging them gives the first loss,&lt;br&gt;
10352.21, the same number Part 1 ended on. That is where the big first number comes&lt;br&gt;
from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gradient points the way.&lt;/strong&gt; Before any learning, &lt;code&gt;loss.backward()&lt;/code&gt; reported a&lt;br&gt;
weight gradient of -9237.2 and a bias gradient of -127.4. Both negative means the&lt;br&gt;
loss would drop if both parameters increased, which is correct: they need to climb&lt;br&gt;
toward 1.8 and 32.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One update, traced by hand.&lt;/strong&gt; The optimizer's rule is&lt;br&gt;
&lt;code&gt;new = old - learning_rate * gradient&lt;/code&gt;, applied to every parameter. The &lt;code&gt;0.0003&lt;/code&gt;&lt;br&gt;
in the run's &lt;code&gt;first weight update:&lt;/code&gt; line is that learning rate (the script's&lt;br&gt;
&lt;code&gt;--lr&lt;/code&gt;, which defaults to &lt;code&gt;3e-4&lt;/code&gt;, another way to write 0.0003). For the weight&lt;br&gt;
that is &lt;code&gt;-0.007487 - 0.0003 * -9237.2129 = 2.763677&lt;/code&gt;, and epoch 1 indeed shows&lt;br&gt;
&lt;code&gt;weight=2.7637&lt;/code&gt;. The same arithmetic on the bias is&lt;br&gt;
&lt;code&gt;0.536444 - 0.0003 * -127.3941 = 0.574662&lt;/code&gt;, and epoch 1 shows &lt;code&gt;bias=0.5747&lt;/code&gt;. So the&lt;br&gt;
first step is not magic; it is that one line of arithmetic done twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the learning rate is so small.&lt;/strong&gt; That first weight gradient is huge, about&lt;br&gt;
-9237, because the raw Celsius inputs are large (with these inputs, parameters, and&lt;br&gt;
MSE loss). Even multiplied by the tiny learning rate 0.0003, the weight still jumps&lt;br&gt;
from about 0 to 2.76 in one step, overshooting its target of 1.8. A larger learning&lt;br&gt;
rate would overshoot so hard the numbers explode, which I show below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The loss falls fast, then slows.&lt;/strong&gt; Reading the recomputed post-update loss, it&lt;br&gt;
drops from 1992 at epoch 1 to 806 by epoch 50, then grinds down to about 0.0000 by&lt;br&gt;
epoch 20000. The weight moves close to its target much sooner than the bias: by&lt;br&gt;
epoch 50 the weight is 2.04 and still drifting, while the bias is only 1.35 and has&lt;br&gt;
a long climb ahead. Why one parameter moves faster than the other is a gradient&lt;br&gt;
question I leave for Week 7.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It lands on the real rule, almost exactly.&lt;/strong&gt; The final weight is 1.8000 and the&lt;br&gt;
bias is 31.9981, with a final loss of 2.9e-06. It is not perfectly 32: the run&lt;br&gt;
stopped at 20000 epochs, and running to 40000 or 100000 epochs gives the exact same&lt;br&gt;
values, because the remaining SGD updates are smaller than what FP32 can represent,&lt;br&gt;
so the parameters stop changing. The network rediscovered &lt;code&gt;F = C * 1.8 + 32&lt;/code&gt; from&lt;br&gt;
six examples, to the displayed precision, without ever being told the formula.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The checkpoint is saved.&lt;/strong&gt; The last line writes the model's &lt;code&gt;state_dict&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;celsius_to_fahrenheit.pt&lt;/code&gt;, a saved copy of the trained weight and bias. That is&lt;br&gt;
the same idea as the model files from Week 3, at the smallest possible scale.&lt;br&gt;
Loading it back and predicting is the job of the next section. The &lt;code&gt;.pt&lt;/code&gt; file is&lt;br&gt;
written in the current directory on the Spark and is not committed to the repo.&lt;/p&gt;

&lt;p&gt;One timing detail explains the numbers. The first loss, 10352.21, is measured&lt;br&gt;
before any update. The epoch 1 loss, 1992.14, is recomputed after the update, so it&lt;br&gt;
already reflects the improved weight. Each logged row shows the loss and the&lt;br&gt;
parameters for the same post-update state, which is why they line up.&lt;/p&gt;
&lt;h2&gt;
  
  
  Loading the checkpoint in a standalone script
&lt;/h2&gt;

&lt;p&gt;Training and inference are separate jobs, so I keep them in separate scripts.&lt;br&gt;
Training, above, produced &lt;code&gt;celsius_to_fahrenheit.pt&lt;/code&gt;. Inference needs none of the&lt;br&gt;
training machinery, no loss, no gradients, no optimizer. It only rebuilds the&lt;br&gt;
model, loads the saved numbers into it, and calls it. Here is the whole&lt;br&gt;
&lt;code&gt;predict.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 6 - load the trained checkpoint and run one inference.

train_tiny.py trains the Celsius-to-Fahrenheit model and saves its parameters to
a .pt file. This script is the other half, with no training code at all: rebuild
the same model shape, load the saved parameters into it, and make a prediction.
Run train_tiny.py first so the checkpoint exists.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celsius_to_fahrenheit.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--celsius&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Build the SAME architecture the checkpoint was trained with. A state_dict
&lt;/span&gt;    &lt;span class="c1"&gt;# stores only numbers, not the model shape, so the layer must exist first.
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Load the saved weight and bias from the .pt file into this layer.
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_state_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# inference mode (no visible effect here, but the right habit)
&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loaded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bias=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# One input, wrapped to shape 1 row by 1 column like the training data.
&lt;/span&gt;    &lt;span class="n"&gt;celsius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="c1"&gt;# predicting only, so do not track gradients
&lt;/span&gt;        &lt;span class="n"&gt;fahrenheit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inference: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; C -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; F&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things make the load work. First, a &lt;code&gt;state_dict&lt;/code&gt; stores only the numbers, not&lt;br&gt;
the model's shape, so the script must build the same &lt;code&gt;nn.Linear(1, 1)&lt;/code&gt; before&lt;br&gt;
loading, the same point Part 1 made about a &lt;code&gt;state_dict&lt;/code&gt;. Second, &lt;code&gt;eval()&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;torch.no_grad()&lt;/code&gt; put the model in inference mode. Run it right after training, from&lt;br&gt;
the same directory so it finds the &lt;code&gt;.pt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt; public/week-06-neural-network-basics/predict.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loaded celsius_to_fahrenheit.pt
weight=1.8000 bias=31.9981
inference: 25.0 C -&amp;gt; 76.9985 F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loaded weight and bias are 1.8000 and 31.9981, the exact values training landed&lt;br&gt;
on, and 25 C predicts 76.9985 F, which rounds to the true 77 (25 * 1.8 + 32 = 77).&lt;br&gt;
Nothing was retrained. &lt;code&gt;predict.py&lt;/code&gt; only read the parameters &lt;code&gt;train_tiny.py&lt;/code&gt; wrote&lt;br&gt;
and applied the same &lt;code&gt;weight * C + bias&lt;/code&gt; from Part 1.&lt;/p&gt;
&lt;h2&gt;
  
  
  Learning rate: too big blows up, and the run repeats
&lt;/h2&gt;

&lt;p&gt;The small learning rate is not a random choice. Raw Celsius inputs make the&lt;br&gt;
gradients large, and at learning rate 0.1 the optimizer overshoots so far the loss&lt;br&gt;
explodes. Here is a short run at learning rate 0.1 that prints the loss in&lt;br&gt;
scientific notation each epoch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch
from torch import nn
torch.manual_seed(0)
# Same six Celsius inputs (C) and their Fahrenheit answers (F) as before.
C=torch.tensor([[-40.],[-10.],[0.],[20.],[37.],[100.]]); F=C*1.8+32
# Same model, loss, and optimizer, but a large learning rate of 0.1.
m=nn.Linear(1,1); lf=nn.MSELoss(); opt=torch.optim.SGD(m.parameters(),lr=0.1)
# Run 15 training steps and print the loss (%.4e = scientific notation).
for e in range(1,16):
    opt.zero_grad(); l=lf(m(C),F); l.backward(); opt.step()
    print("epoch %2d loss=%.4e"%(e,l.item()))
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;epoch  1 loss=1.0352e+04
epoch  2 loss=1.9073e+09
epoch  3 loss=3.8280e+14
epoch  4 loss=7.6828e+19
epoch  5 loss=1.5419e+25
epoch  6 loss=3.0946e+30
epoch  7 loss=6.2109e+35
epoch  8 loss=inf
epoch  9 loss=inf
epoch 10 loss=inf
epoch 11 loss=inf
epoch 12 loss=inf
epoch 13 loss=inf
epoch 14 loss=inf
epoch 15 loss=inf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loss grows fast, gaining about five orders of magnitude every step, and by&lt;br&gt;
epoch 8 it is &lt;code&gt;inf&lt;/code&gt; (floating-point infinity, the result of a number too large to&lt;br&gt;
represent). Once a value is &lt;code&gt;inf&lt;/code&gt; or &lt;code&gt;nan&lt;/code&gt; (short for "not a number") the run is&lt;br&gt;
dead. So the small learning rate is doing real work: it keeps the huge first&lt;br&gt;
gradient from throwing the model off a cliff.&lt;/p&gt;

&lt;p&gt;The run is also reproducible on this setup. &lt;code&gt;torch.manual_seed(0)&lt;/code&gt; fixes the&lt;br&gt;
initial random weight and bias, so on this Spark with PyTorch 2.13.0+cu130 the two&lt;br&gt;
tested seed-0 runs gave the same displayed values, and seeds 1 and 2 reached the&lt;br&gt;
same displayed 1.8 and 32 as well.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Training versus inference, made concrete
&lt;/h2&gt;

&lt;p&gt;The two scripts are the two modes I have been using loosely. &lt;code&gt;train_tiny.py&lt;/code&gt; does&lt;br&gt;
&lt;strong&gt;training&lt;/strong&gt;: every step calls &lt;code&gt;loss.backward()&lt;/code&gt; and &lt;code&gt;optimizer.step()&lt;/code&gt; to change&lt;br&gt;
the weights. &lt;code&gt;predict.py&lt;/code&gt; does &lt;strong&gt;inference&lt;/strong&gt;: it loads finished weights, calls&lt;br&gt;
&lt;code&gt;eval()&lt;/code&gt; and &lt;code&gt;torch.no_grad()&lt;/code&gt;, and never changes anything. These two calls do&lt;br&gt;
different jobs, and the deferred section spells them out.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; The earlier&lt;br&gt;
text-generation weeks ran models in this inference mode. This week is the first&lt;br&gt;
time I ran the training half of the loop, and splitting the scripts makes the line&lt;br&gt;
between the two obvious.&lt;/p&gt;
&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is the training run in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;Celsius to Fahrenheit, F = C * 1.8 + 32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;one linear layer, &lt;code&gt;nn.Linear(1, 1)&lt;/code&gt;, no activation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial weight / bias&lt;/td&gt;
&lt;td&gt;-0.007487 / 0.536444&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First loss&lt;/td&gt;
&lt;td&gt;10352.21 (squared Fahrenheit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First gradients&lt;/td&gt;
&lt;td&gt;weight -9237.21, bias -127.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First weight after one step&lt;/td&gt;
&lt;td&gt;2.7637&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loss at epoch 5000 (post-update)&lt;/td&gt;
&lt;td&gt;4.9179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learned weight / bias&lt;/td&gt;
&lt;td&gt;1.8000 / 31.9981&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final loss&lt;/td&gt;
&lt;td&gt;2.9e-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference: 25 C&lt;/td&gt;
&lt;td&gt;76.9985 F (rounds to 77)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimizer / learning rate&lt;/td&gt;
&lt;td&gt;SGD / 0.0003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Epochs / batch&lt;/td&gt;
&lt;td&gt;20000 / full batch of 6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;I expected the loss to fall. I did not expect the weight and the bias to learn at&lt;br&gt;
such different speeds. The weight moves close to its target within the first&lt;br&gt;
handful of updates, while the bias takes thousands more epochs to crawl from 0.5 up&lt;br&gt;
to nearly 32. The optimizer is not solving an equation; it is taking small steps&lt;br&gt;
downhill on the loss, and the two parameters travel at very different speeds. Week&lt;br&gt;
7 digs into why.&lt;/p&gt;

&lt;p&gt;The other satisfying part was watching six examples be enough to recover the rule&lt;br&gt;
to the displayed precision. A large language model can have billions of parameters&lt;br&gt;
and messy data, but the loop is the same one I just watched: predict, measure loss,&lt;br&gt;
get gradients, step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;My first version scaled the inputs down by 100 to keep SGD stable, then unscaled the&lt;br&gt;
weight before printing. That worked, but it created two different meanings of&lt;br&gt;
"weight": the model's internal parameter and the display value. The printed gradient&lt;br&gt;
belonged to the internal parameter, so it did not line up with the printed weight,&lt;br&gt;
which made the single most important step impossible to follow. Switching to raw&lt;br&gt;
inputs with a small learning rate removed the confusion. Now the printed weight is&lt;br&gt;
the real parameter, and the first-update arithmetic checks out exactly.&lt;/p&gt;

&lt;p&gt;The lesson that survived is about input size. Large inputs make large gradients,&lt;br&gt;
which force a small learning rate. Scaling inputs to a small range is the usual fix&lt;br&gt;
in real training; I left it out here only to keep every printed number literal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;I will not train models this way in production, but this loop is the foundation&lt;br&gt;
under everything that does. Fine-tuning, which this series reaches in Phase 8, is&lt;br&gt;
the same forward, loss, backward, step cycle, just with a pretrained model, far more&lt;br&gt;
parameters, and real data. Knowing what a gradient and a learning rate actually do&lt;br&gt;
makes the later fine-tuning weeks far less mysterious.&lt;/p&gt;

&lt;p&gt;It also explains two settings I will keep seeing. The learning rate is a key&lt;br&gt;
training setting: set it too high and training diverges, as the &lt;code&gt;--lr 0.1&lt;/code&gt; run&lt;br&gt;
showed. And a checkpoint here is a saved &lt;code&gt;state_dict&lt;/code&gt;, the same idea as the model&lt;br&gt;
files I inspected in Week 3, at the smallest possible scale.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 7 stays on the training loop and goes one level deeper: the computational&lt;br&gt;
graph, &lt;code&gt;requires_grad&lt;/code&gt;, and writing the forward, &lt;code&gt;backward()&lt;/code&gt;, and optimizer step by&lt;br&gt;
hand instead of leaning on the tidy loop above. That turns this week's&lt;br&gt;
&lt;code&gt;loss.backward()&lt;/code&gt; from a black box into something I can trace.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The training questions, answered
&lt;/h2&gt;

&lt;p&gt;A few terms in the run reward a closer look. They are not needed to follow the main&lt;br&gt;
story, so I pulled them here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What exactly is a &lt;code&gt;state_dict&lt;/code&gt;?&lt;/strong&gt; For this model it is a mapping from parameter&lt;br&gt;
name to tensor, holding just the learned &lt;code&gt;weight&lt;/code&gt; and &lt;code&gt;bias&lt;/code&gt;. You can print each&lt;br&gt;
name with its tensor shape and value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -c "
import torch; from torch import nn
torch.manual_seed(0)
# state_dict() returns the layer'&lt;/span&gt;s saved parameters as name -&amp;gt; tensor pairs.
&lt;span class="nv"&gt;sd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nn.Linear&lt;span class="o"&gt;(&lt;/span&gt;1,1&lt;span class="o"&gt;)&lt;/span&gt;.state_dict&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="c"&gt;# Print each parameter name, its shape, and its values.&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;name, tensor &lt;span class="k"&gt;in &lt;/span&gt;sd.items&lt;span class="o"&gt;()&lt;/span&gt;:
    print&lt;span class="o"&gt;(&lt;/span&gt;name, tuple&lt;span class="o"&gt;(&lt;/span&gt;tensor.shape&lt;span class="o"&gt;)&lt;/span&gt;, tensor&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weight (1, 1) tensor([[-0.0075]])
bias (1,) tensor([0.5364])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the two entries really are tensors carrying the parameter values (here the&lt;br&gt;
random start, before training). The &lt;code&gt;state_dict&lt;/code&gt; stores the numbers, not the code.&lt;br&gt;
It does not know it came from an &lt;code&gt;nn.Linear(1, 1)&lt;/code&gt;, which is why the script must&lt;br&gt;
build that same layer again before loading the values into it. Calling every&lt;br&gt;
checkpoint "just a &lt;code&gt;state_dict&lt;/code&gt;" would be too broad; this is what a PyTorch&lt;br&gt;
checkpoint holds, not every packaging format from Week 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do &lt;code&gt;eval()&lt;/code&gt; and &lt;code&gt;no_grad()&lt;/code&gt; each do?&lt;/strong&gt; They sound like one setting but have&lt;br&gt;
separate jobs. &lt;code&gt;eval()&lt;/code&gt; switches layers whose behavior differs between training and&lt;br&gt;
inference into inference mode. A plain linear layer behaves the same either way, so&lt;br&gt;
&lt;code&gt;eval()&lt;/code&gt; has no visible effect on this model; I call it because it is the correct&lt;br&gt;
habit for real models that do have such layers. &lt;code&gt;no_grad()&lt;/code&gt; tells PyTorch to stop&lt;br&gt;
recording operations for gradient computation. (Not recording is expected to save&lt;br&gt;
memory and time during inference, though I do not measure that here.) Neither one&lt;br&gt;
freezes the weights. The weights stay put simply because there is no &lt;code&gt;backward()&lt;/code&gt;&lt;br&gt;
and no &lt;code&gt;optimizer.step()&lt;/code&gt; during inference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know other seeds still converge?&lt;/strong&gt; Because I ran them. Seed 0 twice, then&lt;br&gt;
seeds 1 and 2, all 20000 epochs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch
from torch import nn
# Train the model from scratch for one random seed and return the final w, b.
def run(seed):
    torch.manual_seed(seed)  # a different seed = a different random start
    C=torch.tensor([[-40.],[-10.],[0.],[20.],[37.],[100.]]); F=C*1.8+32
    m=nn.Linear(1,1); lf=nn.MSELoss(); opt=torch.optim.SGD(m.parameters(),lr=3e-4)
    for e in range(20000):  # the full training loop, 20000 steps
        opt.zero_grad(); l=lf(m(C),F); l.backward(); opt.step()
    return m.weight.item(),m.bias.item()
# Seed 0 twice (to show it repeats), then seeds 1 and 2 (different starts).
for s in [0,0,1,2]:
    w,b=run(s); print("seed %d -&amp;gt; w=%.4f b=%.4f"%(s,w,b))
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seed 0 -&amp;gt; w=1.8000 b=31.9981
seed 0 -&amp;gt; w=1.8000 b=31.9981
seed 1 -&amp;gt; w=1.8000 b=31.9981
seed 2 -&amp;gt; w=1.8000 b=31.9981
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed 0 gave the same displayed values across runs, and seeds 1 and 2 reached the&lt;br&gt;
same displayed 1.8 and 32. This task has a single best fit, and on this setup the&lt;br&gt;
tested seeds all reached it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 6 lab has the training and inference scripts, the captured runs,&lt;br&gt;
observations, and troubleshooting notes.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;PyTorch &lt;code&gt;torch.optim.SGD&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://docs.pytorch.org/docs/stable/generated/torch.optim.SGD.html" rel="noopener noreferrer"&gt;https://docs.pytorch.org/docs/stable/generated/torch.optim.SGD.html&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;See the deferred section,&lt;br&gt;
The training questions, answered.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Week 7 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-07.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-07.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Week 6 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 6 (Part 1): A Model That Predicts, and How Wrong It Is</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:11:00 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-6-part-1-a-model-that-predicts-and-how-wrong-it-is-3d80</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-6-part-1-a-model-that-predicts-and-how-wrong-it-is-3d80</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 2 of 8: Enough ML to understand inference. Week 6 of 32, part 1 of 2.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every week so far, the model already existed. I ran Phi-4, read Qwen's files, and&lt;br&gt;
measured tensors, but I never made a model learn anything. This week I build one&lt;br&gt;
from scratch and train it. That is a lot for one sitting, so I split the week into&lt;br&gt;
two posts and go slowly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt; the model starts with a random weight and bias, which are just&lt;br&gt;
wrong guesses. Training is what adjusts them, step by step, until they land on&lt;br&gt;
the values that actually convert Celsius to Fahrenheit: a weight of 1.8 and a&lt;br&gt;
bias of 32. Everything in these two posts is in service of that one idea.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This first post builds the model and answers two questions, nothing more: how does&lt;br&gt;
the model make a prediction, and how do we measure how wrong that prediction is?&lt;br&gt;
No learning yet. Part 2 takes the error measured here and turns it into learning.&lt;/p&gt;

&lt;p&gt;The model is a one-neuron linear model: one output value computed from one weight&lt;br&gt;
and one bias. It is deliberately tiny, so nothing hides the machinery. It runs on&lt;br&gt;
the CPU through &lt;code&gt;ssh spark&lt;/code&gt;, because the problem is far too small to need a GPU.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "learning" means here
&lt;/h2&gt;

&lt;p&gt;If you have never trained a model before, "learning" can sound like magic. It is&lt;br&gt;
not. It is trial and error run in a tight loop, the same way you would&lt;br&gt;
reverse-engineer an unknown function from its input and output logs.&lt;/p&gt;

&lt;p&gt;Picture a black box that turns numbers in into numbers out. You cannot see inside&lt;br&gt;
it, but you have a few examples of what it did: it turned -40 into -40, 0 into 32,&lt;br&gt;
and 100 into 212. You want a formula that reproduces those outputs. So you guess&lt;br&gt;
one, check how far each guess is from the real answer, nudge the guess in the&lt;br&gt;
direction that shrinks the error, and repeat until the answers line up. That loop&lt;br&gt;
is all that "training" is.&lt;/p&gt;

&lt;p&gt;A neural network does exactly this, with two differences. First, the "formula" is&lt;br&gt;
a set of adjustable numbers called parameters (the weights and biases from Week&lt;br&gt;
4), and learning means finding good values for them. Second, the nudging is&lt;br&gt;
automatic: the network measures its own error and works out which way to move each&lt;br&gt;
parameter, so nobody tweaks anything by hand.&lt;/p&gt;

&lt;p&gt;That loop has four moves: make a guess, measure how wrong it is, work out which way&lt;br&gt;
to adjust, and take a small step, then repeat. This post covers only the first&lt;br&gt;
two, making a guess (the &lt;strong&gt;forward pass&lt;/strong&gt;) and measuring how wrong it is (the&lt;br&gt;
&lt;strong&gt;loss&lt;/strong&gt;). Part 2 covers the adjusting. Splitting it this way keeps each idea tied&lt;br&gt;
to a real number you can see, instead of a pile of new words up front.&lt;/p&gt;

&lt;p&gt;The black box in this post is Celsius to Fahrenheit. The true rule is&lt;br&gt;
&lt;code&gt;F = C * 1.8 + 32&lt;/code&gt;. The model never sees that rule. I only use it to build the&lt;br&gt;
correct answers, then show the model input-output examples and let it discover on&lt;br&gt;
its own that the weight should be 1.8 and the bias should be 32.&lt;/p&gt;
&lt;h2&gt;
  
  
  The model: one neuron
&lt;/h2&gt;

&lt;p&gt;Week 4 said a &lt;strong&gt;weight&lt;/strong&gt; multiplies an input and a &lt;strong&gt;bias&lt;/strong&gt; is added after. This&lt;br&gt;
model is exactly that, one weight and one bias:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;prediction&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;weight&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Celsius&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;bias&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;That single weight-and-bias unit is a &lt;strong&gt;neuron&lt;/strong&gt;, the smallest building block of a&lt;br&gt;
&lt;strong&gt;neural network&lt;/strong&gt;. A neural network is just many neurons wired together, so the&lt;br&gt;
output of some becomes the input of others. Real networks stack thousands or&lt;br&gt;
millions of them; this week's has exactly one, which is why nothing hides the&lt;br&gt;
mechanics.&lt;/p&gt;

&lt;p&gt;In PyTorch that is a single &lt;strong&gt;linear layer&lt;/strong&gt;, &lt;code&gt;nn.Linear(1, 1)&lt;/code&gt;: one input&lt;br&gt;
&lt;strong&gt;feature&lt;/strong&gt; (the one input value) and one output value per example. A &lt;strong&gt;layer&lt;/strong&gt; is&lt;br&gt;
one processing step, and a &lt;strong&gt;linear transformation&lt;/strong&gt; is the multiply-and-add above.&lt;br&gt;
(Because there is a nonzero bias, a mathematician would call this an affine&lt;br&gt;
transformation, but PyTorch names the layer &lt;code&gt;Linear&lt;/code&gt;, so I use that word.) If the&lt;br&gt;
weight lands on 1.8 and the bias on 32, the layer computes Fahrenheit exactly.&lt;/p&gt;

&lt;p&gt;Why is this called &lt;strong&gt;linear&lt;/strong&gt;? Picture plotting the model's output against its&lt;br&gt;
input on a graph. A weight-times-input-plus-bias rule always traces a straight&lt;br&gt;
line. The weight sets the line's &lt;strong&gt;slope&lt;/strong&gt;: how steeply the output climbs as the&lt;br&gt;
input grows. The bias slides the whole line up or down. Celsius to Fahrenheit is a&lt;br&gt;
straight line with slope 1.8, because every extra degree Celsius adds exactly 1.8&lt;br&gt;
degrees Fahrenheit, the same amount at every temperature. That fixed,&lt;br&gt;
never-changing slope is what "linear" and "straight line" mean here, and a single&lt;br&gt;
linear layer can match it perfectly.&lt;/p&gt;

&lt;p&gt;This model has no activation function, the extra piece that would let it bend a&lt;br&gt;
straight line into a curve. Because Celsius to Fahrenheit is already straight, it&lt;br&gt;
does not need one. What an activation function is, and what a curve looks like, I&lt;br&gt;
unpack at the end, in &lt;em&gt;Wait, why is there no activation function?&lt;/em&gt;, so the main&lt;br&gt;
thread stays on the model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making a prediction: the forward pass
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;forward pass&lt;/strong&gt; runs the inputs through the layer to get predictions. In&lt;br&gt;
PyTorch it is the single expression &lt;code&gt;model(celsius)&lt;/code&gt;. Before any training the&lt;br&gt;
weight and bias are random, so the predictions will be nonsense, which is exactly&lt;br&gt;
what I want to see first.&lt;/p&gt;

&lt;p&gt;The six Celsius inputs are paired with their correct Fahrenheit answers. Each&lt;br&gt;
correct answer is called a &lt;strong&gt;label&lt;/strong&gt; or &lt;strong&gt;target&lt;/strong&gt;, and the line&lt;br&gt;
&lt;code&gt;fahrenheit = celsius * 1.8 + 32.0&lt;/code&gt; builds all six at once. There is no loop here.&lt;br&gt;
&lt;code&gt;celsius&lt;/code&gt; is a tensor, the small array type from Week 5, and multiplying a tensor&lt;br&gt;
by a single number multiplies every element by that number; adding 32 then adds it&lt;br&gt;
to every element. That is an element-wise operation, and it is why the result is&lt;br&gt;
another tensor of six answers, not one number. Note that &lt;code&gt;celsius&lt;/code&gt; is the input&lt;br&gt;
data, not the model. The model is the &lt;code&gt;nn.Linear&lt;/code&gt; layer built a few lines down.&lt;/p&gt;

&lt;p&gt;Each value in &lt;code&gt;celsius&lt;/code&gt; is written with a trailing dot, so &lt;code&gt;-40.&lt;/code&gt; means the&lt;br&gt;
floating-point number &lt;code&gt;-40.0&lt;/code&gt;, not the integer &lt;code&gt;-40&lt;/code&gt;. The dot keeps the tensor in&lt;br&gt;
decimals, which is what the model's math needs. And each value sits in its own&lt;br&gt;
brackets, like &lt;code&gt;[-40.]&lt;/code&gt;, so stacking the six of them makes a tensor shaped 6 rows&lt;br&gt;
by 1 column: six examples, one value each.&lt;/p&gt;

&lt;p&gt;To be clear, those six numbers are six separate Celsius inputs (-40, -10, 0, 20,&lt;br&gt;
37, and 100 degrees). &lt;code&gt;[-40.]&lt;/code&gt; and &lt;code&gt;[-10.]&lt;/code&gt; are two different examples, not an&lt;br&gt;
input-and-answer pair. Each input's Fahrenheit answer lives at the matching&lt;br&gt;
position in the separate &lt;code&gt;fahrenheit&lt;/code&gt; tensor, and the run below prints them side&lt;br&gt;
by side. The value -40 shows up as both the first input and its own answer only&lt;br&gt;
because -40 C equals -40 F, the one temperature where the Celsius and Fahrenheit&lt;br&gt;
scales meet, which is a coincidence of the scales, not the data layout.&lt;/p&gt;

&lt;p&gt;The command below builds the model, prints the device it runs on and its random&lt;br&gt;
starting weight and bias, then does one forward pass and lays each prediction next&lt;br&gt;
to its target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch  # PyTorch: the tensor and neural-network library
from torch import nn  # nn holds ready-made layers such as Linear

# Fix the random starting values so this run is repeatable and matches the post.
torch.manual_seed(0)

# The six training inputs, in Celsius. Each inner [ ] is one example holding one
# value, so the tensor shape is 6 rows by 1 column: six examples, one feature.
celsius = torch.tensor([[-40.], [-10.], [0.], [20.], [37.], [100.]])

# The correct answer for each input, built from the real rule F = C * 1.8 + 32.
# The model never sees this rule; it only sees the inputs and these answers.
fahrenheit = celsius * 1.8 + 32.0

# One linear layer with 1 input feature and 1 output: prediction = w * C + b.
model = nn.Linear(1, 1)

# weight and bias start at random values; .device shows they live on the CPU.
print(f"device={model.weight.device}")
print(f"initial weight={model.weight.item():.6f} bias={model.bias.item():.6f}")

# The forward pass: run all six inputs through the layer to get predictions.
# no_grad() means "just predict, do not record anything for training".
with torch.no_grad():
    preds = model(celsius)

# Show each prediction next to the answer it should have produced.
print("initial predictions vs targets:")
for c, p, f in zip(celsius.tolist(), preds.tolist(), fahrenheit.tolist()):
    print(f"  C={c[0]:&amp;gt;6.1f}  pred={p[0]:&amp;gt;8.3f}  target={f[0]:&amp;gt;7.1f}")
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;device=cpu
initial weight=-0.007487 bias=0.536444
initial predictions vs targets:
  C= -40.0  pred=   0.836  target=  -40.0
  C= -10.0  pred=   0.611  target=   14.0
  C=   0.0  pred=   0.536  target=   32.0
  C=  20.0  pred=   0.387  target=   68.0
  C=  37.0  pred=   0.259  target=   98.6
  C= 100.0  pred=  -0.212  target=  212.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two things to notice. &lt;code&gt;device=cpu&lt;/code&gt; confirms this runs on the CPU, the &lt;code&gt;.device&lt;/code&gt;&lt;br&gt;
attribute from Week 5, and the problem is far too small to need a GPU. And the&lt;br&gt;
random start makes every prediction wrong: the weight is about -0.007 and the bias&lt;br&gt;
about 0.54, straight from &lt;code&gt;torch.manual_seed(0)&lt;/code&gt; (a fixed seed, so you get these&lt;br&gt;
same numbers), so at 100 C the model guesses -0.212 instead of 212. The model has&lt;br&gt;
no idea what Fahrenheit is yet.&lt;/p&gt;

&lt;p&gt;So why are the predictions wrong when the correct answers are sitting right there&lt;br&gt;
in &lt;code&gt;fahrenheit&lt;/code&gt;? Because the model never looks at those answers when it predicts.&lt;br&gt;
A prediction is only &lt;code&gt;weight * celsius + bias&lt;/code&gt;, computed from the model's own&lt;br&gt;
weight and bias, which right now are the random -0.007 and 0.54. The &lt;code&gt;fahrenheit&lt;/code&gt;&lt;br&gt;
values are a separate answer key: they are used to score the predictions, and in&lt;br&gt;
Part 2 to teach the model, but the model does not see them while it predicts. It&lt;br&gt;
is like a student taking a test with the answer key face down. Until they learn&lt;br&gt;
the rule, the answers are guesses, no matter that the key is in the room.&lt;/p&gt;

&lt;p&gt;The answers are not inside the input tensor at all. &lt;code&gt;celsius&lt;/code&gt; and &lt;code&gt;fahrenheit&lt;/code&gt;&lt;br&gt;
are two separate tensors, two different variables. The call &lt;code&gt;model(celsius)&lt;/code&gt;&lt;br&gt;
passes only &lt;code&gt;celsius&lt;/code&gt;, so &lt;code&gt;fahrenheit&lt;/code&gt; never enters the model. Inside, the linear&lt;br&gt;
layer computes exactly &lt;code&gt;weight * celsius +&lt;br&gt;
bias&lt;/code&gt; from its own two numbers. The one and only place &lt;code&gt;fahrenheit&lt;/code&gt; is handed to&lt;br&gt;
anything is the loss, &lt;code&gt;loss_fn(preds, fahrenheit)&lt;/code&gt;, in the next section. You can&lt;br&gt;
prove the model uses only its weight, bias, and the input by recomputing its&lt;br&gt;
prediction by hand and comparing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch
from torch import nn
torch.manual_seed(0)
celsius = torch.tensor([[-40.], [-10.], [0.], [20.], [37.], [100.]])
fahrenheit = celsius * 1.8 + 32.0   # the answer key: a separate tensor

model = nn.Linear(1, 1)
w = model.weight.item()             # weight and bias: the model owns these
b = model.bias.item()

with torch.no_grad():
    preds = model(celsius)          # pass ONLY celsius, never fahrenheit
by_hand = celsius * w + b           # weight * input + bias, computed by hand

print(f"weight={w:.6f} bias={b:.6f}")
print("model(celsius) matches weight*celsius+bias:",
      torch.allclose(preds, by_hand))
print("prediction for 100 C:", round(preds[-1].item(), 4))
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weight=-0.007487 bias=0.536444
model(celsius) matches weight*celsius+bias: True
prediction for 100 C: -0.2122
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;model(celsius)&lt;/code&gt; and the hand-computed &lt;code&gt;weight * celsius + bias&lt;/code&gt; come out&lt;br&gt;
identical, so the model's output depends only on its two parameters and the&lt;br&gt;
Celsius input. The value &lt;code&gt;-0.2122&lt;/code&gt; is the same wrong guess for 100 C from the&lt;br&gt;
table above. &lt;code&gt;fahrenheit&lt;/code&gt; plays no part in producing it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measuring how wrong it is: the loss
&lt;/h2&gt;

&lt;p&gt;Now that the model can make predictions, we need a single number that says how good&lt;br&gt;
or bad those six predictions are as a group. That number is the &lt;strong&gt;loss&lt;/strong&gt;. A smaller&lt;br&gt;
loss means better predictions; a loss of zero means every prediction is exactly&lt;br&gt;
right.&lt;/p&gt;

&lt;p&gt;Building it starts with the &lt;strong&gt;error&lt;/strong&gt; for one example: how far a single prediction&lt;br&gt;
is from its target, which is just &lt;code&gt;prediction - target&lt;/code&gt;. For the 100 C example the&lt;br&gt;
prediction was -0.212 and the target is 212, so the error is about -212. Some&lt;br&gt;
errors come out negative (the guess was too low) and some positive (too high).&lt;/p&gt;

&lt;p&gt;To turn six errors into one score, I use mean squared error (&lt;strong&gt;MSE&lt;/strong&gt;). The name is&lt;br&gt;
the recipe read backwards: take each error, square it, then take the mean, which is&lt;br&gt;
the average.&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;MSE&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mrel mtight"&gt;=&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;prediction&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Fahrenheit&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Squaring does two useful things. It makes every error positive, so a guess that is&lt;br&gt;
too low and one that is too high both count as wrong instead of cancelling out. And&lt;br&gt;
it punishes big misses far more than small ones: an error of 200 becomes 40000,&lt;br&gt;
while an error of 2 becomes just 4. The command below does this by hand, one step at&lt;br&gt;
a time, then checks the result against PyTorch's built-in &lt;code&gt;nn.MSELoss&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch
from torch import nn
torch.manual_seed(0)
celsius = torch.tensor([[-40.], [-10.], [0.], [20.], [37.], [100.]])
fahrenheit = celsius * 1.8 + 32.0     # the correct answers

model = nn.Linear(1, 1)
with torch.no_grad():
    preds = model(celsius)            # the six predictions

# Step 1: the error for each example is prediction minus target.
errors = preds - fahrenheit
# Step 2: square each error so big misses count more and signs do not cancel.
squared = errors ** 2
print("per-example error and squared error:")
for e, s in zip(errors.flatten().tolist(), squared.flatten().tolist()):
    print(f"  error={e:&amp;gt;10.3f}  squared={s:&amp;gt;12.3f}")

# Step 3: the loss is the average of those six squared errors.
by_hand = squared.mean().item()
builtin = nn.MSELoss()(preds, fahrenheit).item()
print(f"average of squared errors (by hand) = {by_hand:.4f}")
print(f"nn.MSELoss() gives the same number  = {builtin:.4f}")
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per-example error and squared error:
  error=    40.836  squared=    1667.572
  error=   -13.389  squared=     179.257
  error=   -31.464  squared=     989.955
  error=   -67.613  squared=    4571.558
  error=   -98.341  squared=    9670.867
  error=  -212.212  squared=   45034.031
average of squared errors (by hand) = 10352.2070
nn.MSELoss() gives the same number  = 10352.2070
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the output top to bottom and the loss stops being a mystery number. Each row&lt;br&gt;
is one example's error and that error squared. The 100 C example dominates: its&lt;br&gt;
error of -212 squares to 45034, more than all the other five combined, which is&lt;br&gt;
exactly the effect of punishing big misses. Add the six squared errors and divide&lt;br&gt;
by six and you get 10352.2070, and PyTorch's &lt;code&gt;nn.MSELoss&lt;/code&gt; returns the identical&lt;br&gt;
value. So the built-in loss really is just the average of the squared errors,&lt;br&gt;
nothing more.&lt;/p&gt;

&lt;p&gt;One consequence of squaring: the loss is in squared Fahrenheit units, not degrees.&lt;br&gt;
A loss of 10352.21 does not mean the model is off by 10352 F. The number is not a&lt;br&gt;
temperature to read; it is a score to drive down. Right now it is huge, which fits,&lt;br&gt;
because the predictions are still random. In Part 2, every training step pushes this&lt;br&gt;
number lower.&lt;/p&gt;

&lt;p&gt;This is the whole job of Part 1. The model can make a prediction (the forward&lt;br&gt;
pass), and we can score how wrong it is with one number (the loss). Part 2 uses&lt;br&gt;
that one number to improve the model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Results so far
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task&lt;/td&gt;
&lt;td&gt;Celsius to Fahrenheit, F = C * 1.8 + 32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;one linear layer, &lt;code&gt;nn.Linear(1, 1)&lt;/code&gt;, no activation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial weight / bias&lt;/td&gt;
&lt;td&gt;-0.007487 / 0.536444&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst initial prediction&lt;/td&gt;
&lt;td&gt;100 C predicts -0.212 (target 212)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First loss (MSE)&lt;/td&gt;
&lt;td&gt;10352.21 (squared Fahrenheit)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What Part 2 covers
&lt;/h2&gt;

&lt;p&gt;Part 2 takes the loss from here and turns it into learning. It introduces the&lt;br&gt;
&lt;strong&gt;gradient&lt;/strong&gt; (which direction to move each parameter), &lt;strong&gt;backpropagation&lt;/strong&gt; (how&lt;br&gt;
PyTorch computes those directions), the &lt;strong&gt;optimizer&lt;/strong&gt; and &lt;strong&gt;learning rate&lt;/strong&gt; (how&lt;br&gt;
big a step to take), and &lt;strong&gt;epoch&lt;/strong&gt; and &lt;strong&gt;batch&lt;/strong&gt; (how the loop repeats). Then it&lt;br&gt;
runs the full training loop and watches the weight and bias climb from random noise&lt;br&gt;
to 1.8 and 32.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wait, why is there no activation function?
&lt;/h2&gt;

&lt;p&gt;Most neural networks include an &lt;strong&gt;activation function&lt;/strong&gt;: a step that reshapes a&lt;br&gt;
layer's output so the network can learn relationships that are not straight lines.&lt;br&gt;
This model leaves it out, and seeing why is a good way to learn what one does.&lt;/p&gt;

&lt;p&gt;Many relationships are not straight. When the output climbs quickly in one place&lt;br&gt;
and slowly in another, plotting it draws a &lt;strong&gt;curve&lt;/strong&gt;: a line whose slope keeps&lt;br&gt;
changing instead of staying fixed. A &lt;strong&gt;bend&lt;/strong&gt; is just a point where the slope&lt;br&gt;
changes. A few everyday examples make it concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A savings account with compound interest.&lt;/strong&gt; The balance grows slowly at first,
then faster and faster, because the interest itself earns interest. Over time the
line curves upward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A phone battery charging.&lt;/strong&gt; It races from 0 to 80 percent, then crawls through
the last stretch to 100. The rate keeps changing, so the line bends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A web server's response time as load rises.&lt;/strong&gt; Lightly loaded, a few more
requests barely change it. Near full capacity, each extra request adds much more
delay and the response time shoots up. Nearly flat, then steep.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Straight-line relationships, by contrast, hold one constant rate: total cost is the&lt;br&gt;
price per item times the number of items, distance is speed times time at a steady&lt;br&gt;
speed, and Celsius to Fahrenheit adds the same 1.8 for every degree. A single&lt;br&gt;
linear layer matches those exactly. The moment the rate has to change, a straight&lt;br&gt;
line cannot follow it, and that is the job an activation function does.&lt;/p&gt;

&lt;p&gt;The most common activation function is ReLU. The rule is simple: replace every&lt;br&gt;
negative value with zero, and leave positive values unchanged. Here it is applied&lt;br&gt;
to five sample numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -c "
import torch; from torch import nn
# Five sample numbers, two negative, a zero, and two positive.
x=torch.tensor([-2.0, -0.5, 0.0, 1.5, 3.0])
# ReLU replaces every negative with 0 and leaves the rest unchanged.
print(nn.ReLU()(x))"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tensor([0.0000, 0.0000, 0.0000, 1.5000, 3.0000])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two negative inputs came out as zero, the zero stayed zero, and the two&lt;br&gt;
positive ones passed through unchanged. That bend at zero is the ingredient that,&lt;br&gt;
stacked across many neurons and layers, lets a network trace curves like the ones&lt;br&gt;
above. Celsius to Fahrenheit is a straight line, though, so this model needs no&lt;br&gt;
activation function. Adding a ReLU here would actually break it: its Fahrenheit&lt;br&gt;
answers go negative (-40 C is -40 F), and ReLU would clamp those to zero. When a&lt;br&gt;
task does need curves, an activation function is what provides them, and a later&lt;br&gt;
week puts one to work.&lt;/p&gt;

&lt;p&gt;So what does a curve mean for a model in practice? It sets a ceiling on what the&lt;br&gt;
model can learn. A model built only from linear layers can draw straight lines and&lt;br&gt;
nothing else, no matter how its weights are set or how many layers you stack,&lt;br&gt;
because stacking straight-line layers just produces another straight line. If the&lt;br&gt;
real relationship bends, that model can never fit it well. An activation function&lt;br&gt;
is what lifts the ceiling: the bends let the network shape its output to follow&lt;br&gt;
curved data.&lt;/p&gt;

&lt;p&gt;That gives a simple rule for when to use one. If the relationship you are modeling&lt;br&gt;
really is a straight line, like this Celsius-to-Fahrenheit toy or a plain linear&lt;br&gt;
regression, you do not need an activation function. For almost everything else,&lt;br&gt;
recognizing an image, predicting a price from many features, understanding text,&lt;br&gt;
the relationship bends, so real networks add an activation function after every&lt;br&gt;
hidden layer by default. This model is the rare exception, which is exactly why it&lt;br&gt;
is a clean place to see what an activation function does by watching a model that&lt;br&gt;
works fine without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 6 lab has the training script used in Part 2, the captured runs,&lt;br&gt;
observations, and troubleshooting notes.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Week 6 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-06-neural-network-basics&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 5: Tensors, the Data Structure Behind Every Model</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Mon, 17 Aug 2026 01:12:44 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-5-tensors-the-data-structure-behind-every-model-18gc</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-5-tensors-the-data-structure-behind-every-model-18gc</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 2 of 8: Enough ML to understand inference. Week 5 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Phase 1 was about running models. Phase 2 is about understanding what happens&lt;br&gt;
inside them, starting with the one data structure they are all built from: the&lt;br&gt;
&lt;strong&gt;tensor&lt;/strong&gt;. This week I stop talking about model files and start touching the&lt;br&gt;
actual numbers, in PyTorch, on the GPU.&lt;/p&gt;

&lt;p&gt;If you write software, a tensor is a typed, multi-dimensional array that lives on&lt;br&gt;
a specific device. By the end of this post I can create tensors, read their shape&lt;br&gt;
and byte size, move them to the GPU, and measure how precision changes both speed&lt;br&gt;
and memory.&lt;/p&gt;

&lt;p&gt;This is also where the parallel &lt;strong&gt;CUDA track&lt;/strong&gt; starts. I am not writing GPU&lt;br&gt;
kernels yet, the small programs that run on the GPU. Level 1 is just being a&lt;br&gt;
competent CUDA user: picking a device, moving data to it, and timing GPU work&lt;br&gt;
correctly.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Everything runs on the DGX Spark over &lt;code&gt;ssh spark&lt;/code&gt;. I reuse the Week 1 PyTorch&lt;br&gt;
environment, &lt;code&gt;~/venvs/w1&lt;/code&gt;, which already has a CUDA build of PyTorch.&lt;/p&gt;
&lt;h2&gt;
  
  
  A tensor is a typed array with a shape and a device
&lt;/h2&gt;

&lt;p&gt;Here are the names used for different array dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;scalar&lt;/strong&gt; is a single number. Rank 0.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;vector&lt;/strong&gt; is a list of numbers. Rank 1.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;matrix&lt;/strong&gt; is a grid of numbers. Rank 2.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;tensor&lt;/strong&gt; is the general word for any of these, including 3-D and higher.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rank&lt;/strong&gt; is the number of dimensions. &lt;strong&gt;Shape&lt;/strong&gt; is the size along each dimension.&lt;br&gt;
&lt;strong&gt;Dtype&lt;/strong&gt; is the number format, the same FP32, FP16, and BF16 I measured in&lt;br&gt;
Week 4. &lt;strong&gt;Device&lt;/strong&gt; is where the tensor lives, the CPU or the GPU.&lt;/p&gt;

&lt;p&gt;Instead of describing this, I print it. The first script builds a scalar, vector,&lt;br&gt;
matrix, and 3-D tensor and reports each one's rank, shape, dtype, byte size, and&lt;br&gt;
device. It then shows three operations I explain right after: an &lt;strong&gt;element-wise&lt;/strong&gt;&lt;br&gt;
add (position by position), a &lt;strong&gt;broadcast&lt;/strong&gt; (a smaller tensor applied across a&lt;br&gt;
larger one), and a &lt;strong&gt;host-to-device transfer&lt;/strong&gt; (moving a tensor to the GPU):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 5 - tensor basics: rank, shape, dtype, bytes, and device.

Shows a scalar, vector, matrix, and 3D tensor, then one element-wise
operation, one broadcast, and one CPU-to-GPU transfer. Every printed size and
byte count comes from PyTorch, not from a hand estimate.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rank=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndim&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shape=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dtype=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;torch.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elem=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;element_size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;B &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nbytes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;B &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;scalar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tensor3d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=== rank, shape, dtype, bytes, device ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scalar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scalar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matrix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tensor3d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tensor3d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== same shape, three precisions ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bfloat16&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;torch.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                                &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== element-wise operation ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a + b =&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== broadcasting ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matrix shape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+ row shape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== device transfer ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;cpu_tensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;gpu_tensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cpu_tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_tensor.device&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu_tensor.device&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gpu_tensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CUDA not available; skipping GPU transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it on the Spark. These commands use &lt;code&gt;public/...&lt;/code&gt; paths because I run them&lt;br&gt;
from the parent repository. If you cloned the public companion repo, drop the&lt;br&gt;
&lt;code&gt;public/&lt;/code&gt; prefix and run from that repo root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt; public/week-05-pytorch-tensors/tensor_basics.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== rank, shape, dtype, bytes, device ===
scalar   rank=0 shape=() dtype=float32 elem=4B total=4B dev=cpu
vector   rank=1 shape=(3,) dtype=float32 elem=4B total=12B dev=cpu
matrix   rank=2 shape=(2, 3) dtype=float32 elem=4B total=24B dev=cpu
tensor3d rank=3 shape=(2, 3, 4) dtype=float32 elem=4B total=96B dev=cpu

=== same shape, three precisions ===
float32  rank=2 shape=(1024, 1024) dtype=float32 elem=4B total=4194304B dev=cpu
float16  rank=2 shape=(1024, 1024) dtype=float16 elem=2B total=2097152B dev=cpu
bfloat16 rank=2 shape=(1024, 1024) dtype=bfloat16 elem=2B total=2097152B dev=cpu

=== element-wise operation ===
a + b = [11.0, 22.0, 33.0]

=== broadcasting ===
matrix shape (2, 3) + row shape (3,)
result:
 [[2.0, 3.0, 4.0], [2.0, 3.0, 4.0]]

=== device transfer ===
cpu_tensor.device cpu
gpu_tensor.device cuda:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot in that output, so here is what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading rank, shape, dtype, and device
&lt;/h2&gt;

&lt;p&gt;The shape notation is worth decoding once. &lt;code&gt;()&lt;/code&gt; means zero dimensions, a single&lt;br&gt;
number. &lt;code&gt;(3,)&lt;/code&gt; means one dimension holding three values, the trailing comma&lt;br&gt;
marking it as a shape rather than a plain number. &lt;code&gt;(2, 3)&lt;/code&gt; means two rows and&lt;br&gt;
three columns. &lt;code&gt;(2, 3, 4)&lt;/code&gt; adds a third dimension.&lt;/p&gt;

&lt;p&gt;I never set a dtype in those constructors, and every tensor still came out&lt;br&gt;
&lt;code&gt;float32&lt;/code&gt;. That is PyTorch's default floating-point type. The precision block&lt;br&gt;
below sets the dtype on purpose.&lt;/p&gt;
&lt;h2&gt;
  
  
  Byte size is Week 4's formula, now measured
&lt;/h2&gt;

&lt;p&gt;Look at the vector: shape &lt;code&gt;(3,)&lt;/code&gt;, 4 bytes per element, 12 total bytes. That is&lt;br&gt;
just 3 times 4. The matrix is 2 times 3 times 4, which is 24 bytes. The rule is&lt;br&gt;
the same one from Week 4: total bytes equals element count times bytes per&lt;br&gt;
element. PyTorch reports it directly with &lt;code&gt;nbytes&lt;/code&gt;, so I no longer have to&lt;br&gt;
estimate.&lt;/p&gt;

&lt;p&gt;The three-precision block makes the point sharper. The same &lt;code&gt;(1024, 1024)&lt;/code&gt; shape&lt;br&gt;
is 4,194,304 bytes at FP32 but 2,097,152 bytes at FP16 and BF16, exactly half. A&lt;br&gt;
dtype decides two things at once: the byte size of every number, and which values&lt;br&gt;
the number can represent. This experiment measures the byte size. The next one&lt;br&gt;
looks at the values.&lt;/p&gt;
&lt;h2&gt;
  
  
  FP16 and BF16 are the same size but not the same numbers
&lt;/h2&gt;

&lt;p&gt;FP16 and BF16 both report 2 bytes, so they use the same memory. The difference is&lt;br&gt;
how they split those 16 bits between range (how large a number can get) and step&lt;br&gt;
size (how finely close numbers can be told apart). I read both directly with&lt;br&gt;
&lt;code&gt;torch.finfo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - &amp;lt;&amp;lt;PY
import torch
for name, dt in (("fp32", torch.float32),
                 ("fp16", torch.float16),
                 ("bf16", torch.bfloat16)):
    fi = torch.finfo(dt)
    print(f"{name:5} max={fi.max:.3e} smallest_step_near_1={fi.eps:.3e}")
PY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fp32  max=3.403e+38 smallest_step_near_1=1.192e-07
fp16  max=6.550e+04 smallest_step_near_1=9.766e-04
bf16  max=3.390e+38 smallest_step_near_1=7.812e-03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FP16 tops out at about 65,500 but has the finer step. BF16 keeps almost FP32's&lt;br&gt;
huge range but has a coarser step. That is the tradeoff in one line: FP16 gives&lt;br&gt;
finer detail over a small range, BF16 gives a wide range with coarser detail.&lt;br&gt;
Which one is better for training and inference is a later-week topic. This week I&lt;br&gt;
only need to see that same size does not mean same numbers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Element-wise operations and broadcasting
&lt;/h2&gt;

&lt;p&gt;Two everyday operations show up constantly, so they are worth naming.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;element-wise operation&lt;/strong&gt; works position by position. Adding &lt;code&gt;[1, 2, 3]&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;[10, 20, 30]&lt;/code&gt; gives &lt;code&gt;[11, 22, 33]&lt;/code&gt;. Nothing mixes across positions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broadcasting&lt;/strong&gt; lets PyTorch treat compatible dimensions as repeated, without&lt;br&gt;
making a full copy. Here the row's length 3 matches the matrix's last dimension&lt;br&gt;
of 3, so PyTorch adds the row to every row of the matrix. The output shows&lt;br&gt;
&lt;code&gt;[1, 2, 3]&lt;/code&gt; added to both rows of ones, giving &lt;code&gt;[2, 3, 4]&lt;/code&gt; twice. This is how&lt;br&gt;
model code adds the same bias vector to every row of a result in one line.&lt;/p&gt;
&lt;h2&gt;
  
  
  Device: CPU tensor versus CUDA tensor
&lt;/h2&gt;

&lt;p&gt;The last block shows the CUDA-track idea in miniature. A tensor's &lt;strong&gt;device&lt;/strong&gt; is&lt;br&gt;
where PyTorch will run its operations, either the &lt;strong&gt;host&lt;/strong&gt; (the CPU side) or a&lt;br&gt;
CUDA GPU. &lt;code&gt;cpu_tensor.device&lt;/code&gt; is &lt;code&gt;cpu&lt;/code&gt;. After&lt;br&gt;
&lt;code&gt;.to("cuda")&lt;/code&gt;, &lt;code&gt;gpu_tensor.device&lt;/code&gt; is &lt;code&gt;cuda:0&lt;/code&gt;. That &lt;code&gt;.to("cuda")&lt;/code&gt; is a&lt;br&gt;
&lt;strong&gt;host-to-device transfer&lt;/strong&gt;, and &lt;code&gt;cuda:0&lt;/code&gt; is &lt;strong&gt;device selection&lt;/strong&gt;, naming the&lt;br&gt;
first GPU. On the DGX Spark the CPU and GPU share one physical memory pool, so&lt;br&gt;
this is not a copy across a separate VRAM. The output proves the device label&lt;br&gt;
changed to &lt;code&gt;cuda:0&lt;/code&gt;, which is what the GPU needs before it will compute on the&lt;br&gt;
tensor. It does not measure transfer cost.&lt;/p&gt;

&lt;p&gt;This week begins CUDA-track Level 1: pick a device, put your data on it, measure&lt;br&gt;
correctly, and know what happens on out-of-memory. Level 1 continues over the&lt;br&gt;
next few weeks. Week 1 already covered the driver, toolkit, and CUDA-enabled&lt;br&gt;
PyTorch underneath it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Precision changes speed, not just memory
&lt;/h2&gt;

&lt;p&gt;Week 4 showed precision changes storage. Week 1 showed the GPU running a large&lt;br&gt;
matrix multiply much faster than the CPU. Week 5 puts those together and adds the&lt;br&gt;
missing dimension: precision also changes how fast the same operation runs.&lt;/p&gt;

&lt;p&gt;The benchmark multiplies two square matrices many times per precision. Two&lt;br&gt;
details keep GPU timing honest, both from the CUDA track. First a &lt;strong&gt;warmup&lt;/strong&gt;: the&lt;br&gt;
first GPU call pays a one-time setup cost, so I run several throwaway iterations&lt;br&gt;
before timing. Second &lt;strong&gt;synchronization&lt;/strong&gt;: CUDA runs work asynchronously, so&lt;br&gt;
without &lt;code&gt;torch.cuda.synchronize()&lt;/code&gt; I would be timing how long it takes to queue&lt;br&gt;
the work, not to finish it. Here is the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 5 - measure matmul speed and memory for FP32, FP16, and BF16.

Times a square matrix multiply on the GPU for three precisions with warmup and
CUDA synchronization, then compares one CPU run with one GPU run at FP32. All
timings use time.perf_counter around synchronized GPU work. Results print as a
table and optionally save to JSON.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;DTYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bf16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bfloat16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--iters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--warmup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;time_matmul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;warmup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cuda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;warmup&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iters&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gflops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="n"&gt;has_cuda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_cuda&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matmul &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, iters=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iters&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, device=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;median_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gflops&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;peak_mib&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DTYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;has_cuda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty_cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_peak_memory_stats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;time_matmul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warmup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;peak_mib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max_memory_allocated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gflops&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gflops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;peak_mib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peak_mib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;median_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gflops&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;peak_mib&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== CPU versus GPU at FP32 ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a_cpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b_cpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cpu_iters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iters&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cpu_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;time_matmul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_cpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_cpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_iters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_fp32   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cpu_s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ms   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;gflops&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;8.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gflops&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;speedup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_cuda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gpu_fp32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;speedup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu_s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;gpu_fp32&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;median_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu_fp32   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gpu_fp32&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;median_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ms   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gpu_fp32&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gflops&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;8.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; gflops&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu_is     &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;speedup&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x faster than cpu at fp32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warmup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warmup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu_iters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_iters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cpu_iters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precisions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_fp32_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu_s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu_speedup_fp32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;speedup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gflops&lt;/code&gt; reports billions of floating-point operations per second. A square&lt;br&gt;
matmul of size &lt;code&gt;n&lt;/code&gt; produces &lt;code&gt;n^2&lt;/code&gt; output cells, and each cell does about &lt;code&gt;n&lt;/code&gt;&lt;br&gt;
multiplications and &lt;code&gt;n&lt;/code&gt; additions, so the total is about &lt;code&gt;2 * n^3&lt;/code&gt; operations.&lt;br&gt;
Dividing that by the seconds taken gives a speed I can compare across precisions.&lt;br&gt;
The GPU results below are the median of 20 timed iterations after warmup; the CPU&lt;br&gt;
result is the median of 5, since each CPU run is much slower. I ran it at 4096:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - --size 4096 --iters 20'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &amp;lt; public/week-05-pytorch-tensors/benchmark_matmul.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;matmul 4096x4096, iters=20, device=cuda

precision   median_ms     gflops   peak_mib
fp32            7.440    18473.0      224.0
fp16            1.548    88807.9      128.0
bf16            1.576    87202.3      128.0

=== CPU versus GPU at FP32 ===
cpu_fp32      168.281 ms      816.7 gflops
gpu_fp32        7.440 ms    18473.0 gflops
gpu_is     22.6x faster than cpu at fp32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two clear results.&lt;/p&gt;

&lt;p&gt;First, the CPU-versus-GPU gap. The CPU does the FP32 matmul in about 168 ms. The&lt;br&gt;
GB10 GPU does it in about 7.4 ms, roughly 22.6 times faster. That matches the&lt;br&gt;
22.5x I measured in Week 1, a good sign the setup is consistent.&lt;/p&gt;

&lt;p&gt;Second, the new result: precision changes speed. FP16 and BF16 finished the same&lt;br&gt;
matmul in about 1.55 ms versus 7.44 ms for FP32, roughly 4.8 times faster, on the&lt;br&gt;
exact same shape and hardware, just by using 16-bit numbers.&lt;/p&gt;

&lt;p&gt;Memory dropped too. The two input matrices plus the temporary output matrix hold&lt;br&gt;
192 MiB of payload at FP32 and 96 MiB at FP16 or BF16, exactly half. PyTorch's&lt;br&gt;
measured peak, which is the most memory it had allocated at once (1 MiB is&lt;br&gt;
1024 x 1024 bytes), fell from 224 MiB to 128 MiB. That is about 43 percent, not&lt;br&gt;
exactly half, because the peak includes roughly 32 MiB of extra working&lt;br&gt;
allocation on top of the three matrices.&lt;/p&gt;

&lt;p&gt;FP16 and BF16 landed nearly equal in this run, about 1.55 versus 1.58 ms. They&lt;br&gt;
are the same size and run at nearly the same speed here, and they can swap order&lt;br&gt;
between runs. The choice between them is about the range-and-step tradeoff shown&lt;br&gt;
earlier, not about performance on this test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is Week 5 in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FP32 bytes per element&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP16 / BF16 bytes per element&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024x1024 FP32 tensor&lt;/td&gt;
&lt;td&gt;4,194,304 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024x1024 FP16 / BF16 tensor&lt;/td&gt;
&lt;td&gt;2,097,152 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU FP32 matmul (4096)&lt;/td&gt;
&lt;td&gt;7.44 ms, ~18,473 GFLOP/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU FP16 matmul (4096)&lt;/td&gt;
&lt;td&gt;1.55 ms, ~88,808 GFLOP/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU BF16 matmul (4096)&lt;/td&gt;
&lt;td&gt;1.58 ms, ~87,202 GFLOP/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU FP32 matmul (4096)&lt;/td&gt;
&lt;td&gt;168.28 ms, ~817 GFLOP/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU vs CPU at FP32&lt;/td&gt;
&lt;td&gt;about 22.6x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP16 vs FP32 speed&lt;/td&gt;
&lt;td&gt;about 4.8x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP32 vs FP16 measured peak&lt;/td&gt;
&lt;td&gt;224 MiB vs 128 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;The byte sizes matching Week 4 exactly was reassuring rather than surprising: a&lt;br&gt;
1024x1024 FP32 tensor really is 4,194,304 bytes, no rounding.&lt;/p&gt;

&lt;p&gt;The real surprise was that precision buys speed, not only memory. I already knew&lt;br&gt;
16-bit numbers use half the bytes. Seeing the same matmul run almost five times&lt;br&gt;
faster in FP16 made it clear why production inference leans on lower precision. It&lt;br&gt;
saves memory and time at once, at least on this one operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;My first timing attempt forgot &lt;code&gt;torch.cuda.synchronize()&lt;/code&gt;. Because CUDA runs work&lt;br&gt;
asynchronously, I was timing how long it took to launch the matmul, not to finish&lt;br&gt;
it, so the GPU looked far faster than it really is. Adding a warmup and a&lt;br&gt;
synchronize per timed iteration fixed it. Week 1 shows the related effect: its&lt;br&gt;
cold first GPU run took much longer than its warmed-up runs. GPU work is&lt;br&gt;
asynchronous, so time it with a warmup and a sync or you time the wrong thing.&lt;/p&gt;

&lt;p&gt;Single timings are also noisy, so the benchmark reports the median of 20 GPU&lt;br&gt;
iterations and 5 CPU iterations. The absolute milliseconds shift between runs,&lt;br&gt;
and FP16 and BF16 can trade places, but the groupings hold: 16-bit beats FP32,&lt;br&gt;
and the GPU beats the CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;This week explains a choice I will keep seeing: serving models in FP16 or BF16&lt;br&gt;
instead of FP32. Lower precision halves the tensor payload and, on this hardware,&lt;br&gt;
ran this dense matmul almost five times faster. That is one operation, not a&lt;br&gt;
whole model. A real model also reads weights from memory and runs other steps, so&lt;br&gt;
it will not simply be 4.8 times faster end to end. Even so, the direction is why&lt;br&gt;
production inference leans on 16-bit precision: it saves both memory and time.&lt;/p&gt;

&lt;p&gt;The CUDA-track habits matter for the rest of the series too. Correct GPU timing,&lt;br&gt;
explicit device placement, and watching peak memory are the starting tools.&lt;br&gt;
They measure elapsed time and capacity, not utilization or bandwidth, so they&lt;br&gt;
cannot yet classify a workload as compute-bound or memory-bound. Later CUDA-track&lt;br&gt;
profiling adds those measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 6 steps up from single tensors to a tiny neural network: inputs, a linear&lt;br&gt;
layer, an activation, a loss, and a training loop small enough to watch the&lt;br&gt;
weights change. That connects this week's tensor operations to how a model&lt;br&gt;
actually learns.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 5 lab has the tensor-basics script, the matmul benchmark, the&lt;br&gt;
captured results, observations, and troubleshooting notes.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;CUDA track (parallel), Level 1:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/cuda-track.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/cuda-track.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Week 6 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-06.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-06.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Week 5 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-05-pytorch-tensors" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-05-pytorch-tensors&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 4: What Model Parameters Actually Cost in Memory</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Mon, 20 Jul 2026 00:54:57 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-4-what-model-parameters-actually-cost-in-memory-2oij</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-4-what-model-parameters-actually-cost-in-memory-2oij</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 1 of 8: Comfortable running local models. Week 4 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;People throw around "7B", "14B", and "70B" like everyone knows what they mean.&lt;br&gt;
For a long time I nodded along without really knowing. This week I make those&lt;br&gt;
numbers concrete. By the end I can look at a model size and a precision and say&lt;br&gt;
how much memory the weights need, then explain why the running model needs more.&lt;/p&gt;

&lt;p&gt;This is the last week of Phase 1. In Week 1 I learned the machine, in Week 2 I&lt;br&gt;
ran a model through Ollama, and in Week 3 I read a model's files on Hugging Face.&lt;br&gt;
Now I connect the parameter count I saw in Week 3 to real memory on the DGX&lt;br&gt;
Spark.&lt;/p&gt;

&lt;p&gt;The calculator part runs anywhere with Python. The memory measurements run on&lt;br&gt;
the Spark over &lt;code&gt;ssh spark&lt;/code&gt;, the same setup as earlier weeks.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a parameter actually is
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;parameter&lt;/strong&gt; is a single learned number inside the model. Training adjusts&lt;br&gt;
billions of these numbers until the model predicts text well. When a model is&lt;br&gt;
called "3B", it has about 3 billion of these numbers.&lt;/p&gt;

&lt;p&gt;Wait, is a parameter the same thing as a token? No. A &lt;strong&gt;token&lt;/strong&gt; is a piece of&lt;br&gt;
text that enters or leaves the model. Tokens are the request and response data;&lt;br&gt;
parameters are the learned numbers stored in the model files.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;a piece of input or generated text&lt;/td&gt;
&lt;td&gt;a learned number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it comes from&lt;/td&gt;
&lt;td&gt;the prompt or model response&lt;/td&gt;
&lt;td&gt;training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens during inference&lt;/td&gt;
&lt;td&gt;input tokens arrive and output tokens are added&lt;/td&gt;
&lt;td&gt;values stay fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What its count affects here&lt;/td&gt;
&lt;td&gt;context and KV-cache use&lt;/td&gt;
&lt;td&gt;weight memory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They are connected, though. The tokenizer turns each token into an ID. The&lt;br&gt;
model uses learned parameters to turn those IDs into internal values, process&lt;br&gt;
them, and predict the next token. A 3B model therefore has about 3 billion&lt;br&gt;
parameters, not 3 billion tokens. The same parameters are reused for every&lt;br&gt;
token the model processes.&lt;/p&gt;

&lt;p&gt;Parameter is the umbrella term. Two kinds sit under it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;weight&lt;/strong&gt; multiplies an input value. Most parameters are weights.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;bias&lt;/strong&gt; is another learned value added after the multiply. Models usually
have far fewer bias entries than weight entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reported parameter count includes both. So when I say "weight memory" in this&lt;br&gt;
post, I mean the storage for all parameters, weights and biases together. The&lt;br&gt;
count is what drives the memory math.&lt;/p&gt;

&lt;p&gt;A few more words show up when you look inside a model, so here they are in plain&lt;br&gt;
terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;tensor&lt;/strong&gt; is a block of numbers. A single number is a scalar, a list is a
vector, a grid is a matrix, and a tensor is the general name for any of these.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;matrix&lt;/strong&gt; is a two-dimensional grid of numbers, the most common shape for a
block of weights.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;layer&lt;/strong&gt; is one processing stage. A model stacks many layers, and each one
holds its own tensors.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;shape&lt;/strong&gt; is the size of a tensor along each dimension, like 2048 by 2048.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-04%2Fw4-tensors-matrices-layers-shapes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-04%2Fw4-tensors-matrices-layers-shapes.png" alt="Scalar, vector, matrix, tensor, shape, and layer examples, followed by a&lt;br&gt;
stack of layers that forms a language model." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I do not need the internals of these this week. Week 5 works with tensors&lt;br&gt;
directly in PyTorch, and Week 11 explains layers. Here they are just the words&lt;br&gt;
for "the numbers we are about to measure".&lt;/p&gt;

&lt;p&gt;In Week 3 I read the parameter count from the Hugging Face API. Here I pin the&lt;br&gt;
API request to the same commit used for the weight index later in this post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qwen/Qwen2.5-3B-Instruct
&lt;span class="nv"&gt;$ REV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aa8e72537993ba99e69dfaafa59ed015b17504d1
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://huggingface.co/api/models/&lt;/span&gt;&lt;span class="nv"&gt;$MODEL&lt;/span&gt;&lt;span class="s2"&gt;/revision/&lt;/span&gt;&lt;span class="nv"&gt;$REV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="s1"&gt;'{
    revision: .sha,
    parameters: .safetensors.total,
    precision_groups: .safetensors.parameters
}'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"revision"&lt;/span&gt;: &lt;span class="s2"&gt;"aa8e72537993ba99e69dfaafa59ed015b17504d1"&lt;/span&gt;,
  &lt;span class="s2"&gt;"parameters"&lt;/span&gt;: 3085938688,
  &lt;span class="s2"&gt;"precision_groups"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"BF16"&lt;/span&gt;: 3085938688
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So this model has 3,085,938,688 parameters, and every one of them is stored in&lt;br&gt;
the BF16 format. That second fact is the other half of the memory question.&lt;/p&gt;
&lt;h2&gt;
  
  
  Precision: how many bytes each number takes
&lt;/h2&gt;

&lt;p&gt;A parameter is a number, and a number needs storage. &lt;strong&gt;Precision&lt;/strong&gt; is the format&lt;br&gt;
used to store each one. The format decides how many bits, and therefore how many&lt;br&gt;
bytes, every parameter takes.&lt;/p&gt;

&lt;p&gt;The short forms are worth spelling out. &lt;code&gt;FP&lt;/code&gt; means floating point, a format for&lt;br&gt;
fractional numbers. &lt;code&gt;BF16&lt;/code&gt; means bfloat16, a 16-bit floating-point format. &lt;code&gt;INT&lt;/code&gt;&lt;br&gt;
means integer. You saw &lt;code&gt;torch_dtype: bfloat16&lt;/code&gt; in the Week 3 config. Here is what&lt;br&gt;
the common formats cost per parameter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FP32&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BF16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FP32 is the 32-bit baseline, using 4 bytes per number. FP16 and BF16 are both&lt;br&gt;
16-bit formats, so both use 2 bytes. INT8 and INT4 are the low-bit formats&lt;br&gt;
produced by quantization, which is a whole phase later in this series (weeks 14&lt;br&gt;
to 16). INT4's 0.5 byte is an average: two 4-bit values pack into one byte. For&lt;br&gt;
now the only thing that matters is the bytes column.&lt;/p&gt;
&lt;h2&gt;
  
  
  GB versus GiB, before the numbers start
&lt;/h2&gt;

&lt;p&gt;Memory numbers come in two units, and mixing them causes confusion, so I define&lt;br&gt;
them once up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GB&lt;/strong&gt; is decimal: divide bytes by 1,000,000,000.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GiB&lt;/strong&gt; is binary: divide bytes by 1024 three times (1024 x 1024 x 1024).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculator prints both. Ollama later prints its own rounded &lt;code&gt;GB&lt;/code&gt; labels, so&lt;br&gt;
those are approximate runtime reports, not byte-exact comparisons.&lt;/p&gt;
&lt;h2&gt;
  
  
  The weight-memory formula
&lt;/h2&gt;

&lt;p&gt;Put the two facts together and the weight memory is simple multiplication:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;weight&amp;nbsp;bytes&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;parameter&amp;nbsp;count&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;bits&amp;nbsp;per&amp;nbsp;parameter&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Dividing by 8 converts bits to bytes. That is the whole formula. The Week 4&lt;br&gt;
deliverable is a small calculator that applies it, so I do not redo the&lt;br&gt;
arithmetic by hand each time.&lt;/p&gt;

&lt;p&gt;Here is the complete script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Estimate model weight memory from a parameter count and a precision.

The math is exact for a single uniform precision:

    weight_bytes = parameter_count * bits_per_parameter / 8

This is the tensor payload only. Real runtime memory is larger because of the
KV cache, activations, and framework overhead, and it cannot be derived from the
parameter count. The blog measures that runtime memory separately.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;

&lt;span class="n"&gt;BITS_PER_PARAMETER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bf16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;GB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000_000_000&lt;/span&gt;
&lt;span class="n"&gt;GIB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_mutually_exclusive_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parameter count in billions, e.g. 70 or 3.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exact parameter count, e.g. 3085938688&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BITS_PER_PARAMETER&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weight_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameter_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parameter_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;BITS_PER_PARAMETER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parameter_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parameter_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weight_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameter_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameter_count    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parameter_count&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precision          &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bits_per_parameter &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BITS_PER_PARAMETER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight_bytes       &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight_gb          &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;GB&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GB (decimal, / 1e9)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight_gib         &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;GIB&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB (binary, / 1024^3)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run the roadmap example first: a 70-billion-parameter model at INT4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 public/week-04-parameters-memory/model_memory.py \
  --parameters 70 --precision int4
parameter_count    70,000,000,000
precision          int4
bits_per_parameter 4
weight_bytes       35,000,000,000
weight_gb          35.00 GB (decimal, / 1e9)
weight_gib         32.60 GiB (binary, / 1024^3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;70 billion parameters at half a byte each is 35 GB of weights. The same 70B&lt;br&gt;
model at BF16 would be four times that, about 140 GB. That is roughly 130 GiB,&lt;br&gt;
already more than the Spark's 121 GiB pool before any runtime memory. This is&lt;br&gt;
why big models are so often quantized before anyone tries to run them locally.&lt;/p&gt;
&lt;h2&gt;
  
  
  Checking the formula against a real file
&lt;/h2&gt;

&lt;p&gt;A formula is only trustworthy if it matches reality. I ran the calculator with&lt;br&gt;
the exact Qwen count and its real BF16 precision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 public/week-04-parameters-memory/model_memory.py \
  --count 3085938688 --precision bf16
parameter_count    3,085,938,688
precision          bf16
bits_per_parameter 16
weight_bytes       6,171,877,376
weight_gb          6.17 GB (decimal, / 1e9)
weight_gib         5.75 GiB (binary, / 1024^3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The calculator says 6,171,877,376 bytes. Now the model's own Safetensors index,&lt;br&gt;
read directly with the pinned revision from Week 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ MODEL=Qwen/Qwen2.5-3B-Instruct
$ REV=aa8e72537993ba99e69dfaafa59ed015b17504d1
$ curl -sL "https://huggingface.co/$MODEL/raw/$REV/model.safetensors.index.json" \
    | jq '.metadata.total_size'
6171877376
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical. The formula is not a guess. For a uniform precision it lands exactly&lt;br&gt;
on the tensor-data bytes the index reports. That total is the parameter payload,&lt;br&gt;
not the whole file with its headers, but for the weight math it is the number I&lt;br&gt;
want.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the running model needs more than its weights
&lt;/h2&gt;

&lt;p&gt;Everything so far is weight storage: the model sitting on disk. A model doing&lt;br&gt;
work needs more memory than that. The extra memory has three main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Activation memory&lt;/strong&gt; is the temporary numbers created while processing a
request. They come and go as the model runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KV-cache memory&lt;/strong&gt; stores saved intermediate data from earlier tokens, so the
model does not recompute them for every new token. This grows with context
length. The KV cache is a Week 18 topic; here I only need to see that it
exists and costs memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework overhead&lt;/strong&gt; is the runtime's own working memory and buffers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact-file check used BF16 Qwen. This runtime experiment switches to&lt;br&gt;
Ollama's quantized &lt;code&gt;llama3.2:3b&lt;/code&gt;, the model already available from Week 2. Its&lt;br&gt;
2.0 GB package, 2.6 GB load, and 4.1 GB load belong to one comparison; they&lt;br&gt;
should not be compared with Qwen's 6.17 GB BF16 weights.&lt;/p&gt;

&lt;p&gt;Ollama reports the loaded size of a model, so I can compare it with the 2.0 GB&lt;br&gt;
package size. I expect the 4,096-token load to exceed 2.0 GB because the runtime&lt;br&gt;
needs memory beyond the package. I expect the 16,384-token load to be larger&lt;br&gt;
again because it reserves more KV-cache capacity. &lt;code&gt;ollama ps&lt;/code&gt; reports the total,&lt;br&gt;
so this experiment can show the change but cannot assign an exact number of&lt;br&gt;
bytes to the KV cache.&lt;/p&gt;

&lt;p&gt;I connect to the Spark once, then run each check directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh spark
$ ollama list | awk 'NR == 1 || $1 == "llama3.2:3b"'
NAME                       ID              SIZE      MODIFIED
llama3.2:3b                a80c4f17acd5    2.0 GB    3 days ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next I unload any existing copy, send one short request with a 4,096-token&lt;br&gt;
context, and inspect the loaded model. The API's &lt;code&gt;HTTP 200&lt;/code&gt; confirms that the&lt;br&gt;
otherwise silent request completed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ollama stop llama3.2:3b &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
$ curl -fsS -o /dev/null -w 'HTTP %{http_code}\n' \
    http://localhost:11434/api/generate \
    -d '{"model":"llama3.2:3b","prompt":"hi","stream":false,
    "options":{"num_ctx":4096,"num_predict":1}}'
HTTP 200
$ ollama ps
NAME           ID              SIZE      PROCESSOR    CONTEXT    UNTIL
llama3.2:3b    a80c4f17acd5    2.6 GB    100% GPU     4096       4 minutes from now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I repeat the same request with a 16,384-token context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ollama stop llama3.2:3b &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
$ curl -fsS -o /dev/null -w 'HTTP %{http_code}\n' \
    http://localhost:11434/api/generate \
    -d '{"model":"llama3.2:3b","prompt":"hi","stream":false,
    "options":{"num_ctx":16384,"num_predict":1}}'
HTTP 200
$ ollama ps
NAME           ID              SIZE      PROCESSOR    CONTEXT    UNTIL
llama3.2:3b    a80c4f17acd5    4.1 GB    100% GPU     16384      4 minutes from now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama lists the package as 2.0 GB. Loaded with a 4,096-token context, it reports&lt;br&gt;
2.6 GB. Loaded with a 16,384-token context, it reports 4.1 GB. The model did not&lt;br&gt;
change. Only the configured context capacity changed, and the reported loaded&lt;br&gt;
size grew by 1.5 GB. The KV cache grows with context, so it is the expected main&lt;br&gt;
cause, but &lt;code&gt;ollama ps&lt;/code&gt; reports one total and does not split KV cache, activations,&lt;br&gt;
and framework buffers.&lt;/p&gt;

&lt;p&gt;This is the practical lesson: weight memory is the floor, not the total. Runtime&lt;br&gt;
memory is larger, and context length is one of the settings that pushes it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is Week 4 in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Verified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Formula&lt;/td&gt;
&lt;td&gt;parameters × bits / 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70B at INT4&lt;/td&gt;
&lt;td&gt;35.00 GB of weights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen2.5-3B-Instruct at BF16&lt;/td&gt;
&lt;td&gt;6,171,877,376 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formula versus Safetensors index&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loaded at 4,096 context&lt;/td&gt;
&lt;td&gt;2.6 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loaded at 16,384 context&lt;/td&gt;
&lt;td&gt;4.1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark unified memory&lt;/td&gt;
&lt;td&gt;121 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;The formula matching the Safetensors index to the exact byte was satisfying.&lt;br&gt;
Weight memory really is just multiplication once you know the count and the&lt;br&gt;
precision.&lt;/p&gt;

&lt;p&gt;The context effect was the most useful. I knew the KV cache existed from Week 2,&lt;br&gt;
but seeing a 2.0 GB package report 4.1 GB of loaded memory just by raising&lt;br&gt;
context made it concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;I also had to be careful about GB versus GiB. The calculator prints both, and&lt;br&gt;
Ollama prints its own rounded GB labels, so I keep the units visible to avoid&lt;br&gt;
comparing the wrong things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;Weight memory sets the floor for whether a model fits. For capacity planning I&lt;br&gt;
would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Estimate weights from the count and precision, then confirm against the real
file for quantized models.&lt;/li&gt;
&lt;li&gt;Measure total runtime memory with the real prompt lengths and workload, since
the KV cache and activations depend on how the model is actually used, not on
the parameter count.&lt;/li&gt;
&lt;li&gt;Treat the Spark's 121 GiB unified pool as shared by the model, its runtime,
and everything else on the box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The formula tells me if the weights fit. The runtime measurement tells me if the&lt;br&gt;
working model fits. Both matter before scheduling hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 5 moves into PyTorch tensors directly. I will allocate tensors on the CPU&lt;br&gt;
and GPU, compare FP32, FP16, and BF16, and watch allocated memory change. That&lt;br&gt;
turns this week's byte math into something I can measure inside a running Python&lt;br&gt;
process.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Weeks 14 to 16 cover quantization properly, which is where the &lt;code&gt;Q4_K_M&lt;/code&gt; mixed&lt;br&gt;
format and its real bit cost get explained in full.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 4 lab has the calculator, direct runtime commands, captured&lt;br&gt;
results, observations, and troubleshooting notes.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; The&lt;br&gt;
quantized-file note holds the optional &lt;code&gt;Q4_K_M&lt;/code&gt; storage investigation with its&lt;br&gt;
full commands and output.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional depth: why Q4 uses more than four bits on disk
&lt;/h2&gt;

&lt;p&gt;The clean formula assumes every parameter uses the same number of bits. A&lt;br&gt;
quantized file can mix several tensor formats and store extra scale data, so a&lt;br&gt;
model labeled "4-bit" may use more than 4 bits per parameter on disk.&lt;/p&gt;

&lt;p&gt;I checked Ollama's &lt;code&gt;llama3.2:3b&lt;/code&gt; package. Its API reports 3,212,749,888&lt;br&gt;
parameters and &lt;code&gt;Q4_K_M&lt;/code&gt; quantization.&lt;sup id="fnref4"&gt;4&lt;/sup&gt; A clean INT4 calculation predicts&lt;br&gt;
1,606,374,944 bytes, but the model blob is 2,019,377,376 bytes. That works out&lt;br&gt;
to about 5.03 on-disk bits per parameter. I also matched the blob's byte size&lt;br&gt;
and SHA-256 digest to its manifest record, so this is the exact file Ollama&lt;br&gt;
references, not only a filename that looks right.&lt;/p&gt;

&lt;p&gt;This was the opposite of the uniform BF16 result. I expected a "4-bit" model to&lt;br&gt;
cost 4 bits per parameter, but this file is about 26 percent larger than the&lt;br&gt;
clean estimate. The fix was to use the manifest and real blob instead of treating&lt;br&gt;
the quantization label as an exact file-size promise.&lt;/p&gt;

&lt;p&gt;The companion note keeps the full manifest-to-blob chain and tensor-type&lt;br&gt;
counts.&lt;sup id="fnref3"&gt;3&lt;/sup&gt; For Week 4, the rule is enough: use the formula for a&lt;br&gt;
first estimate, then read the real file size for a quantized model.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Week 5 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-05.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-05.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Week 4 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-04-parameters-memory" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-04-parameters-memory&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Why a Q4 model uses more than four bits per parameter:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/week-04-parameters-memory/quantized-file-size.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/week-04-parameters-memory/quantized-file-size.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;GGUF and its K-quant mixed formats are from the llama.cpp project:&lt;br&gt;
&lt;a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md" rel="noopener noreferrer"&gt;https://github.com/ggml-org/ggml/blob/master/docs/gguf.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 3: Reading a Hugging Face Model Repository</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Sun, 19 Jul 2026 23:00:44 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-3-reading-a-hugging-face-model-repository-22al</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-3-reading-a-hugging-face-model-repository-22al</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 1 of 8: Comfortable running local models. Week 3 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Week 2 I ran Phi-4 through Ollama. Ollama gave me a friendly model name and&lt;br&gt;
hid most of the files underneath it. This week I remove that layer and inspect a&lt;br&gt;
model at its source: its Hugging Face page.&lt;/p&gt;

&lt;p&gt;The natural way to get to know a model is to open its web page, read the model&lt;br&gt;
card, and click through the files. So that is exactly how I start here, in an&lt;br&gt;
ordinary browser. By the end I can answer four questions before I download&lt;br&gt;
anything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What architecture and size is this model?&lt;/li&gt;
&lt;li&gt;Is it a base model or an instruction-tuned model?&lt;/li&gt;
&lt;li&gt;How much data will I download?&lt;/li&gt;
&lt;li&gt;Am I legally allowed to use it for my product?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I inspect &lt;code&gt;Qwen/Qwen2.5-3B-Instruct&lt;/code&gt;. Everything in this post is done by&lt;br&gt;
browsing its Hugging Face page: no GPU, no API key, no account. The companion&lt;br&gt;
lab repeats every one of these answers as reproducible &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; commands&lt;br&gt;
plus a small Python inspector, the "API way", for anyone who wants to script&lt;br&gt;
it.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The model card page is the front door
&lt;/h2&gt;

&lt;p&gt;I open the model's page in a browser.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Before reading any file,&lt;br&gt;
the top of the page already answers a lot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-card.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-card.png" alt="The Qwen2.5-3B-Instruct model card header on Hugging Face: the repository&lt;br&gt;
name, the tag row, the license badge, and the sidebar showing model size and&lt;br&gt;
tensor type." width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reading the header top to bottom:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Qwen / Qwen2.5-3B-Instruct&lt;/code&gt;&lt;/strong&gt; is the repository: the owner (&lt;code&gt;Qwen&lt;/code&gt;) and the
model name. This is the exact ID I hand to any tool that downloads the model.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;tag row&lt;/strong&gt; shows the task (&lt;code&gt;Text Generation&lt;/code&gt;), the framework
(&lt;code&gt;Transformers&lt;/code&gt;), the weight format (&lt;code&gt;Safetensors&lt;/code&gt;, a model-weight file
format), and a &lt;code&gt;chat&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;License: qwen-research&lt;/code&gt;&lt;/strong&gt; is a license label, not the license itself. I come
back to it below, because the label alone does not tell me whether I can use
this commercially.&lt;/li&gt;
&lt;li&gt;The three tabs are &lt;strong&gt;Model card&lt;/strong&gt;, &lt;strong&gt;Files and versions&lt;/strong&gt;, and &lt;strong&gt;Community&lt;/strong&gt;.
The whole week lives in the first two.&lt;/li&gt;
&lt;li&gt;The right sidebar already says &lt;strong&gt;Model size 3B params&lt;/strong&gt;, &lt;strong&gt;Tensor type BF16&lt;/strong&gt;
(bfloat16, a 16-bit number format), and &lt;strong&gt;Chat template&lt;/strong&gt;. So before opening a
single file I know this is a three-billion-parameter model, stored in a 16-bit
format, that ships a chat template.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The four names I kept mixing up
&lt;/h2&gt;

&lt;p&gt;The page uses four words for different parts of the same release, and I kept&lt;br&gt;
mixing them up at first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Plain meaning&lt;/th&gt;
&lt;th&gt;Where it shows on the page&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model family&lt;/td&gt;
&lt;td&gt;related models released together&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;Qwen2.5&lt;/code&gt; name and collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;the neural-network layout&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;config.json&lt;/code&gt; file (opened below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkpoint&lt;/td&gt;
&lt;td&gt;the learned parameter values&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;.safetensors&lt;/code&gt; weight files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;the versioned files that ship the model&lt;/td&gt;
&lt;td&gt;the whole page, owner plus name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;repository&lt;/strong&gt; is the page itself: the owner &lt;code&gt;Qwen&lt;/code&gt; plus the name&lt;br&gt;
&lt;code&gt;Qwen2.5-3B-Instruct&lt;/code&gt;. A Hugging Face repository is a Git repository designed to&lt;br&gt;
hold large ML files, so it carries a full commit history like any other&lt;br&gt;
repo.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;model family&lt;/strong&gt; is &lt;code&gt;Qwen2.5&lt;/code&gt;. The name and its linked collection show the&lt;br&gt;
family includes several sizes plus base and instruction-tuned&lt;br&gt;
variants.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;architecture&lt;/strong&gt; is the neural-network layout. The page does not print it in&lt;br&gt;
the header; it lives in &lt;code&gt;config.json&lt;/code&gt;, which I open later. Architecture describes&lt;br&gt;
the shape (layers, hidden width, attention heads), not the learned knowledge.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;checkpoint&lt;/strong&gt; is the learned numbers stored in the weight files. Two models&lt;br&gt;
can share the same architecture but hold different checkpoints, just as two&lt;br&gt;
containers can run the same application build with different data. The base and&lt;br&gt;
instruct models are exactly that: same shape, different learned weights.&lt;/p&gt;
&lt;h2&gt;
  
  
  Base model versus instruct model
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;base model&lt;/strong&gt; learns to predict the next token from a large training set. It&lt;br&gt;
is useful as a starting point for more training, but it is not automatically a&lt;br&gt;
good chat assistant.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;instruct model&lt;/strong&gt; starts from a base checkpoint and receives post-training&lt;br&gt;
that teaches it to follow requests and behave better in conversations. The&lt;br&gt;
&lt;strong&gt;model card&lt;/strong&gt; is the &lt;code&gt;README.md&lt;/code&gt; shown on the Model card tab. It describes what&lt;br&gt;
the model is, how it was trained, and its limits. Scrolling down the card, the&lt;br&gt;
model states plainly what it is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-card-features.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-card-features.png" alt="The model card lists the model as instruction-tuned, with training stage,&lt;br&gt;
parameter count, layer count, attention-head layout, and context&lt;br&gt;
length." width="799" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The card says "This repo contains the instruction-tuned 3B Qwen2.5 model" and&lt;br&gt;
lists &lt;strong&gt;Training Stage: Pretraining &amp;amp; Post-training&lt;/strong&gt;. That post-training step is&lt;br&gt;
what makes it an instruct model. The same block answers most of question 1 too:&lt;br&gt;
&lt;strong&gt;3.09B parameters&lt;/strong&gt;, &lt;strong&gt;36 layers&lt;/strong&gt;, &lt;strong&gt;16 query heads and 2 key/value heads&lt;/strong&gt;&lt;br&gt;
(more on that split in Week 10 and Week 13), and a native &lt;strong&gt;32,768-token&lt;/strong&gt;&lt;br&gt;
context length.&lt;/p&gt;

&lt;p&gt;The card is a claim by the publisher. The page backs it with a machine-readable&lt;br&gt;
link. The &lt;strong&gt;Model tree&lt;/strong&gt; box on the same page names the base model this one was&lt;br&gt;
fine-tuned from:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-tree.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-model-tree.png" alt="The Model tree box names the base model as Qwen/Qwen2.5-3B and shows this&lt;br&gt;
repository as a fine-tune of it." width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the lineage is explicit: &lt;code&gt;Qwen/Qwen2.5-3B-Instruct&lt;/code&gt; is a fine-tune of&lt;br&gt;
&lt;code&gt;Qwen/Qwen2.5-3B&lt;/code&gt;.&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Fine-tuning means continuing&lt;br&gt;
training from an existing checkpoint; here it is the post-training that turns the&lt;br&gt;
base model into an instruction follower. The architecture stays the same while&lt;br&gt;
post-training changes the weights. The companion lab confirms the weights really&lt;br&gt;
differ by comparing the two checkpoints' Hub-reported file fingerprints, but the&lt;br&gt;
model card and the Model tree already answer the question for a normal user.&lt;/p&gt;
&lt;h2&gt;
  
  
  The license is the first thing to check
&lt;/h2&gt;

&lt;p&gt;Question 4 (am I allowed to use this?) is the one I answer before running&lt;br&gt;
anything, because it can stop a project before it starts. The header badge says&lt;br&gt;
&lt;code&gt;qwen-research&lt;/code&gt;, a non-standard license, so the badge alone is not enough. I&lt;br&gt;
click the &lt;code&gt;LICENSE&lt;/code&gt; file on the Files tab and read it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-license.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-license.png" alt="The LICENSE file, a Qwen Research License Agreement, granting a license FOR&lt;br&gt;
NON-COMMERCIAL PURPOSES ONLY and requiring a separate license for commercial&lt;br&gt;
use." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;LICENSE&lt;/code&gt; file is the Qwen Research License Agreement. Line 19 grants use&lt;br&gt;
"FOR NON-COMMERCIAL PURPOSES ONLY", and the next line says commercial use&lt;br&gt;
requires a separate license from Alibaba Cloud.&lt;sup id="fnref6"&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;That is the most important result this week. The model page looks ready to use,&lt;br&gt;
but this 3B release is not licensed for commercial use by default. License review&lt;br&gt;
has to happen before model evaluation, not after a prototype is built. The Hub's&lt;br&gt;
machine-readable label for this is &lt;code&gt;license: other&lt;/code&gt;, a non-standard license, a&lt;br&gt;
detail I show in the lab.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Files and versions tab
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Files and versions&lt;/strong&gt; tab lists everything the repository ships. This is&lt;br&gt;
where question 3 (how much will I download?) gets answered:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-files.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdramasamy%2Ffrom-api-to-gpu%2Fmain%2Fblog-assets%2Fweek-03%2Fw3-files.png" alt="The Files and versions tab: twelve files with sizes, a total repository size&lt;br&gt;
of 6.18 GB, and the latest commit on the main branch." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tab shows a total of &lt;strong&gt;6.18 GB&lt;/strong&gt; across &lt;strong&gt;12 files&lt;/strong&gt;, and the header line&lt;br&gt;
records the branch (&lt;code&gt;main&lt;/code&gt;), the contributor count, and the latest commit. To&lt;br&gt;
make the list easier to learn, I group the twelve files into six groups, which&lt;br&gt;
is just my reading order, not anything Hugging Face labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs and license:&lt;/strong&gt; &lt;code&gt;README.md&lt;/code&gt; (the model card) and &lt;code&gt;LICENSE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model shape:&lt;/strong&gt; &lt;code&gt;config.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation defaults:&lt;/strong&gt; &lt;code&gt;generation_config.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokenizer:&lt;/strong&gt; &lt;code&gt;tokenizer.json&lt;/code&gt;, &lt;code&gt;tokenizer_config.json&lt;/code&gt;, &lt;code&gt;vocab.json&lt;/code&gt;,
&lt;code&gt;merges.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weights:&lt;/strong&gt; &lt;code&gt;model-00001-of-00002.safetensors&lt;/code&gt;,
&lt;code&gt;model-00002-of-00002.safetensors&lt;/code&gt;, and their index
&lt;code&gt;model.safetensors.index.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git plumbing:&lt;/strong&gt; &lt;code&gt;.gitattributes&lt;/code&gt;, which configures Git LFS and is not model
data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clicking any file opens it in the browser. The rest of this post opens the small&lt;br&gt;
ones and reads them.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;config.json&lt;/code&gt; is the architecture contract
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;config.json&lt;/code&gt; tells a runtime how to build the model before loading any weights.&lt;br&gt;
It is tiny, 661 bytes, so I open it and read the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"architectures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Qwen2ForCausalLM"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attention_dropout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bos_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151643&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eos_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151645&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hidden_act"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"silu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hidden_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"initializer_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intermediate_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11008&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_position_embeddings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_window_layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"num_attention_heads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"num_hidden_layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"num_key_value_heads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rms_norm_eps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1e-06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rope_theta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sliding_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tie_word_embeddings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"torch_dtype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bfloat16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transformers_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.43.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"use_cache"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"use_sliding_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vocab_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151936&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is where the &lt;strong&gt;architecture&lt;/strong&gt; name lives: &lt;code&gt;Qwen2ForCausalLM&lt;/code&gt;. The header did&lt;br&gt;
not print it, but the file does. I do not need to understand every field in&lt;br&gt;
Week 3. My goal today is to find and record them, and their deeper lessons are&lt;br&gt;
already scheduled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Week 4 turns &lt;code&gt;torch_dtype&lt;/code&gt; and the parameter count into weight-memory math.
Week 5 compares FP32, FP16, and BF16 tensors on CPU and GPU.&lt;/li&gt;
&lt;li&gt;Week 9 explains &lt;code&gt;vocab_size&lt;/code&gt;, tokenizer files, and special token IDs.&lt;/li&gt;
&lt;li&gt;Week 10 explains attention heads. Week 13 returns to why this model has 16
query heads (&lt;code&gt;num_attention_heads&lt;/code&gt;) but only 2 key/value heads
(&lt;code&gt;num_key_value_heads&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Week 11 explains &lt;code&gt;num_hidden_layers&lt;/code&gt;, &lt;code&gt;hidden_size&lt;/code&gt;, and &lt;code&gt;intermediate_size&lt;/code&gt;,
and how the architecture class builds transformer blocks from them.&lt;/li&gt;
&lt;li&gt;Week 18 compares &lt;code&gt;max_position_embeddings&lt;/code&gt; with the tokenizer limit, the
runtime setting, and the context length that is practical in memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important distinction is simpler than the individual fields: &lt;code&gt;config.json&lt;/code&gt;&lt;br&gt;
describes the model's shape, while the checkpoint files contain the learned&lt;br&gt;
numbers that fill that shape.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;generation_config.json&lt;/code&gt; supplies defaults, not hard limits
&lt;/h2&gt;

&lt;p&gt;This file holds the default sampling settings for text generation. It is 242&lt;br&gt;
bytes, so again I open the whole thing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bos_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151643&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pad_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151643&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"do_sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eos_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;151645&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;151643&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repetition_penalty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_p"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_k"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transformers_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.37.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are defaults, not training facts, and a runtime or API request can override&lt;br&gt;
them. I changed generation temperature in Week 2 and briefly introduced top-p&lt;br&gt;
there. Week 12 is the full generation-and-sampling lesson, where I change top-k,&lt;br&gt;
top-p, temperature, repetition penalty, and seed one at a time. In Week 3 I only&lt;br&gt;
need to know that &lt;code&gt;generation_config.json&lt;/code&gt; is where this repository stores its&lt;br&gt;
defaults, and that it names IDs &lt;code&gt;151643&lt;/code&gt; and &lt;code&gt;151645&lt;/code&gt; as end-of-generation&lt;br&gt;
markers. The field names spell out the roles: &lt;code&gt;bos_token_id&lt;/code&gt; is the&lt;br&gt;
beginning-of-sequence (BOS) marker, &lt;code&gt;eos_token_id&lt;/code&gt; the end-of-sequence (EOS)&lt;br&gt;
marker, and &lt;code&gt;pad_token_id&lt;/code&gt; the padding marker. One ID can fill several roles:&lt;br&gt;
here &lt;code&gt;151643&lt;/code&gt; is BOS and padding and is also one of the two accepted EOS IDs.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tokenizer is several files working together
&lt;/h2&gt;

&lt;p&gt;The tokenizer turns text into token IDs. This repository ships it as several&lt;br&gt;
files, all visible on the Files tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vocab.json&lt;/code&gt; maps token text to IDs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merges.txt&lt;/code&gt; holds byte-pair encoding merge rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokenizer_config.json&lt;/code&gt; stores the special tokens and the chat template.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokenizer.json&lt;/code&gt; packages the whole tokenizer into one file for fast loading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Byte-pair encoding&lt;/strong&gt; (BPE) builds tokens by repeatedly joining common text&lt;br&gt;
pieces according to the merge rules.&lt;sup id="fnref7"&gt;7&lt;/sup&gt; This is one tokenizer method, not a&lt;br&gt;
universal standard. Other model families can use different algorithms and file&lt;br&gt;
layouts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tokenizer_config.json&lt;/code&gt; is larger than the config files, so instead of reading it&lt;br&gt;
top to bottom I look for two things: the special tokens and the chat template.&lt;br&gt;
Opening it and scrolling to the tokenizer class and the &lt;code&gt;added_tokens_decoder&lt;/code&gt;&lt;br&gt;
map, the fields that matter here are:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokenizer_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Qwen2Tokenizer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_max_length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;131072&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"added_tokens_decoder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"151643"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;|endoftext|&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"special"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"151644"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;|im_start|&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"special"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"151645"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;|im_end|&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"special"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That maps the three special tokens that matter here:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token ID&lt;/th&gt;
&lt;th&gt;Text&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;151643&lt;/td&gt;
&lt;td&gt;`&amp;lt;&lt;/td&gt;
&lt;td&gt;endoftext&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;151644&lt;/td&gt;
&lt;td&gt;{% raw %}`&amp;lt;&lt;/td&gt;
&lt;td&gt;im_start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;151645&lt;/td&gt;
&lt;td&gt;{% raw %}`&amp;lt;&lt;/td&gt;
&lt;td&gt;im_end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This answers a question left over from Week 2. With Phi-4 in Ollama I saw&lt;br&gt;
{% raw %}&lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;|im_sep|&amp;gt;&lt;/code&gt; inside the packaged GGUF model.&lt;br&gt;
Now I can see where the source repository keeps that information:&lt;br&gt;
&lt;code&gt;generation_config.json&lt;/code&gt; names which IDs act as start, end, and padding markers,&lt;br&gt;
and &lt;code&gt;tokenizer_config.json&lt;/code&gt; maps those IDs to text and stores the chat template&lt;br&gt;
that places them around messages. The values belong to each model's tokenizer:&lt;br&gt;
Phi-4 used IDs 100264 to 100266, Qwen uses 151643 to 151645, and Qwen has no&lt;br&gt;
&lt;code&gt;&amp;lt;|im_sep|&amp;gt;&lt;/code&gt;. I should never copy special-token IDs or assume the same chat&lt;br&gt;
format across model families.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;chat template&lt;/strong&gt; is a template written in Jinja, a text templating language,&lt;br&gt;
stored inside &lt;code&gt;tokenizer_config.json&lt;/code&gt;.&lt;sup id="fnref8"&gt;8&lt;/sup&gt; It converts role-based messages&lt;br&gt;
into the special-token sequence the model expects, the same job as the ChatML&lt;br&gt;
markers I inspected in Week 2.&lt;sup id="fnref9"&gt;9&lt;/sup&gt; The full template is worth a&lt;br&gt;
look, so I show it after the file tour in&lt;br&gt;
the chat-template walkthrough.&lt;/p&gt;

&lt;p&gt;One number on the tokenizer looks contradictory at first. &lt;code&gt;tokenizer_config.json&lt;/code&gt;&lt;br&gt;
sets &lt;code&gt;model_max_length&lt;/code&gt; to 131,072, while &lt;code&gt;config.json&lt;/code&gt; says 32,768. The two&lt;br&gt;
fields describe different things: &lt;code&gt;model_max_length&lt;/code&gt; is tokenizer metadata (a&lt;br&gt;
safety cap on input length), while &lt;code&gt;max_position_embeddings&lt;/code&gt; in &lt;code&gt;config.json&lt;/code&gt; is&lt;br&gt;
this checkpoint's native architecture limit. The model card's introduction&lt;br&gt;
mentions the wider Qwen2.5 family supports up to 128K tokens, but this specific&lt;br&gt;
3B checkpoint lists a native 32,768-token context, which matches &lt;code&gt;config.json&lt;/code&gt;.&lt;br&gt;
The 131,072 setting is not proof that this checkpoint can safely use that length,&lt;br&gt;
so I treat 32,768 as the verified native limit.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Safetensors files are the checkpoint
&lt;/h2&gt;

&lt;p&gt;The two &lt;code&gt;.safetensors&lt;/code&gt; files hold the learned &lt;strong&gt;tensors&lt;/strong&gt;, the multidimensional&lt;br&gt;
arrays of numbers that are the model's parameters. Safetensors is a weight-file&lt;br&gt;
format designed to load those arrays without the code-execution risk of Python's&lt;br&gt;
Pickle format.&lt;sup id="fnref10"&gt;10&lt;/sup&gt; It is one option, not the only one. Week 2 used&lt;br&gt;
GGUF, a different format that packages weights and metadata together.&lt;/p&gt;

&lt;p&gt;The checkpoint is split into two &lt;strong&gt;shards&lt;/strong&gt;, &lt;code&gt;model-00001-of-00002.safetensors&lt;/code&gt;&lt;br&gt;
(3.97 GB) and &lt;code&gt;model-00002-of-00002.safetensors&lt;/code&gt; (2.2 GB). The third weight file,&lt;br&gt;
&lt;code&gt;model.safetensors.index.json&lt;/code&gt;, is not weights at all: it is a map from each&lt;br&gt;
tensor name to the shard that holds it. Opening it, the top is a &lt;code&gt;metadata&lt;/code&gt; block&lt;br&gt;
followed by a &lt;code&gt;weight_map&lt;/code&gt; with one entry per tensor:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6171877376&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weight_map"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model.embed_tokens.weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"model-00001-of-00002.safetensors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model.layers.0.input_layernorm.weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"model-00001-of-00002.safetensors"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The real &lt;code&gt;weight_map&lt;/code&gt; has 434 entries (I show two); that count is how many&lt;br&gt;
tensors a loader must place. Splitting one checkpoint into shards just makes it&lt;br&gt;
easier to upload, cache, and download; both shards are required, and two files do&lt;br&gt;
not mean two models.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;metadata.total_size&lt;/code&gt; field records the total tensor size: 6,171,877,376&lt;br&gt;
bytes. That number is a good check on the "3B params" and "BF16" labels from the&lt;br&gt;
sidebar. BF16 means each parameter uses 16 bits, or 2 bytes,&lt;sup id="fnref11"&gt;11&lt;/sup&gt; so:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;171&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;877&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;376&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;&amp;nbsp;bytes&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;÷&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;085&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;938&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;688&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;&amp;nbsp;parameters&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;That is 3.09 billion parameters, exactly what the model card rounds to. Next week&lt;br&gt;
I turn this calculation into a reusable memory tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  How do I know it is really an instruct model?
&lt;/h2&gt;

&lt;p&gt;There is no universal &lt;code&gt;is_instruct: true&lt;/code&gt; field. Here is the evidence the page&lt;br&gt;
gives, strongest first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Publisher statement:&lt;/strong&gt; the model card says "instruction-tuned" and
"Pretraining &amp;amp; Post-training". That is why I call it an instruct model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model relationship:&lt;/strong&gt; the Model tree names the base model as
&lt;code&gt;Qwen/Qwen2.5-3B&lt;/code&gt;, and the repository carries the &lt;code&gt;chat&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different learned checkpoint:&lt;/strong&gt; the companion lab reads only the file
fingerprints and shows the base and instruct first shards have different
SHA-256 hashes, which proves the weight bytes differ.&lt;sup id="fnref12"&gt;12&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat-oriented defaults:&lt;/strong&gt; the instruct &lt;code&gt;generation_config.json&lt;/code&gt; enables
sampling and lists &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; as an end token, which the base model does
not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most architecture values are identical between the two, because post-training&lt;br&gt;
changes the learned weights, not the model's shape. Both still have 36 layers&lt;br&gt;
and the same parameter count. Confirming that with real file hashes is the lab's&lt;br&gt;
job; the model card and the Model tree already answer it for day-to-day use.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Qwen chat template, connected to Week 2
&lt;/h2&gt;

&lt;p&gt;This section is optional depth. The file tour already gave me what I need for&lt;br&gt;
Week 3, but the full template makes the Week 2 roles and markers concrete.&lt;/p&gt;

&lt;p&gt;The template is stored as one long string inside &lt;code&gt;tokenizer_config.json&lt;/code&gt;. Opening&lt;br&gt;
that file in the browser shows it escaped on a single line; the lab prints it&lt;br&gt;
cleanly with one &lt;code&gt;jq&lt;/code&gt; command. Formatted, it reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{%- if tools %}
    {{- '&amp;lt;|im_start|&amp;gt;system\n' }}
    {%- if messages[0]['role'] == 'system' %}
        {{- messages[0]['content'] }}
    {%- else %}
        {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
    {%- endif %}
    {{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within &amp;lt;tools&amp;gt;&amp;lt;/tools&amp;gt; XML tags:\n&amp;lt;tools&amp;gt;" }}
    {%- for tool in tools %}
        {{- "\n" }}
        {{- tool | tojson }}
    {%- endfor %}
    {{- "\n&amp;lt;/tools&amp;gt;\n\nFor each function call, return a json object with function name and arguments within &amp;lt;tool_call&amp;gt;&amp;lt;/tool_call&amp;gt; XML tags:\n&amp;lt;tool_call&amp;gt;\n{\"name\": &amp;lt;function-name&amp;gt;, \"arguments\": &amp;lt;args-json-object&amp;gt;}\n&amp;lt;/tool_call&amp;gt;&amp;lt;|im_end|&amp;gt;\n" }}
{%- else %}
    {%- if messages[0]['role'] == 'system' %}
        {{- '&amp;lt;|im_start|&amp;gt;system\n' + messages[0]['content'] + '&amp;lt;|im_end|&amp;gt;\n' }}
    {%- else %}
        {{- '&amp;lt;|im_start|&amp;gt;system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.&amp;lt;|im_end|&amp;gt;\n' }}
    {%- endif %}
{%- endif %}
{%- for message in messages %}
    {%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
        {{- '&amp;lt;|im_start|&amp;gt;' + message.role + '\n' + message.content + '&amp;lt;|im_end|&amp;gt;' + '\n' }}
    {%- elif message.role == "assistant" %}
        {{- '&amp;lt;|im_start|&amp;gt;' + message.role }}
        {%- if message.content %}
            {{- '\n' + message.content }}
        {%- endif %}
        {%- for tool_call in message.tool_calls %}
            {%- if tool_call.function is defined %}
                {%- set tool_call = tool_call.function %}
            {%- endif %}
            {{- '\n&amp;lt;tool_call&amp;gt;\n{"name": "' }}
            {{- tool_call.name }}
            {{- '", "arguments": ' }}
            {{- tool_call.arguments | tojson }}
            {{- '}\n&amp;lt;/tool_call&amp;gt;' }}
        {%- endfor %}
        {{- '&amp;lt;|im_end|&amp;gt;\n' }}
    {%- elif message.role == "tool" %}
        {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
            {{- '&amp;lt;|im_start|&amp;gt;user' }}
        {%- endif %}
        {{- '\n&amp;lt;tool_response&amp;gt;\n' }}
        {{- message.content }}
        {{- '\n&amp;lt;/tool_response&amp;gt;' }}
        {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
            {{- '&amp;lt;|im_end|&amp;gt;\n' }}
        {%- endif %}
    {%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
    {{- '&amp;lt;|im_start|&amp;gt;assistant\n' }}
{%- endif %}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not need to understand every branch yet. The four parts that connect to&lt;br&gt;
Week 2 are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A system message becomes &lt;code&gt;&amp;lt;|im_start|&amp;gt;system&lt;/code&gt;, its content, then
&lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The loop does the same for &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;assistant&lt;/code&gt; messages.&lt;/li&gt;
&lt;li&gt;If no system message is supplied, the template inserts Qwen's default system
message.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_generation_prompt&lt;/code&gt; ends with &lt;code&gt;&amp;lt;|im_start|&amp;gt;assistant&lt;/code&gt;, telling the model
which role should speak next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first branch also formats tool calls. I leave that branch for Week 27. The&lt;br&gt;
important point here is that the API's role-based messages do not go straight to&lt;br&gt;
the model. The tokenizer's chat template turns them into the model-specific text&lt;br&gt;
and special-token sequence first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Browsing the page answered every item from the Week 3 plan:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Verified answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-3B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latest commit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aa8e725&lt;/code&gt; (shown on the Files tab)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Qwen2ForCausalLM&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameters&lt;/td&gt;
&lt;td&gt;3,085,938,688 BF16 values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layers&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden size&lt;/td&gt;
&lt;td&gt;2,048&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attention configuration&lt;/td&gt;
&lt;td&gt;16 query heads, 2 shared key/value heads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vocabulary&lt;/td&gt;
&lt;td&gt;151,936 token IDs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native configured context&lt;/td&gt;
&lt;td&gt;32,768 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BOS ID&lt;/td&gt;
&lt;td&gt;151643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EOS IDs&lt;/td&gt;
&lt;td&gt;151645 and 151643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weight shards&lt;/td&gt;
&lt;td&gt;2 files, 6,171,877,376 tensor bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Qwen Research License, non-commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The machine-readable copy is saved as &lt;code&gt;results.json&lt;/code&gt; in the public lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;The biggest surprise was the license. A page that looks production-ready can&lt;br&gt;
still be non-commercial by default, and only the &lt;code&gt;LICENSE&lt;/code&gt; file says so. Reading&lt;br&gt;
it first is now a habit, not an afterthought.&lt;/p&gt;

&lt;p&gt;The second surprise was how much the web page alone answered. Without a GPU, an&lt;br&gt;
API key, or a line of code, I read the architecture, the size, the base-model&lt;br&gt;
lineage, the tokenizer's special tokens, and a 6.18 GB download estimate straight&lt;br&gt;
off the page.&lt;/p&gt;

&lt;p&gt;The third surprise was that the round "3B" label and the exact file size agree.&lt;br&gt;
The index reports 6,171,877,376 bytes; divided by 2 bytes per BF16 value, that is&lt;br&gt;
exactly 3,085,938,688 parameters, which is what the card rounds to 3.09B.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;My first instinct was to trust the license badge. The header says &lt;code&gt;qwen-research&lt;/code&gt;,&lt;br&gt;
which sounds like a name I could look up and move on. Opening the actual &lt;code&gt;LICENSE&lt;/code&gt;&lt;br&gt;
file is what surfaced the non-commercial restriction. The badge is a label; the&lt;br&gt;
file is the contract.&lt;/p&gt;

&lt;p&gt;I also assumed the sidebar's "3B params" and the card's "3.09B" were rounded&lt;br&gt;
marketing numbers. The index's exact byte count showed otherwise: the round label&lt;br&gt;
and the precise file size describe the same 3,085,938,688 parameters.&lt;/p&gt;

&lt;p&gt;The lab's &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; version hits a few API-specific snags (the Hub reports&lt;br&gt;
the license as &lt;code&gt;other&lt;/code&gt;, the card object needs &lt;code&gt;to_dict()&lt;/code&gt;, and unauthenticated&lt;br&gt;
requests print a rate-limit warning). Those are written up in the lab's&lt;br&gt;
troubleshooting notes, since they only matter for the scripted path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;Model selection is dependency management with much larger artifacts. I would&lt;br&gt;
apply the same controls used for containers and packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pin the full repository revision, not mutable &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Record every weight hash and verify it after download.&lt;/li&gt;
&lt;li&gt;Review the model card, then read the actual license.&lt;/li&gt;
&lt;li&gt;Estimate download and memory size before scheduling hardware.&lt;/li&gt;
&lt;li&gt;Review repository code before enabling &lt;code&gt;trust_remote_code&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep tokenizer and generation files with the exact checkpoint they came from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This repository contains no Python model files, so the metadata inspection did&lt;br&gt;
not execute repository code. That is a useful security property, but I would&lt;br&gt;
check it again for every new model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 4 turns the parameter count and BF16 data type into memory math. I will&lt;br&gt;
separate raw weight storage from runtime memory, activations, the KV cache, and&lt;br&gt;
framework overhead, then build a calculator for different precisions.&lt;sup id="fnref13"&gt;13&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself: the API way in the lab
&lt;/h2&gt;

&lt;p&gt;Everything above was done by browsing the web page. The companion Week 3 lab does&lt;br&gt;
the same inspection the "API way", so you can automate or script it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; one-liners that read each field from the Hub API and the
raw files, pinned to an exact repository revision.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;model-inspector.py&lt;/code&gt;, which pulls the file list, config, tokenizer, license,
and shard index for any model in one command and can write &lt;code&gt;results.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compare-models.py&lt;/code&gt;, which compares the base and instruct repositories field by
field and checks the Hub-reported SHA-256 (Git LFS) fingerprint of their first
weight shard, proving the checkpoints differ.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its default and verified target is &lt;code&gt;Qwen/Qwen2.5-3B-Instruct&lt;/code&gt;. The &lt;code&gt;--model&lt;/code&gt;&lt;br&gt;
option works for compatible repositories, but different model families can use&lt;br&gt;
different files and config keys.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Week 3 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-03-model-files" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-03-model-files&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Qwen2.5-3B-Instruct model card:&lt;br&gt;
&lt;a href="https://huggingface.co/Qwen/Qwen2.5-3B-Instruct" rel="noopener noreferrer"&gt;https://huggingface.co/Qwen/Qwen2.5-3B-Instruct&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Hugging Face repository documentation:&lt;br&gt;
&lt;a href="https://huggingface.co/docs/hub/repositories" rel="noopener noreferrer"&gt;https://huggingface.co/docs/hub/repositories&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Qwen2.5 model collection:&lt;br&gt;
&lt;a href="https://huggingface.co/collections/Qwen/qwen25" rel="noopener noreferrer"&gt;https://huggingface.co/collections/Qwen/qwen25&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Qwen2.5-3B base model card:&lt;br&gt;
&lt;a href="https://huggingface.co/Qwen/Qwen2.5-3B" rel="noopener noreferrer"&gt;https://huggingface.co/Qwen/Qwen2.5-3B&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Qwen2.5-3B-Instruct license:&lt;br&gt;
&lt;a href="https://huggingface.co/Qwen/Qwen2.5-3B-Instruct/blob/main/LICENSE" rel="noopener noreferrer"&gt;https://huggingface.co/Qwen/Qwen2.5-3B-Instruct/blob/main/LICENSE&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Hugging Face Tokenizers model documentation:&lt;br&gt;
&lt;a href="https://huggingface.co/docs/tokenizers/main/en/components#models" rel="noopener noreferrer"&gt;https://huggingface.co/docs/tokenizers/main/en/components#models&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;Jinja template-language documentation:&lt;br&gt;
&lt;a href="https://jinja.palletsprojects.com/" rel="noopener noreferrer"&gt;https://jinja.palletsprojects.com/&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;Hugging Face chat-template documentation:&lt;br&gt;
&lt;a href="https://huggingface.co/docs/transformers/chat_templating" rel="noopener noreferrer"&gt;https://huggingface.co/docs/transformers/chat_templating&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn10"&gt;
&lt;p&gt;Safetensors documentation:&lt;br&gt;
&lt;a href="https://huggingface.co/docs/safetensors/index" rel="noopener noreferrer"&gt;https://huggingface.co/docs/safetensors/index&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn11"&gt;
&lt;p&gt;PyTorch data-type documentation:&lt;br&gt;
&lt;a href="https://pytorch.org/docs/stable/tensor_attributes.html#torch.dtype" rel="noopener noreferrer"&gt;https://pytorch.org/docs/stable/tensor_attributes.html#torch.dtype&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn12"&gt;
&lt;p&gt;NIST Secure Hash Standard:&lt;br&gt;
&lt;a href="https://csrc.nist.gov/pubs/fips/180-4/upd1/final" rel="noopener noreferrer"&gt;https://csrc.nist.gov/pubs/fips/180-4/upd1/final&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn13"&gt;
&lt;p&gt;Week 4 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-04.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-04.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 2: What Actually Happens Behind the API</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Sun, 19 Jul 2026 20:19:40 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-2-what-actually-happens-behind-the-api-3d0h</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-2-what-actually-happens-behind-the-api-3d0h</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 1 of 8: Comfortable running local models. Week 2 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Week 1 I went through the machine and proved the GPU works. This week I run an&lt;br&gt;
actual language model on it and, for the first time, look at what sits behind the&lt;br&gt;
"AI API" I have called for years. The short version: the API is not magic. It is&lt;br&gt;
an HTTP layer over a local process that loads a file of numbers into memory and&lt;br&gt;
does the matrix math from Week 1. By the end of this post that sentence will be&lt;br&gt;
clear, backed by real commands and real output you can reproduce.&lt;/p&gt;

&lt;p&gt;I am using Ollama, a runtime that makes running a local model about as easy as&lt;br&gt;
running a container. Other names you will see in this space are &lt;code&gt;llama.cpp&lt;/code&gt; for&lt;br&gt;
lightweight local inference, vLLM for high-throughput serving, and NVIDIA&lt;br&gt;
TensorRT-LLM for optimized NVIDIA inference. Hugging Face Transformers is also&lt;br&gt;
common, but it is a broader Python framework for running and training models,&lt;br&gt;
not a ready-made local model service. These tools overlap, but they are not exact&lt;br&gt;
replacements. I chose Ollama because it gives me a CLI and local HTTP API with&lt;br&gt;
very little setup.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Everything below runs on the DGX Spark and is reached over SSH as &lt;code&gt;spark&lt;/code&gt;, the&lt;br&gt;
same setup as Week 1.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 1: a model is not a runtime
&lt;/h2&gt;

&lt;p&gt;The first idea to keep straight, because it holds for the rest of the series, is&lt;br&gt;
the split between the model and the runtime.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;model&lt;/strong&gt; is trained data: weights plus the metadata needed to use them.
On its own it does nothing. It is data on disk.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;runtime&lt;/strong&gt; is the program that loads those weights into memory and runs
the math to turn your prompt into text. Ollama is the runtime here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you know Docker, there is a useful analogy, but it is not exact. A model&lt;br&gt;
package is like an image made of versioned, content-addressed layers. Ollama is&lt;br&gt;
like the engine that pulls those layers and starts the workload. Unlike a&lt;br&gt;
container image, the main model layer is trained numeric data, not an app and&lt;br&gt;
its operating-system files.&lt;/p&gt;

&lt;p&gt;Ollama calls each stored package file a &lt;strong&gt;blob&lt;/strong&gt;. Here, a blob is just a file&lt;br&gt;
kept under a name derived from its content digest. The inspection below follows&lt;br&gt;
the package index to the model blob and checks that it is the expected file.&lt;/p&gt;

&lt;p&gt;Rather than run a wrapper script, I inspected the model with a few direct&lt;br&gt;
commands, one at a time. Each command answers a single question, so you can&lt;br&gt;
paste it, read the output, then move to the next.&lt;/p&gt;

&lt;p&gt;First, list what Ollama has downloaded locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'ollama list'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                       ID              SIZE      MODIFIED
nomic-embed-text:latest    0a109f422b47    274 MB    2 days ago
llama3.2:3b                a80c4f17acd5    2.0 GB    3 days ago
phi4:latest                ac896e5b8b34    9.1 GB    7 days ago
qwen3.6:35b-a3b-bf16       94061ddd23a7    71 GB     8 days ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, ask Ollama for the focused facts about Phi-4. The Ollama server listens on&lt;br&gt;
&lt;code&gt;localhost:11434&lt;/code&gt; on the Spark, so run this in a shell on the Spark&lt;br&gt;
(&lt;code&gt;ssh spark&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/show &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "phi4"}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'{
  format: .details.format,
    architecture: .details.family,
    parameters: .details.parameter_size,
    context_length: (.model_info | to_entries
      | map(select(.key | endswith(".context_length"))) | first.value),
    embedding_length: (.model_info | to_entries
      | map(select(.key | endswith(".embedding_length"))) | first.value),
    quantization: .details.quantization_level,
    capabilities,
    runtime_parameters: (.parameters | split("\n")
      | map(select(length &amp;gt; 0) | gsub(" +"; " ")))
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gguf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phi3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14.7B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context_length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"embedding_length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Q4_K_M"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"completion"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtime_parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"stop &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;|im_start|&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"stop &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;|im_end|&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"stop &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;|im_sep|&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That call gives readable model facts, including &lt;code&gt;"format": "gguf"&lt;/code&gt;. For a&lt;br&gt;
normal check, this is how I know Ollama identifies the model as GGUF. I do not&lt;br&gt;
need to locate the raw file and inspect its bytes just to answer that question.&lt;/p&gt;

&lt;p&gt;To see the exact files that make up the package, read Ollama's manifest. It is a&lt;br&gt;
small JSON file on the Spark, so &lt;code&gt;jq&lt;/code&gt; can read it directly. I select just the&lt;br&gt;
package version and the file list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="s1"&gt;'{schemaVersion, layers: [.layers[] | {mediaType, digest, size}]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /usr/share/ollama/.ollama/models/manifests/registry.ollama.ai/library/phi4/latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.ollama.image.model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:fd7b6731c33c57f61767612f56517460ec2d1e2e5a3f0163e0eb3d8d8cb5df20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9053114464&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.ollama.image.template"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:32695b892af87ef8fca6e13a1a31c67c1441d7398be037e366e2fc763857c06a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;275&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.ollama.image.license"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:fa8235e5b48faca34e3ca98cf4f694ef08bd216d28b58071a1f85b1d50cb814d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1084&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.ollama.image.params"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:45a1c652dddc9efdcefa977ab81cfbe26b6e52bc8e78f2f4c698538783e0ac80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry path ends in &lt;code&gt;library/phi4/latest&lt;/code&gt;, so this manifest is the index&lt;br&gt;
for the model name &lt;code&gt;phi4&lt;/code&gt; and tag &lt;code&gt;latest&lt;/code&gt;. It does not hold the 9.1 GB of&lt;br&gt;
weights. It lists the files that make up the Ollama package, much like a lock&lt;br&gt;
file maps package names to exact artifacts.&lt;/p&gt;

&lt;p&gt;Each entry under &lt;code&gt;layers&lt;/code&gt; is one file in the package (one blob). It has three&lt;br&gt;
useful fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mediaType&lt;/code&gt; says what the file's role is. This package has a model, a prompt
template, a license, and default parameters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;digest&lt;/code&gt; is the file's content identity. The &lt;code&gt;sha256&lt;/code&gt; prefix names the hash
algorithm, and the characters after the colon are the hash of the file's
bytes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size&lt;/code&gt; says exactly how many bytes that file should contain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model layer's &lt;code&gt;digest&lt;/code&gt; is also how Ollama names the file on disk. It stores&lt;br&gt;
each blob under a &lt;code&gt;blobs&lt;/code&gt; directory using the digest as the filename, with the&lt;br&gt;
colon changed to a hyphen, so &lt;code&gt;sha256:fd7b...df20&lt;/code&gt; becomes &lt;code&gt;sha256-fd7b...df20&lt;/code&gt;.&lt;br&gt;
That file is &lt;code&gt;9,053,114,464&lt;/code&gt; bytes, the &lt;code&gt;size&lt;/code&gt; shown above, and its SHA-256&lt;br&gt;
matches the digest. The linked investigation independently checks the file&lt;br&gt;
header with &lt;code&gt;xxd&lt;/code&gt;: bytes &lt;code&gt;47 47 55 46&lt;/code&gt; spell &lt;code&gt;GGUF&lt;/code&gt; in ASCII. That deeper check&lt;br&gt;
is useful when reusing the raw file in another runtime, but &lt;code&gt;.details.format&lt;/code&gt;&lt;br&gt;
is the simple Ollama API answer.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;ollama show&lt;/code&gt; and the manifest answer different questions. The metadata call&lt;br&gt;
gives readable model facts such as architecture and context length. The&lt;br&gt;
manifest tells Ollama which exact files form the runnable package and where to&lt;br&gt;
find them by content ID.&lt;/p&gt;

&lt;p&gt;You might now wonder whether another runtime can use the same GGUF file. That is&lt;br&gt;
useful, but it is not part of the main Ollama lesson. I answer it near the end in&lt;br&gt;
Can llama.cpp reuse this Ollama model?.&lt;/p&gt;

&lt;p&gt;I am using &lt;code&gt;phi4&lt;/code&gt;, Microsoft's 14.7B-parameter model. At 16 bits per weight,&lt;br&gt;
14.7 billion weights alone would need about 29.4 GB. The local package is only&lt;br&gt;
9.1 GB, which leads to the first surprise.&lt;/p&gt;

&lt;p&gt;A quick tour of what each line means, since these terms come up constantly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Plain meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;format&lt;/td&gt;
&lt;td&gt;gguf&lt;/td&gt;
&lt;td&gt;model file format reported by Ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;architecture&lt;/td&gt;
&lt;td&gt;phi3&lt;/td&gt;
&lt;td&gt;the neural-network design (phi4 reuses the phi3 family)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;parameters&lt;/td&gt;
&lt;td&gt;14.7B&lt;/td&gt;
&lt;td&gt;how many trained weights the model has&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;context length&lt;/td&gt;
&lt;td&gt;16384&lt;/td&gt;
&lt;td&gt;the most tokens (prompt + reply) it can consider at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;embedding length&lt;/td&gt;
&lt;td&gt;5120&lt;/td&gt;
&lt;td&gt;width of each token vector; covered later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;quantization&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;the weights are stored at about 4 bits each, not 16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row answers the 9.1 GB surprise. This build is &lt;strong&gt;quantized&lt;/strong&gt;: its&lt;br&gt;
weights use a mixed low-bit &lt;code&gt;Q4_K_M&lt;/code&gt; representation instead of 16-bit values.&lt;br&gt;
Four bits per weight would be about 7.35 GB before metadata and quantization&lt;br&gt;
overhead, so a 9.1 GB model layer is reasonable.&lt;br&gt;
Quantization is a whole phase later in this series (weeks 14 to 16). For now the&lt;br&gt;
only thing to take away is that Ollama runs lower-precision weights, and that is&lt;br&gt;
why this 14.7B model takes much less space than its 16-bit source model.&lt;/p&gt;

&lt;p&gt;The three &lt;code&gt;stop&lt;/code&gt; entries (&lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|im_sep|&amp;gt;&lt;/code&gt;) are&lt;br&gt;
&lt;strong&gt;special tokens&lt;/strong&gt;: markers the model was trained to use as turn boundaries in a&lt;br&gt;
chat. The runtime watches for them to know when the model has finished its reply.&lt;br&gt;
They show up again in the next section, so keep them in mind.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 2: the API is just HTTP over a local process
&lt;/h2&gt;

&lt;p&gt;Ollama exposes a local HTTP API on port 11434,&lt;sup id="fnref3"&gt;3&lt;/sup&gt; the same shape of&lt;br&gt;
thing as any cloud AI API. The difference is that the "server" is a process on&lt;br&gt;
my own machine.&lt;br&gt;
I called the generate endpoint with &lt;code&gt;curl&lt;/code&gt; and formatted the reply with &lt;code&gt;jq&lt;/code&gt;.&lt;br&gt;
Run it directly on the Spark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi4",
  "prompt": "Explain Kubernetes scheduling in three sentences.",
  "stream": false
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the complete response, nothing removed. It is long, but seeing all of it&lt;br&gt;
is the point, because two parts of it explain how the model actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phi4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-14T03:11:50.862545331Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kubernetes scheduling is the process of assigning workloads, such as pods, to appropriate nodes within a cluster based on resource availability and constraints. The scheduler evaluates factors like CPU, memory requirements, affinity/anti-affinity rules, taints/tolerations, and node selectors to determine the best fit for each pod. This ensures optimal utilization of resources, workload distribution, and adherence to specified policies, thereby maintaining efficient cluster operation and performance."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"done_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;100264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;882&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100266&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;198&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;849&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21435&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;67474&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38952&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;304&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;23719&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100265&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;198&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;78191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100266&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;198&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30927&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;38952&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;374&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;279&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;61853&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;990&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33785&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1778&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;439&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55687&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8475&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7954&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2949&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10879&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3196&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;389&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5211&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18539&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17413&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;578&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;29909&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;67349&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9547&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;1093&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14266&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5044&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8670&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;51552&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15719&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71260&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;13797&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5718&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;259&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1673&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22847&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;811&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2494&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;57137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8417&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;279&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5052&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;369&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1855&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;7661&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23669&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50549&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5070&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;54696&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;8141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;71628&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10396&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28592&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20958&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="mi"&gt;11297&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10879&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5784&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5178&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3974942694&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"load_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100037676&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46132000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3817895000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading it top to bottom:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;response&lt;/code&gt; field is the answer, a real reply from a model running on my&lt;br&gt;
hardware with no network call leaving the box.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;context&lt;/code&gt; field is the part worth staring at. You might be wondering what&lt;br&gt;
those numbers are. They are &lt;strong&gt;tokens&lt;/strong&gt;. A model does not read text; it reads&lt;br&gt;
token IDs, which are integers. Before anything runs, a &lt;strong&gt;tokenizer&lt;/strong&gt; splits the&lt;br&gt;
text into tokens and maps each one to an integer. And this array is not just the&lt;br&gt;
prompt, it is the whole conversation as tokens: the chat template, my question,&lt;br&gt;
and the model's full answer. Decoding the first several so it is not a mystery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;100264&lt;/code&gt; is the special marker &lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt; (start of a turn), and &lt;code&gt;882&lt;/code&gt; is
the word "user". So the conversation begins "start of turn, user".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;849, 21435, 67474, 38952&lt;/code&gt; are "Ex", "plain", " Kubernetes", " scheduling".
Notice "Explain" is split into two tokens. Tokens are often sub-word pieces,
not whole words.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;304, 2380, 23719, 13&lt;/code&gt; are " in", " three", " sentences", ".", finishing my
prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;100265&lt;/code&gt; is &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; (end of turn), then &lt;code&gt;100264 78191 100266&lt;/code&gt; is the start
of the assistant's turn ("start, assistant, separator").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;42, 30927&lt;/code&gt; onward ("K", "ubernetes", ...) is the model's answer, token by
token, which is exactly the text in the &lt;code&gt;response&lt;/code&gt; field above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The special IDs &lt;code&gt;100264&lt;/code&gt;, &lt;code&gt;100265&lt;/code&gt;, &lt;code&gt;100266&lt;/code&gt; are the chat markers&lt;br&gt;
(&lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|im_sep|&amp;gt;&lt;/code&gt;), the turn boundaries from the&lt;br&gt;
model's embedded tokenizer and chat template. Two questions probably popped into&lt;br&gt;
your head reading that, the same ones that popped into mine: wait, how do I even&lt;br&gt;
know &lt;code&gt;849&lt;/code&gt; is "Ex" or that &lt;code&gt;100264&lt;/code&gt; is &lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt;? And how does Ollama know&lt;br&gt;
which tokenizer to use? Both have clear answers, but they would break the flow&lt;br&gt;
here, so I answer them at the end. If you want them now, jump to&lt;br&gt;
the token questions, answered.&lt;/p&gt;

&lt;p&gt;This is why token counts matter everywhere. On a cloud API you pay per token. In&lt;br&gt;
a model, tokens are what fill the context window, and the whole conversation is&lt;br&gt;
carried forward as this growing list of integers. When people say "tokens per&lt;br&gt;
second," this array is the unit being counted.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 3: inference has two measured phases
&lt;/h2&gt;

&lt;p&gt;The bottom of that same output has the timing, and it turns vague words like&lt;br&gt;
"latency" into measured numbers. All the durations are in nanoseconds, so here&lt;br&gt;
they are converted:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Raw value&lt;/th&gt;
&lt;th&gt;Converted value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;load_duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100,037,676 ns&lt;/td&gt;
&lt;td&gt;0.10 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_eval_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;18 input tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt_eval_duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;46,132,000 ns&lt;/td&gt;
&lt;td&gt;46 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;89 output tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,817,895,000 ns&lt;/td&gt;
&lt;td&gt;3.82 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,974,942,694 ns&lt;/td&gt;
&lt;td&gt;3.97 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;prompt_eval_duration&lt;/code&gt; is the time spent reading the tokenized input. That is&lt;br&gt;
the &lt;strong&gt;prefill&lt;/strong&gt; phase. &lt;code&gt;eval_duration&lt;/code&gt; is the time spent generating output&lt;br&gt;
tokens. That is the &lt;strong&gt;decode&lt;/strong&gt; phase. &lt;code&gt;load_duration&lt;/code&gt; is time Ollama spent&lt;br&gt;
loading or preparing the model for this request.&lt;/p&gt;

&lt;p&gt;One headline number can be calculated directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokens per second&lt;/strong&gt; (generation speed): 89 tokens divided by 3.82 s is about
&lt;strong&gt;23.3 tokens/sec&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I cannot honestly calculate &lt;strong&gt;time to first token&lt;/strong&gt; (TTFT) from this&lt;br&gt;
non-streaming response. Adding load and prefill gives 146 ms of server work&lt;br&gt;
before decode, but that is not the same as observing when the first token reaches&lt;br&gt;
the client. To measure TTFT, the client must request a streamed response and&lt;br&gt;
timestamp the first non-empty token. I do that in the benchmark later in this&lt;br&gt;
post.&lt;/p&gt;

&lt;p&gt;The timing split also shows that generating text has two phases that behave&lt;br&gt;
differently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prefill&lt;/strong&gt; (&lt;code&gt;prompt_eval&lt;/code&gt;): the model reads all 18 prompt tokens at once.
This is fast (46 ms) because the tokens are processed together, which is the
parallel, compute-heavy work the GPU from Week 1 is good at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode&lt;/strong&gt; (&lt;code&gt;eval&lt;/code&gt;): the model generates output tokens one at a time, each one
depending on the previous. This is the slow, sequential phase, and it is where
memory bandwidth matters. Each token passes through the model layers and uses
their weights again, while the key-value cache avoids recalculating all prior
tokens from scratch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the core of Week 2. The API is HTTP over a local process, the "text" is&lt;br&gt;
really a stream of integer tokens, and a request is load, then prefill, then&lt;br&gt;
decode, each one measurable. Now I can start changing the settings that shape the&lt;br&gt;
output.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 4: temperature, or how random the model is allowed to be
&lt;/h2&gt;

&lt;p&gt;Back in Concept 2 I ran the same prompt twice and got slightly different wording&lt;br&gt;
each time. That is not a bug. It is a setting called &lt;strong&gt;temperature&lt;/strong&gt;, and it&lt;br&gt;
controls how random the model is allowed to be when it picks each next token.&lt;/p&gt;

&lt;p&gt;The setting is &lt;code&gt;options.temperature&lt;/code&gt;. I sent the same coffee-shop prompt twice&lt;br&gt;
at each temperature to watch it change. I changed the value in this direct call&lt;br&gt;
and ran it twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi4",
  "prompt": "Give me a one-sentence tagline for a coffee shop.",
  "stream": false,
  "options": { "temperature": 0 }
}'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twice at temperature 0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temperature=0
run 1: "Awaken Your Senses, One Cup at a Time."
run 2: "Awaken Your Senses, One Cup at a Time."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two runs matched. The same call with &lt;code&gt;"temperature": 1.2&lt;/code&gt; gives variety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temperature=1.2
run 1: "Where every sip is a moment of delight."
run 2: "Where Every Cup is a Perfect Brew—Awaken Your Senses."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same input, two different answers.&lt;/p&gt;

&lt;p&gt;So what is temperature actually doing? At each step, the model does not just pick&lt;br&gt;
one next token. It produces a probability for every token in its vocabulary, like&lt;br&gt;
"there is a 40% chance the next token is &lt;code&gt;Awaken&lt;/code&gt;, 8% chance it is &lt;code&gt;Where&lt;/code&gt;, and so&lt;br&gt;
on". Temperature reshapes that list before one token is picked. Top-p and seed&lt;br&gt;
also affect the choice, but each control has a different job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Changes&lt;/th&gt;
&lt;th&gt;Lower/same&lt;/th&gt;
&lt;th&gt;Higher/different&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Temperature&lt;/td&gt;
&lt;td&gt;Probability shape&lt;/td&gt;
&lt;td&gt;Favors likely tokens&lt;/td&gt;
&lt;td&gt;Allows more variety&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-p&lt;/td&gt;
&lt;td&gt;Candidate set&lt;/td&gt;
&lt;td&gt;Fewer likely tokens&lt;/td&gt;
&lt;td&gt;More possible tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seed&lt;/td&gt;
&lt;td&gt;Random sequence&lt;/td&gt;
&lt;td&gt;Same starting sequence&lt;/td&gt;
&lt;td&gt;Another sequence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Top-p is also called &lt;strong&gt;nucleus sampling&lt;/strong&gt;. At &lt;code&gt;0.9&lt;/code&gt;, it keeps the most likely&lt;br&gt;
tokens whose probabilities add up to 90%, then samples only from that set.&lt;br&gt;
Temperature and top-p can be used together, but while learning I would change&lt;br&gt;
one at a time so I know which setting changed the output.&lt;/p&gt;

&lt;p&gt;You might also see a &lt;strong&gt;seed&lt;/strong&gt; beside temperature. A model runtime uses a&lt;br&gt;
&lt;strong&gt;pseudorandom number generator&lt;/strong&gt; when it samples from the possible next tokens.&lt;br&gt;
Pseudorandom means the values look random, but they are generated by a formula.&lt;br&gt;
The seed is the starting number for that formula, like starting with the same&lt;br&gt;
shuffle of a deck. The same seed gives the sampler the same sequence of random&lt;br&gt;
choices when the model, prompt, options, runtime, and execution conditions stay&lt;br&gt;
the same.&lt;/p&gt;

&lt;p&gt;I tested that with temperature &lt;code&gt;1.2&lt;/code&gt;, where sampling has room to vary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;RUN &lt;span class="k"&gt;in &lt;/span&gt;1 2 3 4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"run=%s seed=42: "&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "phi4",
    "prompt": "Reply with one invented coffee shop name and nothing else.",
    "stream": false,
    "options": {"temperature": 1.2, "seed": 42, "num_predict": 16}
  }'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .response | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run=1 seed=42: Espresso Enchantments
run=2 seed=42: Espresso Enchantments
run=3 seed=42: Espresso Enchantments
run=4 seed=42: Espresso Enchantments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These four warm-model runs matched. A fixed seed improves repeatability, but it&lt;br&gt;
is not a universal guarantee. A different runtime version, model build,&lt;br&gt;
hardware path, parallel execution order, or other nondeterministic GPU behavior&lt;br&gt;
can still change the result. A seed controls the sampler's random sequence; it&lt;br&gt;
does not freeze the whole software and hardware stack.&lt;/p&gt;

&lt;p&gt;The practical takeaway is simple. Start near zero when repeatability matters,&lt;br&gt;
and raise temperature when you want variety. Add a fixed seed when you want more&lt;br&gt;
repeatable comparisons, and record the runtime and model version too. Everything&lt;br&gt;
else this week uses the same &lt;code&gt;options&lt;/code&gt; object in the API call.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 5: chat is a list of role-tagged messages
&lt;/h2&gt;

&lt;p&gt;So far I used &lt;code&gt;/api/generate&lt;/code&gt;, which takes one plain prompt. Ollama also has&lt;br&gt;
chat interfaces. I will use them in increasing depth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CLI proves I can have a one-shot conversation.&lt;/li&gt;
&lt;li&gt;The HTTP API exposes the role-tagged message structure.&lt;/li&gt;
&lt;li&gt;The Python client keeps that structure across multiple turns.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  First, chat through the CLI
&lt;/h3&gt;

&lt;p&gt;I keep using &lt;code&gt;phi4&lt;/code&gt;, already pulled in Concept 1, so there is nothing new to&lt;br&gt;
download. Ollama's CLI is terminal-aware, so I forced SSH to allocate a terminal&lt;br&gt;
with &lt;code&gt;-tt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-tt&lt;/span&gt; spark &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'ollama run --nowordwrap phi4 \
  "Reply with exactly these three words: local model ready"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local model ready.

Connection to spark closed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves the CLI can send a prompt and display a reply. It does not show the&lt;br&gt;
message roles or how a client maintains a conversation. For that, I need the&lt;br&gt;
HTTP API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Next, inspect messages through the API
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/api/chat&lt;/code&gt; endpoint takes a &lt;code&gt;messages&lt;/code&gt; array. Each item has a &lt;code&gt;role&lt;/code&gt; that&lt;br&gt;
says who wrote it and &lt;code&gt;content&lt;/code&gt; that holds the text:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Who writes it&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Sets behavior, style, or limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Person using the app&lt;/td&gt;
&lt;td&gt;Carries the user's prompt or follow-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assistant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Stores a previous model reply in the history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;system prompt&lt;/strong&gt; is an instruction the application places before the user&lt;br&gt;
conversation. It sets the expected role, style, or limits of the assistant. It&lt;br&gt;
is configuration, not a security boundary. A user can still send conflicting&lt;br&gt;
instructions, so production systems need checks outside the prompt too.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;user prompt&lt;/strong&gt; is simply the &lt;code&gt;content&lt;/code&gt; of a message with &lt;code&gt;role: "user"&lt;/code&gt;.&lt;br&gt;
After the model replies, the client stores that answer as an &lt;code&gt;assistant&lt;/code&gt; message.&lt;br&gt;
The next request sends the earlier messages plus the new user message.&lt;/p&gt;

&lt;p&gt;Why resend them? The Ollama API does not remember this client's conversation&lt;br&gt;
between independent HTTP requests. I sent two consecutive requests to the same&lt;br&gt;
running Ollama process. First, I included the earlier user question and&lt;br&gt;
assistant reply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/chat &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi4",
  "stream": false,
  "messages": [
    {"role": "system", "content": "Reply with only a name or UNKNOWN."},
    {"role": "user", "content": "My cluster is called Atlas."},
    {"role": "assistant", "content": "Atlas"},
    {"role": "user", "content": "What name did I give it?"}
  ],
  "options": {"temperature": 0, "seed": 42}
}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'{reply: .message, prompt_tokens: .prompt_eval_count}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Atlas"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I sent only the follow-up, with no earlier turns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:11434/api/chat &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi4",
  "stream": false,
  "messages": [
    {"role": "system", "content": "Reply with only a name or UNKNOWN."},
    {"role": "user", "content": "What name did I give it?"}
  ],
  "options": {"temperature": 0, "seed": 42}
}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'{reply: .message, prompt_tokens: .prompt_eval_count}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UNKNOWN"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Reply&lt;/th&gt;
&lt;th&gt;Prompt tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Earlier turns included&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Atlas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Earlier turns omitted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What changed between the two calls? Only the &lt;code&gt;messages&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;In the first request, the JSON body included the earlier line &lt;code&gt;My cluster is&lt;br&gt;
called Atlas&lt;/code&gt;, the model's earlier answer, and the new follow-up. Ollama could&lt;br&gt;
therefore pass all of that text to the model, so the answer was &lt;code&gt;Atlas&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the second request, the conversation history contained only &lt;code&gt;What name did I&lt;br&gt;
give it?&lt;/code&gt;. There was no mention of Atlas in its messages, so the model answered&lt;br&gt;
&lt;code&gt;UNKNOWN&lt;/code&gt;. Ollama did not look up the earlier HTTP call and add it automatically.&lt;/p&gt;

&lt;p&gt;That is what &lt;strong&gt;the client owns the conversation state&lt;/strong&gt; means. The application,&lt;br&gt;
such as this Python client, keeps the message list in memory or a database. For&lt;br&gt;
each new turn, it appends the latest user message and sends the relevant history&lt;br&gt;
again. Ollama processes the messages it receives in that request.&lt;/p&gt;

&lt;p&gt;Resending history has a cost. The request with earlier turns contained 50 prompt&lt;br&gt;
tokens; the request without them contained 31. The 19-token difference includes&lt;br&gt;
the added message text, role labels, and chat-template separators. I measured&lt;br&gt;
the total difference here; I did not split it token by token. All 19 additional&lt;br&gt;
tokens used positions in the context window.&lt;/p&gt;
&lt;h3&gt;
  
  
  Finally, implement the conversation in Python
&lt;/h3&gt;

&lt;p&gt;The required Python client now implements the message flow I just tested. It&lt;br&gt;
uses &lt;code&gt;/api/chat&lt;/code&gt;, keeps prior turns in the &lt;code&gt;messages&lt;/code&gt;&lt;br&gt;
list, and streams the reply as it arrives. With &lt;code&gt;"stream": true&lt;/code&gt;, Ollama sends&lt;br&gt;
one JSON object per line; each text fragment is in &lt;code&gt;message.content&lt;/code&gt;. The loop&lt;br&gt;
prints each fragment and joins them into the completed assistant message. Here&lt;br&gt;
is the complete file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Small command-line chat client for Ollama&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s streaming /api/chat endpoint.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.error&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phi4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a concise assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--top-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--num-ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--once&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Send one prompt and exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama request failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;once&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;request_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chatting with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Type /exit to quit.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;you&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;EOFError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used &lt;code&gt;--once&lt;/code&gt; for a repeatable blog example and passed a system prompt. If I&lt;br&gt;
leave off &lt;code&gt;--once&lt;/code&gt;, the same program opens an interactive loop and keeps each&lt;br&gt;
user and assistant turn in the next request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - \
  --model phi4 \
  --system "Answer in exactly one short sentence." \
  --once "What does the Kubernetes scheduler do?"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; public/week-02-first-local-model/chat_client.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Kubernetes scheduler assigns pods to nodes based on resource availability,
constraints, and policies.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same local runtime and the same &lt;code&gt;phi4&lt;/code&gt; model through three&lt;br&gt;
interfaces: Ollama's CLI, &lt;code&gt;/api/generate&lt;/code&gt; with &lt;code&gt;curl&lt;/code&gt;, and &lt;code&gt;/api/chat&lt;/code&gt; from&lt;br&gt;
Python. Only the client interface changed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Concept 6: measure cold starts and verify context allocation
&lt;/h2&gt;

&lt;p&gt;Imagine Phi-4 is behind a chat application. No one has used it for a while, so&lt;br&gt;
Ollama has unloaded it from memory. A user sends a message and waits about six&lt;br&gt;
seconds before seeing the first word. They send another message a minute later&lt;br&gt;
and see the first word in a fraction of a second.&lt;/p&gt;

&lt;p&gt;That difference is what this experiment measures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request state&lt;/th&gt;
&lt;th&gt;What Ollama must do&lt;/th&gt;
&lt;th&gt;What the user notices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cold&lt;/td&gt;
&lt;td&gt;Load the model, then process the prompt&lt;/td&gt;
&lt;td&gt;Long wait for first text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;Reuse the model already in memory&lt;/td&gt;
&lt;td&gt;First text arrives quickly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;strong&gt;cold request&lt;/strong&gt; starts with the model unloaded, so Ollama must load it before&lt;br&gt;
inference. A &lt;strong&gt;warm request&lt;/strong&gt; starts with the model already resident in memory&lt;br&gt;
and ready for reuse.&lt;/p&gt;

&lt;p&gt;Before running anything, I expected the first request to have a much higher&lt;br&gt;
&lt;strong&gt;time to first token&lt;/strong&gt; (TTFT) because it includes model loading. I expected the&lt;br&gt;
next requests to start quickly. I did not expect the output generation rate to&lt;br&gt;
change much, because all runs use the same model and output length.&lt;/p&gt;

&lt;p&gt;I also used the experiment to answer a separate configuration question:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What does &lt;code&gt;num_ctx&lt;/code&gt; change? It changes how much token capacity Ollama
allocates. It does not make a short prompt longer or automatically faster.&lt;/li&gt;
&lt;li&gt;How different is the first request from later requests? A streamed client can
measure real time to first token for both a cold and warm model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are two checks in one script, not a claim that context size causes the&lt;br&gt;
cold-start difference. The cold-versus-warm runs measure model loading. The&lt;br&gt;
2,048-versus-4,096 setting verifies how I request context capacity.&lt;/p&gt;

&lt;p&gt;The model metadata showed a maximum context length of 16,384 tokens. The&lt;br&gt;
&lt;strong&gt;configured context size&lt;/strong&gt; tells Ollama how much of that capacity to allocate&lt;br&gt;
for a run. The prompt does not become 4,096 tokens just because I set&lt;br&gt;
&lt;code&gt;num_ctx&lt;/code&gt; to 4,096. It means the prompt, chat history, and generated reply may&lt;br&gt;
use up to that budget.&lt;/p&gt;

&lt;p&gt;A larger context also needs a larger &lt;strong&gt;key-value cache&lt;/strong&gt;, often shortened to KV&lt;br&gt;
cache. That cache holds intermediate values from earlier tokens so decode does&lt;br&gt;
not repeat all prior work for every new token. More context gives the cache room&lt;br&gt;
for more tokens, which uses more memory.&lt;/p&gt;

&lt;p&gt;The benchmark changes &lt;code&gt;num_ctx&lt;/code&gt; from 2,048 to 4,096. At each setting it runs&lt;br&gt;
three requests in this exact order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Model state&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Cold&lt;/td&gt;
&lt;td&gt;Measure the user-visible cost of loading Phi-4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;Measure a request with Phi-4 already loaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;Repeat the warm measurement once more&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It fixes temperature and seed, limits each reply to 96 tokens, and calls&lt;br&gt;
&lt;code&gt;ollama stop phi4&lt;/code&gt; before run 1 at each context setting. Runs 2 and 3 do not stop&lt;br&gt;
the model, so they reuse it in memory.&lt;/p&gt;

&lt;p&gt;Most importantly, it requests a streamed response. The client starts a clock&lt;br&gt;
before the HTTP request and stops the TTFT clock when the first non-empty text&lt;br&gt;
chunk arrives. That is a real client-observed TTFT measurement.&lt;/p&gt;

&lt;p&gt;Here is the full benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Measure client TTFT and server timings from Ollama&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s streaming API.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phi4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain Kubernetes scheduling in three sentences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--contexts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2048,4096&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--runs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;unload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_predict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_token_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;first_token_at&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;first_token_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
    &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_token_at&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama stream ended without timing data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;eval_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eval_duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttft_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;first_token_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_total_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load_duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_eval_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_eval_duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens_per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eval_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;eval_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;contexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctx  run  cold  ttft_ms  total_ms  load_ms  out_tok  tok/s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;run_number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;run_number&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_number&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cold&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ttft_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_total_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;load_ms&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;output_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tokens_per_second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runs_per_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;measurements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran three requests per context size. The first is cold because the script&lt;br&gt;
stops Phi-4 before it. The next two reuse the loaded model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python - --contexts 2048,4096 --runs 3'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; public/week-02-first-local-model/benchmark.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ctx  run  cold  ttft_ms  total_ms  load_ms  out_tok  tok/s
2048    1   True   6034.1   10129.6   5908.4       96   23.4
2048    2  False    196.1    4288.0    140.4       96   23.5
2048    3  False    151.9    4244.7    104.1       96   23.5
4096    1   True   6260.8   10369.2   6150.1       96   23.4
4096    2  False    174.3    4262.8    117.8       96   23.5
4096    3  False    173.5    4260.8    124.8       96   23.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cold requests took about 6.0 to 6.3 seconds before the first token. The warm&lt;br&gt;
requests took 151.9 to 196.1 ms. This is the cold-start effect I could not&lt;br&gt;
measure from the earlier non-streaming call.&lt;/p&gt;

&lt;p&gt;Generation stayed at 23.4 to 23.5 tokens/sec. The short 18-token input did not&lt;br&gt;
use the extra context capacity, so doubling the setting did not make this test&lt;br&gt;
faster. It just allowed a larger KV cache. &lt;code&gt;ollama ps&lt;/code&gt; confirmed the last&lt;br&gt;
requested allocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'ollama ps'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME           ID              SIZE      PROCESSOR    CONTEXT    UNTIL
phi4:latest    ac896e5b8b34    9.7 GB    100% GPU     4096       4 minutes from now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What did this experiment prove?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A cold Phi-4 request took about 6 seconds to produce its first token because
Ollama had to load the model.&lt;/li&gt;
&lt;li&gt;Warm requests produced the first token in about 0.15 to 0.20 seconds.&lt;/li&gt;
&lt;li&gt;Ollama accepted the final 4,096-token setting and reported &lt;code&gt;CONTEXT 4096&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Doubling the requested context did not change throughput for this 18-token
prompt. Both settings stayed near 23.5 tokens/sec.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It did &lt;strong&gt;not&lt;/strong&gt; measure the memory difference between the two context settings,&lt;br&gt;
and it did not test a long prompt. Those need a separate experiment. The lesson&lt;br&gt;
here is simpler: keep models warm for latency, and treat context size as a&lt;br&gt;
capacity setting rather than a speed setting.&lt;/p&gt;
&lt;h2&gt;
  
  
  The token questions, answered
&lt;/h2&gt;

&lt;p&gt;Earlier, in the token section (Concept 2), I parked two questions to keep the&lt;br&gt;
main thread moving: how do I even know that &lt;code&gt;849&lt;/code&gt; is "Ex", and how does Ollama&lt;br&gt;
know which tokenizer to use? Here are the answers. You do not need them to follow&lt;br&gt;
the rest of the post, so read on only if you want to poke at the internals.&lt;/p&gt;
&lt;h3&gt;
  
  
  Wait, how do I even know what each token ID means?
&lt;/h3&gt;

&lt;p&gt;I did not guess. A runnable model package needs a &lt;strong&gt;tokenizer&lt;/strong&gt;, the exact&lt;br&gt;
rulebook that maps text to token IDs and back. The ordinary Phi-4 token IDs in&lt;br&gt;
the API response match OpenAI's &lt;code&gt;cl100k_base&lt;/code&gt; encoding. I verified that match&lt;br&gt;
with &lt;code&gt;decode_tokens.py&lt;/code&gt; instead of inferring it from the size of the IDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Week 2 - show how text becomes tokens for a cl100k model like phi4.

Encodes the prompt and decodes individual token IDs, so you can match the
`context` array from the generate API back to real words.

Run with a Python that has tiktoken installed, e.g. the Week 1 venv:
    ~/venvs/w1/bin/python -m pip install -q tiktoken
    ~/venvs/w1/bin/python decode_tokens.py
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain Kubernetes scheduling in three sentences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# A few IDs taken from the generate API's context array.
&lt;/span&gt;&lt;span class="n"&gt;SAMPLE_IDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;849&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21435&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67474&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;38952&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;304&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23719&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;882&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78191&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cl100k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encode:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decode individual IDs:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_IDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I ran the file with the pinned &lt;code&gt;tiktoken&lt;/code&gt; 0.13.0 package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; public/week-02-first-local-model/decode_tokens.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encode: [849, 21435, 67474, 38952, 304, 2380, 23719, 13]
decode individual IDs:
     849  'Ex'
   21435  'plain'
   67474  ' Kubernetes'
   38952  ' scheduling'
     304  ' in'
    2380  ' three'
   23719  ' sentences'
      13  '.'
     882  'user'
   78191  'assistant'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line reproduces the exact prompt tokens from the &lt;code&gt;context&lt;/code&gt; array, which&lt;br&gt;
confirms phi4 really does use this vocabulary. Decoding the individual IDs gives&lt;br&gt;
back the words, including &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;assistant&lt;/code&gt;. This proves the ordinary&lt;br&gt;
prompt and role IDs match &lt;code&gt;cl100k_base&lt;/code&gt;. It does not prove what the added special&lt;br&gt;
IDs mean. I verify those from the model file in the next section.&lt;/p&gt;

&lt;p&gt;Is &lt;code&gt;cl100k&lt;/code&gt; a universal standard? No. It is one of a small set of named&lt;br&gt;
vocabularies from OpenAI's &lt;code&gt;tiktoken&lt;/code&gt; library, where each vocabulary (such as&lt;br&gt;
&lt;code&gt;cl100k_base&lt;/code&gt; for GPT-4 and the newer &lt;code&gt;o200k_base&lt;/code&gt; for GPT-4o) is tied to specific&lt;br&gt;
models.&lt;sup id="fnref4"&gt;4&lt;/sup&gt; Other model families use entirely different tokenizers. Llama&lt;br&gt;
models, for example, use a different scheme with different token IDs, so the same&lt;br&gt;
word gets a different number there. Each model comes with its own vocabulary, and&lt;br&gt;
you can always load that exact tokenizer to see how it splits text.&lt;/p&gt;
&lt;h3&gt;
  
  
  And how does Ollama know which tokenizer to use?
&lt;/h3&gt;

&lt;p&gt;The runtime is what turns the prompt into tokens, so this is a fair question. It&lt;br&gt;
does not keep a list of models and guess. The tokenizer is packed inside the&lt;br&gt;
model file. Ollama stores models in the GGUF format,&lt;sup id="fnref5"&gt;5&lt;/sup&gt; and a GGUF file&lt;br&gt;
carries the tokenizer metadata next to the weights.&lt;/p&gt;

&lt;p&gt;There are two files involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The small manifest file is
&lt;code&gt;/usr/share/ollama/.ollama/models/manifests/registry.ollama.ai/library/phi4/latest&lt;/code&gt;.
It is a JSON index for the Phi-4 package. It does not contain the tokenizer.
Its model-layer entry tells Ollama which blob file to open.&lt;/li&gt;
&lt;li&gt;The model blob is in &lt;code&gt;/usr/share/ollama/.ollama/models/blobs&lt;/code&gt;. Its filename
is &lt;code&gt;sha256-fd7b6731c33c57f61767612f56517460ec2d1e2e5a3f0163e0eb3d8d8cb5df20&lt;/code&gt;.
This 9 GB file is the actual GGUF model. It contains the model weights,
tokenizer vocabulary, special tokens, and chat template.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I resolved that path directly before using Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'
  MODELS_DIR=/usr/share/ollama/.ollama/models
  MANIFEST_FILE=manifests/registry.ollama.ai/library/phi4/latest
  MANIFEST="$MODELS_DIR/$MANIFEST_FILE"
  DIGEST="$(jq -r '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'.layers[]
    | select(.mediaType == "application/vnd.ollama.image.model")
    | .digest'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' "$MANIFEST")"
  BLOB_FILE="blobs/${DIGEST/:/-}"
  printf "models directory: %s\n" "$MODELS_DIR"
  printf "manifest file: %s\n" "$MANIFEST_FILE"
  printf "model digest: %s\n" "$DIGEST"
  printf "GGUF file: %s\n" "$BLOB_FILE"
  stat -c "GGUF bytes: %s" "$MODELS_DIR/$BLOB_FILE"
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;models directory: /usr/share/ollama/.ollama/models
manifest file: manifests/registry.ollama.ai/library/phi4/latest
model digest: sha256:fd7b6731c33c57f61767612f56517460ec2d1e2e5a3f0163e0eb3d8d8cb5df20
GGUF file: blobs/sha256-fd7b6731c33c57f61767612f56517460ec2d1e2e5a3f0163e0eb3d8d8cb5df20
GGUF bytes: 9053114464
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The digest changes from &lt;code&gt;sha256:...&lt;/code&gt; in the manifest to &lt;code&gt;sha256-...&lt;/code&gt; in the&lt;br&gt;
blob filename. Only the colon becomes a hyphen. The Python script automates&lt;br&gt;
these same steps, opens that GGUF blob, and reads a few tokenizer fields so I do&lt;br&gt;
not have to inspect a 9 GB binary file by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read selected tokenizer metadata directly from an Ollama GGUF model layer.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;gguf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GGUFReader&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phi4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--models-dir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/share/ollama/.ollama/models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GGUFReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing GGUF field: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models_dir&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manifests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registry.ollama.ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;library&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;digest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;layers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mediaType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GGUFReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer.ggml.tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer.chat_template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer model:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer.ggml.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer pre:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokenizer.ggml.pre&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token count:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100257&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100265&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100266&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;|im_start|&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;|im_end|&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;|im_sep|&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template contains &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it with the pinned &lt;code&gt;gguf&lt;/code&gt; 0.19.0 package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; public/week-02-first-local-model/inspect_tokenizer.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenizer model: gpt2
tokenizer pre: dbrx
token count: 100352
token 100257: '&amp;lt;|endoftext|&amp;gt;'
token 100264: '&amp;lt;|im_start|&amp;gt;'
token 100265: '&amp;lt;|im_end|&amp;gt;'
token 100266: '&amp;lt;|im_sep|&amp;gt;'
template contains &amp;lt;|im_start|&amp;gt;: True
template contains &amp;lt;|im_end|&amp;gt;: True
template contains &amp;lt;|im_sep|&amp;gt;: True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything the runtime needs is in the file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tokenizer.ggml.model = 'gpt2'&lt;/code&gt; is the tokenizer type. It is a byte-pair
encoding (BPE), the same family GPT-2 and GPT-4 use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokenizer.ggml.tokens&lt;/code&gt; is the full vocabulary, all 100,352 entries, the list
that maps text pieces to IDs.&lt;/li&gt;
&lt;li&gt;The three IDs are the exact special-token strings claimed earlier.&lt;/li&gt;
&lt;li&gt;The embedded chat template contains those same markers. Phi-4's model card
uses this ChatML-style prompt layout.&lt;sup id="fnref6"&gt;6&lt;/sup&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Ollama reads the vocabulary from the model file itself and uses it to turn my&lt;br&gt;
prompt into token IDs. The ordinary IDs used in this prompt are compatible with&lt;br&gt;
&lt;code&gt;cl100k_base&lt;/code&gt;, while the GGUF adds Phi-4's special chat tokens. A Llama model's&lt;br&gt;
GGUF carries a different vocabulary, and Ollama uses that instead. Same runtime,&lt;br&gt;
different tokenizer per model, because the tokenizer travels with the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here is the Week 2 result in one place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Measured result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Ollama 0.31.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main model&lt;/td&gt;
&lt;td&gt;Phi-4, 14.7B parameters, Q4_K_M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model layer&lt;/td&gt;
&lt;td&gt;9,053,114,464 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context settings tested&lt;/td&gt;
&lt;td&gt;2,048 and 4,096 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold TTFT&lt;/td&gt;
&lt;td&gt;6,034.1 to 6,260.8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm TTFT&lt;/td&gt;
&lt;td&gt;151.9 to 196.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation speed&lt;/td&gt;
&lt;td&gt;23.4 to 23.5 tokens/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python deliverable&lt;/td&gt;
&lt;td&gt;Streaming command-line chat client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Benchmark deliverable&lt;/td&gt;
&lt;td&gt;Cold/warm TTFT and throughput benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I also used every interface in the plan: interactive CLI, &lt;code&gt;curl&lt;/code&gt;, and Python.&lt;br&gt;
The commands changed temperature and context size. The Python client used a&lt;br&gt;
system prompt, and the benchmark compared repeatable runs with measured timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;p&gt;The first surprise was the model size. Phi-4 has 14.7 billion parameters, but&lt;br&gt;
its quantized model layer is 9.1 GB instead of the roughly 29.4 GB needed for&lt;br&gt;
16-bit weights alone.&lt;/p&gt;

&lt;p&gt;The bigger surprise was the cold start. Generation speed stayed near 23.5&lt;br&gt;
tokens/sec, but the first token took about 6 seconds when Ollama had to load the&lt;br&gt;
model. Once warm, it arrived in about 0.15 to 0.20 seconds.&lt;/p&gt;

&lt;p&gt;Changing context capacity from 2,048 to 4,096 did not improve this short&lt;br&gt;
request. A larger limit gives the KV cache room for more tokens. It does not make&lt;br&gt;
an 18-token prompt larger or automatically faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes and troubleshooting
&lt;/h2&gt;

&lt;p&gt;I first called the output of &lt;code&gt;ollama show&lt;/code&gt; a manifest. That was wrong. Model&lt;br&gt;
metadata is a readable summary. The manifest is the JSON index that points to&lt;br&gt;
the model, template, license, and parameter layers.&lt;/p&gt;

&lt;p&gt;I also tried to estimate TTFT by adding &lt;code&gt;load_duration&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;prompt_eval_duration&lt;/code&gt; from a non-streaming response. That is useful server&lt;br&gt;
timing, but it is not client-observed TTFT. The fixed benchmark streams the&lt;br&gt;
response and timestamps the first non-empty text chunk.&lt;/p&gt;

&lt;p&gt;One smaller issue came from Ollama's terminal-aware CLI. Its one-shot response&lt;br&gt;
was not visible through plain SSH capture, so I used &lt;code&gt;ssh -tt&lt;/code&gt; to allocate a&lt;br&gt;
pseudo-terminal. The Python and &lt;code&gt;curl&lt;/code&gt; clients did not need that workaround.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production implications
&lt;/h2&gt;

&lt;p&gt;A production service should keep commonly used models warm. Otherwise, a user&lt;br&gt;
can wait seconds for model loading before generation even starts. Streaming also&lt;br&gt;
matters because it lets the user see the first token instead of waiting for the&lt;br&gt;
whole reply.&lt;/p&gt;

&lt;p&gt;Context size is a capacity and memory decision. Setting it to the model maximum&lt;br&gt;
for every request can reserve more KV-cache memory than the workload needs. The&lt;br&gt;
right setting comes from measured prompt and conversation lengths.&lt;/p&gt;

&lt;p&gt;The system prompt is not an access-control system. If I expose Ollama beyond&lt;br&gt;
localhost, I still need a gateway with authentication, authorization, TLS, rate&lt;br&gt;
limits, request-size limits, and logging. Prompt instructions do not replace&lt;br&gt;
those controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will learn next
&lt;/h2&gt;

&lt;p&gt;Week 3 moves from running a packaged model to opening a Hugging Face repository&lt;br&gt;
and inspecting its files. I will compare base and instruct models, read the&lt;br&gt;
model card and license, then inspect configuration, tokenizer, chat-template,&lt;br&gt;
and Safetensors files.&lt;sup id="fnref7"&gt;7&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The public Week 2 lab contains the chat client, benchmark, scripts, pinned&lt;br&gt;
requirements, raw results, and model-run record.&lt;sup id="fnref8"&gt;8&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Can llama.cpp reuse this Ollama model?
&lt;/h2&gt;

&lt;p&gt;This is optional reading. It is useful if you want to understand whether a model&lt;br&gt;
file belongs to one runtime or can move between compatible runtimes.&lt;/p&gt;

&lt;p&gt;For this Phi-4 download, yes. Ollama reports &lt;code&gt;details.format = "gguf"&lt;/code&gt;, and the&lt;br&gt;
independent file-header check agrees. &lt;code&gt;llama.cpp&lt;/code&gt; loads local GGUF files, so it&lt;br&gt;
can use this same 9,053,114,464-byte model blob without downloading the weights&lt;br&gt;
again.&lt;sup id="fnref5"&gt;5&lt;/sup&gt; The content matters, not the missing &lt;code&gt;.gguf&lt;/code&gt; extension on Ollama's&lt;br&gt;
content-addressed filename.&lt;/p&gt;

&lt;p&gt;A file header alone does not prove another runtime can load the model and&lt;br&gt;
generate text, so I installed a pinned CUDA build of &lt;code&gt;llama.cpp&lt;/code&gt; and pointed it&lt;br&gt;
at the exact model blob named by Ollama's manifest digest. It loaded the file&lt;br&gt;
and generated a reply:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model blob&lt;/td&gt;
&lt;td&gt;same file, Ollama manifest digest &lt;code&gt;fd7b...df20&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;llama.cpp&lt;/code&gt; build&lt;/td&gt;
&lt;td&gt;pinned commit &lt;code&gt;571d0d54&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device used&lt;/td&gt;
&lt;td&gt;CUDA0 (NVIDIA GB10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU layers offloaded&lt;/td&gt;
&lt;td&gt;41/41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated reply&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Shared model works.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is a concrete yes. The full investigation records the failed first check,&lt;br&gt;
CUDA toolkit discovery, pinned ARM64 build, scripts, commands, raw output, and&lt;br&gt;
the apparent terminal hang.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;One limit remains. The model blob is only one layer of the Ollama package. The&lt;br&gt;
manifest also lists a prompt template, license, and default parameters as&lt;br&gt;
separate layers. &lt;code&gt;llama.cpp&lt;/code&gt; does not read those Ollama layers just because it&lt;br&gt;
can read the model blob. This Phi-4 GGUF carries its own tokenizer and chat&lt;br&gt;
template, but I still set the seed, temperature, context, and GPU layers&lt;br&gt;
explicitly. Same weights do not guarantee the same output from two runtimes.&lt;/p&gt;

&lt;p&gt;This result is specific to this Phi-4 file and this pinned &lt;code&gt;llama.cpp&lt;/code&gt; commit.&lt;br&gt;
For another Ollama download, I would repeat the test because an older runtime&lt;br&gt;
may not support a newer model architecture.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Official project docs for the alternatives named here:&lt;br&gt;
&lt;code&gt;llama.cpp&lt;/code&gt; at &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;https://github.com/ggml-org/llama.cpp&lt;/a&gt;, vLLM at&lt;br&gt;
&lt;a href="https://docs.vllm.ai" rel="noopener noreferrer"&gt;https://docs.vllm.ai&lt;/a&gt;, NVIDIA TensorRT-LLM at&lt;br&gt;
&lt;a href="https://nvidia.github.io/TensorRT-LLM/" rel="noopener noreferrer"&gt;https://nvidia.github.io/TensorRT-LLM/&lt;/a&gt;, and Hugging Face Transformers at&lt;br&gt;
&lt;a href="https://huggingface.co/docs/transformers/index" rel="noopener noreferrer"&gt;https://huggingface.co/docs/transformers/index&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Full Ollama-to-llama.cpp investigation, including the pinned&lt;br&gt;
CUDA build and raw command output:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/week-02-first-local-model/ollama-model-in-llamacpp.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/week-02-first-local-model/ollama-model-in-llamacpp.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Ollama API reference:&lt;br&gt;
&lt;a href="https://docs.ollama.com/api/introduction" rel="noopener noreferrer"&gt;https://docs.ollama.com/api/introduction&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;tiktoken, OpenAI's tokenizer library, lists the standard vocabularies&lt;br&gt;
and which model each one belongs to. See the model-to-encoding table at&lt;br&gt;
&lt;a href="https://github.com/openai/tiktoken" rel="noopener noreferrer"&gt;https://github.com/openai/tiktoken&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;GGUF is the model file format from the llama.cpp project. It stores the&lt;br&gt;
weights plus metadata such as the tokenizer and chat template. See&lt;br&gt;
&lt;a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md" rel="noopener noreferrer"&gt;https://github.com/ggml-org/ggml/blob/master/docs/gguf.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Microsoft Phi-4 model card, including its chat input format:&lt;br&gt;
&lt;a href="https://huggingface.co/microsoft/phi-4#input-formats" rel="noopener noreferrer"&gt;https://huggingface.co/microsoft/phi-4#input-formats&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Week 3 roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-03.md" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/blob/main/roadmap/week-03.md&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;Week 2 companion lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-02-first-local-model" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-02-first-local-model&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From API to GPU, Week 1: Understanding Your Local GPU Environment</title>
      <dc:creator>Dinesh Kumar Ramasamy</dc:creator>
      <pubDate>Sat, 11 Jul 2026 20:57:28 +0000</pubDate>
      <link>https://dev.to/dramasamy/from-api-to-gpu-week-1-understanding-nvidia-dgx-spark-environment-1aol</link>
      <guid>https://dev.to/dramasamy/from-api-to-gpu-week-1-understanding-nvidia-dgx-spark-environment-1aol</guid>
      <description>&lt;p&gt;&lt;em&gt;Phase 1 of 8: Comfortable running local models. Week 1 of 32.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've used AI through APIs to ship features: &lt;code&gt;POST&lt;/code&gt; a prompt, get tokens back,&lt;br&gt;
move on. I have never once deployed a model myself. No PyTorch, no GPU memory&lt;br&gt;
math, no idea what actually happens between my HTTP request and the text that&lt;br&gt;
comes back. This series is me closing that gap on purpose, one week at a time,&lt;br&gt;
on an NVIDIA DGX Spark.&lt;/p&gt;

&lt;p&gt;I'm a software engineer and technical program manager. I'm comfortable with&lt;br&gt;
Linux, Python, Docker, Kubernetes, and APIs. I'm a complete beginner at machine&lt;br&gt;
learning. So Week 1 is deliberately unglamorous: before running any model, I&lt;br&gt;
want to &lt;em&gt;know the machine&lt;/em&gt;: what CPU and GPU it has, how its memory works, and&lt;br&gt;
what the NVIDIA software stack underneath is actually made of. Every claim below&lt;br&gt;
is backed by a real command and its real output, so you can run the same thing&lt;br&gt;
on your own box and compare.&lt;/p&gt;
&lt;h2&gt;
  
  
  About this series
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;From API to GPU&lt;/em&gt; is a 32-week journey from &lt;strong&gt;AI-API consumer&lt;/strong&gt; to&lt;br&gt;
&lt;strong&gt;local LLM systems architect&lt;/strong&gt;: running, optimizing, and eventually&lt;br&gt;
fine-tuning models on local hardware, documenting each week as a hands-on lab&lt;br&gt;
plus a blog post. The full week-by-week plan lives in the roadmap&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, and&lt;br&gt;
every week's runnable code lands in the companion GitHub repo&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. If you have&lt;br&gt;
a similar machine, you can follow along and reproduce every result.&lt;/p&gt;

&lt;p&gt;The plan runs in eight phases, with a parallel CUDA track starting around week 5:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Weeks&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1–4&lt;/td&gt;
&lt;td&gt;Comfortable running local models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5–8&lt;/td&gt;
&lt;td&gt;Enough ML to understand inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9–13&lt;/td&gt;
&lt;td&gt;Transformers and terminology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;14–16&lt;/td&gt;
&lt;td&gt;Quantization and model formats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;17–20&lt;/td&gt;
&lt;td&gt;Inference engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;21–24&lt;/td&gt;
&lt;td&gt;Production model services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;25–28&lt;/td&gt;
&lt;td&gt;RAG and application integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;29–32&lt;/td&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The goal of this first post&lt;/strong&gt; is narrow on purpose: stand up and &lt;em&gt;understand&lt;/em&gt;&lt;br&gt;
the environment. By the end you'll be able to inventory a DGX Spark, read what&lt;br&gt;
&lt;code&gt;nvidia-smi&lt;/code&gt; tells you, explain how its unified memory differs from a normal&lt;br&gt;
GPU, untangle the NVIDIA driver/CUDA-runtime/toolkit layers, and prove the GPU&lt;br&gt;
is usable from PyTorch with a measured CPU-vs-GPU speedup. No model yet; that's&lt;br&gt;
week 2. This is the foundation everything else builds on.&lt;/p&gt;

&lt;p&gt;All the commands in this post are packaged as a runnable script in the companion&lt;br&gt;
repo under &lt;code&gt;week-01-environment/&lt;/code&gt;&lt;sup id="fnref3"&gt;3&lt;/sup&gt;. Clone it, set up an SSH alias &lt;code&gt;spark&lt;/code&gt;&lt;br&gt;
that reaches your DGX Spark, and run &lt;code&gt;./inventory.sh&lt;/code&gt; to reproduce everything&lt;br&gt;
here. The repo holds the commands and scripts; this post holds the explanations,&lt;br&gt;
so neither repeats the other.&lt;/p&gt;

&lt;p&gt;Here's the whole inventory script, so you can see exactly what it runs without&lt;br&gt;
cloning anything. It just wraps each command in an SSH call to the Spark and&lt;br&gt;
prints a labelled section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Week 1: DGX Spark machine inventory. Prereq: an SSH alias `spark`.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;SPARK_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SPARK_HOST&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

run&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== {% katex inline %}1 ==="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; ssh &lt;span class="s2"&gt;"{% endkatex %}SPARK_HOST"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

run &lt;span class="s2"&gt;"uname"&lt;/span&gt;      &lt;span class="s1"&gt;'uname -a'&lt;/span&gt;
run &lt;span class="s2"&gt;"os-release"&lt;/span&gt; &lt;span class="s1"&gt;'cat /etc/os-release'&lt;/span&gt;
run &lt;span class="s2"&gt;"lscpu"&lt;/span&gt;      &lt;span class="s1"&gt;'lscpu'&lt;/span&gt;
run &lt;span class="s2"&gt;"memory"&lt;/span&gt;     &lt;span class="s1"&gt;'free -h'&lt;/span&gt;
run &lt;span class="s2"&gt;"storage"&lt;/span&gt;    &lt;span class="s1"&gt;'lsblk'&lt;/span&gt;
run &lt;span class="s2"&gt;"gpu"&lt;/span&gt;        &lt;span class="s1"&gt;'nvidia-smi'&lt;/span&gt;
run &lt;span class="s2"&gt;"cuda-nvcc"&lt;/span&gt;  &lt;span class="s1"&gt;'nvcc --version || /usr/local/cuda/bin/nvcc --version'&lt;/span&gt;
run &lt;span class="s2"&gt;"cuda-dirs"&lt;/span&gt;  &lt;span class="s1"&gt;'ls -d /usr/local/cuda*'&lt;/span&gt;
run &lt;span class="s2"&gt;"python"&lt;/span&gt;     &lt;span class="s1"&gt;'python3 --version'&lt;/span&gt;
run &lt;span class="s2"&gt;"docker"&lt;/span&gt;     &lt;span class="s1"&gt;'docker version'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The rest of this post walks through the interesting parts of that output one&lt;br&gt;
section at a time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where I started: two machines, and a rule I broke immediately
&lt;/h2&gt;

&lt;p&gt;The setup is two machines. A MacBook Pro is the &lt;em&gt;control&lt;/em&gt; machine, for writing,&lt;br&gt;
editing, and opening SSH sessions. An NVIDIA DGX Spark is the &lt;em&gt;workhorse&lt;/em&gt;, where&lt;br&gt;
every model and every GPU command actually runs. I reach it over SSH as &lt;code&gt;spark&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A theme for this whole series is that I verify facts with a command instead of&lt;br&gt;
assuming them, even the obvious ones. So rather than start by &lt;em&gt;stating&lt;/em&gt; what the&lt;br&gt;
machines are, I'll show them. First the control machine:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the control MacBook&lt;/span&gt;
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; machdep.cpu.brand_string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x86_64
Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An &lt;strong&gt;Intel x86_64&lt;/strong&gt; Mac. This matters more than it looks, because it's a&lt;br&gt;
different architecture from the Spark, so I want it on the record, not assumed.&lt;br&gt;
Now the Spark:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'uname -a'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linux spark-66b9 6.17.0-1026-nvidia ... aarch64 aarch64 aarch64 GNU/Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Spark is &lt;strong&gt;aarch64&lt;/strong&gt;: ARM64, the same CPU family as phones and Apple&lt;br&gt;
Silicon, but a different architecture from the Intel Mac.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Verified by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MacBook Pro&lt;/td&gt;
&lt;td&gt;control / authoring&lt;/td&gt;
&lt;td&gt;x86_64 (Intel i5)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uname -m&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DGX Spark&lt;/td&gt;
&lt;td&gt;model + GPU work&lt;/td&gt;
&lt;td&gt;aarch64 (ARM64)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uname -a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not trivia. Because the two machines are &lt;strong&gt;different architectures&lt;/strong&gt;, a&lt;br&gt;
Python wheel or Docker image built for the Intel Mac will not necessarily run on&lt;br&gt;
the ARM64 Spark. That is exactly why all the real work in this series happens&lt;br&gt;
over &lt;code&gt;ssh spark&lt;/code&gt;, on the box itself, and why "it works on my laptop" means&lt;br&gt;
nothing here.&lt;/p&gt;
&lt;h2&gt;
  
  
  CPU versus GPU, and why models love the GPU
&lt;/h2&gt;

&lt;p&gt;The Spark's CPU is a 20-core ARM chip:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'lscpu'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Architecture: aarch64
CPU(s):       20
Model name:   Cortex-X925   (10 performance cores)
Model name:   Cortex-A725   (10 efficiency cores)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Twenty cores sounds like a lot, but for running a model the CPU is mostly a&lt;br&gt;
traffic director: it runs the OS, your Python, and the data loading. The heavy&lt;br&gt;
lifting happens on the GPU. Here's what finally made that click for me.&lt;/p&gt;

&lt;p&gt;A neural-network layer boils down to one operation repeated endlessly: multiply&lt;br&gt;
a big grid of numbers (the model's &lt;strong&gt;weights&lt;/strong&gt;, 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
) by a list of numbers (the&lt;br&gt;
input, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
) and add them up, a &lt;strong&gt;matrix multiplication&lt;/strong&gt;. One output value is:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop op-limits"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="mop op-symbol large-op"&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;ij&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;A model with billions of parameters does &lt;em&gt;billions&lt;/em&gt; of these multiply-adds for a&lt;br&gt;
single token. The magic property is that they're &lt;strong&gt;independent&lt;/strong&gt;: computing&lt;br&gt;

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 doesn't need 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. In terms I already know, that's &lt;em&gt;embarrassingly&lt;br&gt;
parallel&lt;/em&gt;, like the map phase of a MapReduce where no shard waits on another.&lt;/p&gt;

&lt;p&gt;That's the whole reason a GPU wins:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;CPU (the 20 ARM cores)&lt;/th&gt;
&lt;th&gt;GPU (the NVIDIA GB10)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Parallel workers&lt;/td&gt;
&lt;td&gt;a few strong cores&lt;/td&gt;
&lt;td&gt;thousands of small cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good at&lt;/td&gt;
&lt;td&gt;branching logic, one-at-a-time&lt;/td&gt;
&lt;td&gt;the &lt;em&gt;same&lt;/em&gt; math on huge data at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analogy&lt;/td&gt;
&lt;td&gt;a few expert chefs&lt;/td&gt;
&lt;td&gt;a stadium of line cooks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A CPU is a handful of very smart workers doing tasks in sequence. A GPU is&lt;br&gt;
thousands of simpler workers all doing the &lt;em&gt;identical&lt;/em&gt; multiply-add on different&lt;br&gt;
numbers simultaneously. Since a model is nothing but that identical operation&lt;br&gt;
repeated, the GPU is the right tool.&lt;/p&gt;

&lt;p&gt;One caveat I'm carrying forward: those cores are useless if you can't &lt;em&gt;feed&lt;/em&gt;&lt;br&gt;
them numbers fast enough, so &lt;strong&gt;memory bandwidth&lt;/strong&gt;, not raw compute, usually&lt;br&gt;
limits how fast a model runs. I'll test the CPU-vs-GPU speed difference directly&lt;br&gt;
with a matmul benchmark once PyTorch is installed; my prediction is a 10x–50x&lt;br&gt;
GPU speedup, with a slow first GPU run due to one-time warmup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reading &lt;code&gt;nvidia-smi&lt;/code&gt;: my new &lt;code&gt;top&lt;/code&gt; for the GPU
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; is the command I'll run every day from now on. It's the GPU&lt;br&gt;
equivalent of &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;docker stats&lt;/code&gt;. Here's the real output, lightly trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'nvidia-smi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NVIDIA-SMI 580.159.03   Driver Version: 580.159.03   CUDA Version: 13.0
GPU 0: NVIDIA GB10   Persistence-M: On
Temp  Perf  Pwr:Usage/Cap   Memory-Usage    GPU-Util  Compute M.
35C   P8    4W / N/A        Not Supported   0%        Default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field by field, and why each one will matter later:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Driver Version&lt;/td&gt;
&lt;td&gt;580.159.03&lt;/td&gt;
&lt;td&gt;kernel driver talking to the GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CUDA Version&lt;/td&gt;
&lt;td&gt;13.0&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;max&lt;/strong&gt; CUDA the driver supports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU name&lt;/td&gt;
&lt;td&gt;NVIDIA GB10&lt;/td&gt;
&lt;td&gt;the device (Grace-Blackwell)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temp&lt;/td&gt;
&lt;td&gt;35C&lt;/td&gt;
&lt;td&gt;die temperature (heat → throttling)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perf&lt;/td&gt;
&lt;td&gt;P8&lt;/td&gt;
&lt;td&gt;clock state, P0 = max … P8 = idle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pwr:Usage/Cap&lt;/td&gt;
&lt;td&gt;4W / N/A&lt;/td&gt;
&lt;td&gt;current vs max power draw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory-Usage&lt;/td&gt;
&lt;td&gt;Not Supported&lt;/td&gt;
&lt;td&gt;would show VRAM used/total (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU-Util&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;% of last sample the GPU was busy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bottom of the output also has a process table listing every PID holding the&lt;br&gt;
GPU. Right now that's just Xorg, GNOME, and Firefox using it for the desktop.&lt;br&gt;
That's my first stop whenever I hit "out of memory": find the offender, like&lt;br&gt;
&lt;code&gt;lsof&lt;/code&gt; on a stuck port.&lt;/p&gt;

&lt;p&gt;For Week 1 I'm using &lt;code&gt;nvidia-smi&lt;/code&gt; purely as an &lt;strong&gt;inventory&lt;/strong&gt; tool: what GPU,&lt;br&gt;
what driver, what max CUDA, who's using it. The deeper use (streaming monitors,&lt;br&gt;
reading utilization and memory bandwidth to decide if a workload is&lt;br&gt;
compute-bound or memory-bound) is a profiling skill I'm deliberately saving for&lt;br&gt;
the CUDA track around Week 5, so I don't tangle the two learning tracks.&lt;/p&gt;
&lt;h2&gt;
  
  
  The surprise: unified memory, and a blank that isn't a bug
&lt;/h2&gt;

&lt;p&gt;The one field that stopped me was &lt;code&gt;Memory-Usage: Not Supported&lt;/code&gt;. On a normal PC&lt;br&gt;
with a discrete GPU, that column is how you answer "did my model fit? how much&lt;br&gt;
VRAM is left?" On the Spark it's blank, and that's not a bug; it's the whole&lt;br&gt;
point of the machine.&lt;/p&gt;

&lt;p&gt;A traditional GPU has its own separate memory (VRAM), physically distinct from&lt;br&gt;
system RAM. The DGX Spark's GB10 is a Grace-Blackwell superchip that fuses the&lt;br&gt;
ARM CPU and the GPU onto one package and gives them &lt;strong&gt;one shared memory pool&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'free -h'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;               total   used   free   shared  buff/cache   available
Mem:           121Gi   6.2Gi   67Gi     36Mi        48Gi        115Gi
Swap:           15Gi   152Ki   15Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional discrete GPU&lt;/th&gt;
&lt;th&gt;DGX Spark (GB10)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System RAM (e.g. 64 GB) + separate VRAM (e.g. 24 GB)&lt;/td&gt;
&lt;td&gt;one shared 121 GiB pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy data RAM → VRAM over PCIe&lt;/td&gt;
&lt;td&gt;CPU and GPU read the same memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Will it fit?" limited by VRAM (24 GB)&lt;/td&gt;
&lt;td&gt;limited by total RAM (121 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is &lt;strong&gt;unified memory&lt;/strong&gt;. &lt;code&gt;nvidia-smi&lt;/code&gt; reports "Not Supported" for GPU memory&lt;br&gt;
because there is no separate VRAM to report: the GPU's memory &lt;em&gt;is&lt;/em&gt; the system's&lt;br&gt;
121 GiB. In infra terms, a discrete GPU pays a "copy tax" moving weights across&lt;br&gt;
the PCIe bus, like shuffling data between two services with separate caches;&lt;br&gt;
unified memory removes that hop, like two services sharing one in-memory cache.&lt;/p&gt;

&lt;p&gt;The practical consequence for me: on the Spark, the ceiling on model size isn't&lt;br&gt;
a stingy 24 GB of VRAM; it's 121 GB. But I have to track model memory&lt;br&gt;
differently, via &lt;code&gt;free -h&lt;/code&gt; or PyTorch's own counters, not the &lt;code&gt;nvidia-smi&lt;/code&gt;&lt;br&gt;
memory column. The trade-off (which I'll measure later) is that shared memory&lt;br&gt;
usually has lower peak bandwidth than a top-end discrete card's dedicated VRAM,&lt;br&gt;
so the Spark trades some raw speed for the ability to fit much larger models.&lt;/p&gt;
&lt;h2&gt;
  
  
  Driver vs CUDA runtime vs CUDA toolkit
&lt;/h2&gt;

&lt;p&gt;The most confusing part of the NVIDIA stack for a newcomer is that "CUDA" isn't&lt;br&gt;
one thing: it's three separate layers, installed and versioned independently.&lt;br&gt;
Mapping each to infrastructure I already understand finally made it stick:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Infra analogy&lt;/th&gt;
&lt;th&gt;Who needs it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA driver&lt;/td&gt;
&lt;td&gt;kernel module that talks to the GPU&lt;/td&gt;
&lt;td&gt;a device driver&lt;/td&gt;
&lt;td&gt;everyone using the GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CUDA runtime (libcudart)&lt;/td&gt;
&lt;td&gt;shared libs an app calls to run GPU work&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;.so&lt;/code&gt; libs you link&lt;/td&gt;
&lt;td&gt;anyone &lt;em&gt;running&lt;/em&gt; GPU programs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CUDA toolkit&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;nvcc&lt;/code&gt; compiler, headers, profilers&lt;/td&gt;
&lt;td&gt;gcc + headers + build tools&lt;/td&gt;
&lt;td&gt;only people &lt;em&gt;compiling&lt;/em&gt; CUDA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The insight that unblocked me: &lt;strong&gt;you can run GPU code without the toolkit.&lt;/strong&gt;&lt;br&gt;
PyTorch ships its own copy of the CUDA runtime inside its wheel. So to run&lt;br&gt;
models I need the &lt;em&gt;driver&lt;/em&gt; (system-level) plus a &lt;em&gt;CUDA-enabled PyTorch&lt;/em&gt; (which&lt;br&gt;
brings its own runtime). I do &lt;strong&gt;not&lt;/strong&gt; need &lt;code&gt;nvcc&lt;/code&gt;; that's only for compiling&lt;br&gt;
custom CUDA kernels, a much-later CUDA-track activity.&lt;/p&gt;

&lt;p&gt;With that lens, two clues from the inventory make sense. First, the header line&lt;br&gt;
&lt;code&gt;CUDA Version: 13.0&lt;/code&gt; is the &lt;strong&gt;maximum&lt;/strong&gt; CUDA the driver supports, a ceiling, not&lt;br&gt;
what's installed. That's the number that matters this week: when I install&lt;br&gt;
PyTorch, I must pick a CUDA build ≤ 13.0 so the driver can run it.&lt;/p&gt;

&lt;p&gt;Second, the alarming-looking one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'nvcc --version'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash: nvcc: command not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks broken but isn't. It only means the toolkit's compiler isn't on my&lt;br&gt;
&lt;code&gt;PATH&lt;/code&gt;. The driver and runtime clearly work; &lt;code&gt;nvidia-smi&lt;/code&gt; talks to the GPU. And&lt;br&gt;
the toolkit is physically installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'ls -d /usr/local/cuda*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/local/cuda  /usr/local/cuda-13  /usr/local/cuda-13.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;nvcc&lt;/code&gt; exists; it's just at &lt;code&gt;/usr/local/cuda/bin/nvcc&lt;/code&gt;, not on &lt;code&gt;PATH&lt;/code&gt;. Rather&lt;br&gt;
than assert that, I confirmed it by calling the full path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'/usr/local/cuda/bin/nvcc --version'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cuda compilation tools, release 13.0, V13.0.88
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The toolkit is version &lt;strong&gt;13.0.88&lt;/strong&gt;, matching the driver's CUDA 13.0 ceiling, a&lt;br&gt;
healthy, consistent stack. Nothing is broken; &lt;code&gt;command not found&lt;/code&gt; was a &lt;code&gt;PATH&lt;/code&gt;&lt;br&gt;
issue, not a missing install. If I ever want &lt;code&gt;nvcc&lt;/code&gt; on &lt;code&gt;PATH&lt;/code&gt;, it's one line:&lt;br&gt;
&lt;code&gt;export PATH=/usr/local/cuda/bin:$PATH&lt;/code&gt;. But to &lt;em&gt;run&lt;/em&gt; models, I never need it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing PyTorch, the right way: a virtual environment
&lt;/h2&gt;

&lt;p&gt;PyTorch is the Python library I'll use to talk to the GPU. Before installing it,&lt;br&gt;
one habit worth keeping: never install into the system Python. I use a&lt;br&gt;
&lt;strong&gt;virtual environment&lt;/strong&gt; (venv): an isolated per-project Python with its own&lt;br&gt;
packages, exactly like a per-service dependency sandbox so one project's&lt;br&gt;
libraries can't break another's. On the Spark that's why the earlier&lt;br&gt;
&lt;code&gt;python3 -c "import torch"&lt;/code&gt; failed: the system Python genuinely has no torch, and&lt;br&gt;
I want to keep it that way.&lt;/p&gt;

&lt;p&gt;If you're following along, first check whether you already have PyTorch, since&lt;br&gt;
many setups ship with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'python3 -c "import torch, sys; print(torch.__version__)" \
  || echo "no torch in this Python"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't, so I made a clean venv and installed torch into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'python3 -m venv ~/venvs/w1'&lt;/span&gt;
ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -m pip install --upgrade pip'&lt;/span&gt;
ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python -m pip install torch numpy'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install is packaged as &lt;code&gt;setup.sh&lt;/code&gt; in the companion repo, which just runs the&lt;br&gt;
three steps above against &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;VENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{% katex inline %}{VENV:-{% endkatex %}HOME/venvs/w1}"&lt;/span&gt;
&lt;span class="nv"&gt;HERE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{% katex inline %}(cd "&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endkatex %&lt;span class="o"&gt;}(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" &amp;amp;&amp;amp; pwd)"&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;&lt;span class="s2"&gt;/bin/python"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip
&lt;span class="s2"&gt;"{% katex inline %}VENV/bin/python"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"{% endkatex %}HERE/requirements.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is what pip pulled in. Remember the stack tops out at CUDA&lt;br&gt;
13.0, and without me specifying any special index, PyPI served an &lt;strong&gt;ARM64 +&lt;br&gt;
CUDA 13&lt;/strong&gt; build automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successfully installed torch-2.13.0 nvidia-cuda-runtime-13.0.96
  nvidia-cudnn-cu13-9.20.0.48 nvidia-cublas-13.1.1.3 ... (aarch64 wheels)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the payoff of the "PyTorch ships its own CUDA runtime" point from&lt;br&gt;
earlier: I did &lt;strong&gt;not&lt;/strong&gt; install the CUDA toolkit or touch &lt;code&gt;nvcc&lt;/code&gt;. Torch brought&lt;br&gt;
its own CUDA 13 runtime libraries (&lt;code&gt;libcudart&lt;/code&gt;, &lt;code&gt;cuDNN&lt;/code&gt;, &lt;code&gt;cuBLAS&lt;/code&gt;) as ordinary&lt;br&gt;
Python wheels, matched to the ARM64 architecture and the driver's CUDA 13&lt;br&gt;
ceiling. The driver was the only piece I needed pre-installed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proving the GPU actually works from Python
&lt;/h2&gt;

&lt;p&gt;Now the moment this whole week builds to: does Python see the GPU? The&lt;br&gt;
validation script (&lt;code&gt;validate_gpu.py&lt;/code&gt; in the repo) is deliberately tiny:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PyTorch:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CUDA available:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CUDA version (torch):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_available&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Device:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_device_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Capability:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_device_capability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# put a real tensor on the GPU
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tensor on:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with the venv's Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python validate_gpu.py'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PyTorch: 2.13.0+cu130
CUDA available: True
CUDA version (torch): 13.0
Device: NVIDIA GB10
Capability: (12, 1)
Tensor on: cuda:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line here is worth reading. &lt;code&gt;CUDA available: True&lt;/code&gt; means PyTorch found a&lt;br&gt;
usable GPU through the driver. &lt;code&gt;2.13.0+cu130&lt;/code&gt; confirms it's a CUDA 13 build.&lt;br&gt;
&lt;code&gt;Device: NVIDIA GB10&lt;/code&gt; is our chip. &lt;code&gt;Capability: (12, 1)&lt;/code&gt; is the GPU's &lt;em&gt;compute&lt;br&gt;
capability&lt;/em&gt;, NVIDIA's versioning for GPU features; &lt;code&gt;12.x&lt;/code&gt; is the Blackwell&lt;br&gt;
generation. And &lt;code&gt;Tensor on: cuda:0&lt;/code&gt; is the real proof: the tensor physically&lt;br&gt;
lives in GPU memory, not on the CPU. That's the difference between "installed"&lt;br&gt;
and "actually usable."&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing the prediction: CPU vs GPU
&lt;/h2&gt;

&lt;p&gt;Earlier I predicted the GPU would beat the CPU by 10x-50x on a large matrix&lt;br&gt;
multiply. Time to measure instead of hand-wave. The benchmark&lt;br&gt;
(&lt;code&gt;benchmark.py&lt;/code&gt;) multiplies two 4096x4096 matrices 20 times on each device and&lt;br&gt;
averages. Two details make the GPU timing honest: a &lt;strong&gt;warmup&lt;/strong&gt; (the first GPU&lt;br&gt;
call pays a one-time kernel-load cost, so I run a few throwaway iterations&lt;br&gt;
first), and &lt;code&gt;torch.cuda.synchronize()&lt;/code&gt; before stopping the clock (CUDA launches&lt;br&gt;
kernels asynchronously, so without a sync I'd be timing &lt;em&gt;queueing&lt;/em&gt;, not&lt;br&gt;
&lt;em&gt;computing&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITERS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ITERS&lt;/span&gt;

&lt;span class="n"&gt;cpu_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;# on CPU
&lt;/span&gt;&lt;span class="n"&gt;ag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ag&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;            &lt;span class="c1"&gt;# warmup
&lt;/span&gt;&lt;span class="n"&gt;gpu_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# on GPU
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the core idea; the full &lt;code&gt;benchmark.py&lt;/code&gt; also times the very first (cold)&lt;br&gt;
GPU call and runs a TF32 pass, which is where the extra numbers below come from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh spark &lt;span class="s1"&gt;'~/venvs/w1/bin/python benchmark.py'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Matrix: 4096x4096 float32, iters: 20
CPU        172.57 ms   0.80 TFLOP/s
GPU cold     135.73 ms   (first call: one-time kernel load)
GPU FP32       7.67 ms   17.9 TFLOP/s   22.5x faster than CPU
GPU TF32       3.42 ms   40.1 TFLOP/s   50.4x faster than CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single 4096x4096 float32 matmul is about 137 billion floating-point&lt;br&gt;
operations. The 20-core ARM CPU works through it in ~173 ms (~0.80 TFLOP/s); the&lt;br&gt;
GB10 does it in ~7.7 ms (~17.9 TFLOP/s), a &lt;strong&gt;22.5x speedup&lt;/strong&gt;, inside my predicted&lt;br&gt;
10x-50x range. That gap is why GPUs exist: the same math, done thousands at a&lt;br&gt;
time instead of a few at a time.&lt;/p&gt;

&lt;p&gt;Two details in that output are worth calling out. First, the &lt;strong&gt;cold GPU call&lt;/strong&gt;&lt;br&gt;
took 135 ms, nearly as long as the whole CPU run, then every warm call took 7.7&lt;br&gt;
ms. That is the one-time kernel-load cost I predicted; it is exactly why the&lt;br&gt;
benchmark warms up before timing. Second, I measured a &lt;strong&gt;TF32&lt;/strong&gt; number too:&lt;br&gt;
40 TFLOP/s, a 50x speedup. TF32 is a lower-precision mode NVIDIA GPUs use on&lt;br&gt;
tensor cores; PyTorch leaves it off for matmul by default, so the honest FP32&lt;br&gt;
number is the 22.5x one. TF32 and other reduced-precision formats are a whole&lt;br&gt;
topic for later weeks, but it is worth seeing early that precision is a dial you&lt;br&gt;
can trade for speed.&lt;/p&gt;

&lt;p&gt;One caveat so the number is not oversold: this matmul is &lt;strong&gt;compute-bound&lt;/strong&gt; (it&lt;br&gt;
keeps the cores busy), which is why the GPU looks so good. When a model generates&lt;br&gt;
text token by token, it is usually &lt;strong&gt;memory-bandwidth-bound&lt;/strong&gt; instead, and the&lt;br&gt;
speedups are smaller. That distinction is a big theme later. (All these values&lt;br&gt;
are in &lt;code&gt;results.json&lt;/code&gt; in the repo; yours will differ by machine.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A GPU with &lt;strong&gt;no VRAM number&lt;/strong&gt; is a feature, not a fault. Unified memory
reframes "will it fit?" from ~24 GB to 121 GB.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvcc: command not found&lt;/code&gt; is not an error state; the CUDA that matters for
running models lives inside PyTorch, not in the toolkit. Installing torch pulled
its own CUDA 13 runtime as plain wheels; I never touched the toolkit.&lt;/li&gt;
&lt;li&gt;Verifying every fact with a command, even the "obvious" ones, already caught
assumptions I would otherwise have carried into later weeks.&lt;/li&gt;
&lt;li&gt;The GPU's 22.5x matmul win was real and measurable on day one, not a slide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'll do next
&lt;/h2&gt;

&lt;p&gt;Week 1 is done: I know the machine, I understand the NVIDIA stack, and I've proven&lt;br&gt;
the GPU is usable from Python with a real speedup. Week 2 leaves inventory behind&lt;br&gt;
and runs an actual language model with Ollama, a model runtime with a local HTTP&lt;br&gt;
API, where I'll start measuring the things that matter for serving: tokens per&lt;br&gt;
second and time to first token.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;32-week roadmap:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/roadmap" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/roadmap&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Companion code repository:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Week 1 lab:&lt;br&gt;
&lt;a href="https://github.com/dramasamy/from-api-to-gpu/tree/main/week-01-environment" rel="noopener noreferrer"&gt;https://github.com/dramasamy/from-api-to-gpu/tree/main/week-01-environment&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>gpu</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
