<?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 1: Understanding NVIDIA DGX Spark 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;I've used AI through APIs for years — &lt;code&gt;POST&lt;/code&gt; a prompt, get tokens back, ship the&lt;br&gt;
feature. 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 my&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 my 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 my 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, my Python, and the data loading. The heavy&lt;br&gt;
lifting happens on the GPU. Here's the intuition that finally made "why a GPU"&lt;br&gt;
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 just Xorg, GNOME, and Firefox using it for the desktop. That's&lt;br&gt;
my first stop whenever I hit "out of memory": find the offender, like &lt;code&gt;lsof&lt;/code&gt; on&lt;br&gt;
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   available
Mem:           121Gi   6.2Gi  67Gi   115Gi
&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 — many&lt;br&gt;
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 matters. &lt;code&gt;CUDA available: True&lt;/code&gt; means PyTorch found a usable GPU&lt;br&gt;
through the driver. &lt;code&gt;2.13.0+cu130&lt;/code&gt; confirms it's a CUDA 13 build. &lt;code&gt;Device: NVIDIA&lt;br&gt;
GB10&lt;/code&gt; is our chip. &lt;code&gt;Capability: (12, 1)&lt;/code&gt; is the GPU's &lt;em&gt;compute capability&lt;/em&gt; —&lt;br&gt;
NVIDIA's versioning for GPU features; &lt;code&gt;12.x&lt;/code&gt; is the Blackwell generation. And&lt;br&gt;
&lt;code&gt;Tensor on: cuda:0&lt;/code&gt; is the real proof: I created a tensor and it physically lives&lt;br&gt;
in GPU memory, not on the CPU. This is the line that says "I can run models on&lt;br&gt;
this machine now."&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;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  ms/matmul: 173.8   GFLOP/s: 790.8
GPU  ms/matmul: 7.53    GFLOP/s: 18263.5
Speedup (CPU/GPU): 23.1 x
&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 chews through it in ~174 ms (~0.79 TFLOP/s); the&lt;br&gt;
GB10 does it in ~7.5 ms (~18.3 TFLOP/s) — a &lt;strong&gt;23x speedup&lt;/strong&gt;, squarely inside my&lt;br&gt;
predicted 10x-50x range. That number &lt;em&gt;is&lt;/em&gt; the reason this hardware exists: the&lt;br&gt;
same math, done thousands-at-a-time instead of a-few-at-a-time. (These are the&lt;br&gt;
raw values 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 23x 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&lt;br&gt;
HTTP API — where I'll start measuring the things that matter for serving: tokens&lt;br&gt;
per 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>
