<?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 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>
