{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b3388cef",
   "metadata": {},
   "source": [
    "# Day 10 — Demo Day + The Road to HPC 🎓\n",
    "\n",
    "> **Mission briefing:** Ten days ago you walked into the Lab knowing basic Python. Today you walk out an AI explorer with a working **AI Studio** you built by hand — seven capabilities, one per day. Demo Day has three acts: **assemble & launch** the whole Studio, **rehearse** your demo, and then run the **Grand Benchmark** — where we reveal the single operation beneath everything you've built, and why making it *fast* is the next great adventure.\n",
    "\n",
    "**Previously in the Lab:** yesterday you unlocked the 🎮 **Game Master** and trained two agents with reinforcement learning — a Q-table and a Deep Q-Network.\n",
    "\n",
    "**Today you will:**\n",
    "- **Act I** — assemble every module and launch the full AI Studio app\n",
    "- **Act II** — rehearse a great demo and write an honest **model card**\n",
    "- **Act III** — run the **Grand Benchmark** and meet `C = A @ B`, the operation under all of AI\n",
    "- See why **high-performance computing** is the natural next chapter of your story\n",
    "\n",
    "**Today you unlock:** 🎓 nothing new to build — today you *ship* everything you already made.\n",
    "\n",
    "<details data-resource-precision=\"10\"><summary>A precise reading</summary><p>The classical dense matmul counts about 2n³ floating-point operations, but measured timing curves need not have slope 3 at every size. Compare the same size and precision, warm up, synchronize the GPU, and state whether transfer time is included. Speedups are measurements, not guarantees. Matrix products power many neural layers; tabular Q-learning is a scalar update, and other operations still matter.</p></details>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f299104",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:47.618372Z",
     "iopub.status.busy": "2026-08-17T12:57:47.618240Z",
     "iopub.status.idle": "2026-08-17T12:57:47.724516Z",
     "shell.execute_reply": "2026-08-17T12:57:47.723414Z"
    }
   },
   "outputs": [],
   "source": [
    "# ╔═══════════════════════════════════════════════════════════════════╗\n",
    "# ║ SETUP — run me first! (identical in every Lab notebook)           ║\n",
    "# ╚═══════════════════════════════════════════════════════════════════╝\n",
    "import os, sys, pathlib\n",
    "\n",
    "IN_COLAB = \"google.colab\" in sys.modules\n",
    "SMOKE = os.environ.get(\"SMOKE_TEST\", \"0\") == \"1\"   # tiny fast run for automated tests\n",
    "\n",
    "REPO_URL = \"https://github.com/A-Halimi/ai-studio-internship.git\"  # instructor: set once\n",
    "\n",
    "if IN_COLAB:\n",
    "    if not pathlib.Path(\"/content/ai-studio-internship\").exists():\n",
    "        !git clone -q {REPO_URL} /content/ai-studio-internship\n",
    "    %cd /content/ai-studio-internship/notebooks/day10                    # ← ADAPT day folder\n",
    "    %pip -q install gradio==6.19.0                                       # ← ADAPT per-day installs\n",
    "\n",
    "HERE = pathlib.Path.cwd()\n",
    "REPO = HERE.parents[1]                       # .../notebooks/dayNN -> repo root\n",
    "DATA_DIR = pathlib.Path(os.environ.get(\"COURSE_DATA_DIR\", REPO / \"data\"))\n",
    "SAMPLES_DIR = REPO / \"data_samples\"          # small datasets shipped with the repo\n",
    "MODELS_DIR = REPO / \"ai_studio\" / \"models\"   # where your Studio modules unlock\n",
    "for p in (REPO / \"data\", MODELS_DIR):\n",
    "    p.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "import random\n",
    "import numpy as np\n",
    "SEED = 42\n",
    "random.seed(SEED); np.random.seed(SEED)\n",
    "print(f\"✅ Setup done | Colab: {IN_COLAB} | Smoke test: {SMOKE}\")\n",
    "print(f\"   data: {DATA_DIR}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe2475b8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:47.726427Z",
     "iopub.status.busy": "2026-08-17T12:57:47.726224Z",
     "iopub.status.idle": "2026-08-17T12:57:49.005244Z",
     "shell.execute_reply": "2026-08-17T12:57:49.004145Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "torch.manual_seed(SEED)\n",
    "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "if DEVICE.type == \"cuda\":\n",
    "    print(f\"⚡ Running on GPU: {torch.cuda.get_device_name(0)}\")\n",
    "else:\n",
    "    print(\"🐢 No GPU found — running on CPU. Everything still works, just slower.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe9c04b2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:49.007270Z",
     "iopub.status.busy": "2026-08-17T12:57:49.007036Z",
     "iopub.status.idle": "2026-08-17T12:57:49.011158Z",
     "shell.execute_reply": "2026-08-17T12:57:49.010250Z"
    }
   },
   "outputs": [],
   "source": [
    "# ── CONFIG — the Lab's control panel for today's Grand Benchmark ──\n",
    "PURE_SIZES = [32, 64] if SMOKE else [32, 64, 128]                          # pure-Python matmul\n",
    "NP_SIZES   = [64, 128, 256] if SMOKE else [64, 128, 256, 512, 1024, 2048]  # NumPy & GPU matmul\n",
    "print(\"pure-Python sizes:\", PURE_SIZES)\n",
    "print(\"NumPy / GPU sizes:\", NP_SIZES)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51ded9de",
   "metadata": {},
   "source": [
    "# 🎬 Act I — Assemble the Studio\n",
    "\n",
    "## Roll call\n",
    "\n",
    "Your AI Studio is a small app (`ai_studio/`) that scans its `modules/` folder and shows one tab per capability. A module lights up only when the files it needs — the ones each day's notebook saved — are present. Let's import the Studio's own code and take attendance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bff5947",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:49.012536Z",
     "iopub.status.busy": "2026-08-17T12:57:49.012411Z",
     "iopub.status.idle": "2026-08-17T12:57:51.107933Z",
     "shell.execute_reply": "2026-08-17T12:57:51.106774Z"
    }
   },
   "outputs": [],
   "source": [
    "sys.path.insert(0, str(REPO))\n",
    "from ai_studio.studio import load_modules, missing_files, MODELS_DIR as STUDIO_MODELS\n",
    "\n",
    "modules = load_modules()\n",
    "n_unlocked = sum(1 for m in modules if not m[\"error\"] and not missing_files(m[\"requires\"]))\n",
    "\n",
    "print(f\"AI STUDIO — ROLL CALL   ({n_unlocked}/{len(modules)} modules unlocked)\")\n",
    "print(\"=\" * 64)\n",
    "for m in modules:\n",
    "    day = m[\"name\"].split(\"_\")[0]              # e.g. 'day05'\n",
    "    miss = missing_files(m[\"requires\"])\n",
    "    if m[\"error\"]:\n",
    "        status = \"⚠️  crashed on load\"\n",
    "    elif miss:\n",
    "        status = \"🔒 locked — missing: \" + \", \".join(miss)\n",
    "    else:\n",
    "        status = \"✅ unlocked\"\n",
    "    print(f\"  {m['title']:<24} [{day}]  {status}\")\n",
    "    if miss and not m[\"error\"]:\n",
    "        print(f\"       → finish the {day} notebook to unlock it\")\n",
    "print(\"=\" * 64)\n",
    "print(f\"Models live in: {STUDIO_MODELS}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1074a894",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 1 — how big are your models?\n",
    "\n",
    "The roll call tells you *which* modules are ready. Now show *how heavy* each one is. For every **unlocked** module, print each required file with its size.\n",
    "\n",
    "Use `path.stat().st_size` — it returns a file's size **in bytes**. Divide by 1024 for kilobytes. (Notice how tiny some are: the whole Q-table is a few hundred bytes, while a fine-tuned network is megabytes.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "90de9a3a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:51.110404Z",
     "iopub.status.busy": "2026-08-17T12:57:51.110031Z",
     "iopub.status.idle": "2026-08-17T12:57:51.136519Z",
     "shell.execute_reply": "2026-08-17T12:57:51.135471Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"Files behind each unlocked module:\")\n",
    "for m in modules:\n",
    "    if m[\"error\"] or missing_files(m[\"requires\"]):\n",
    "        continue\n",
    "    print(f\"\\n{m['title']}:\")\n",
    "    for fname in m[\"requires\"]:\n",
    "        path = STUDIO_MODELS / fname\n",
    "        # ==================== YOUR CODE HERE ====================\n",
    "        ### HINT: path.stat().st_size gives the size in bytes\n",
    "        ...\n",
    "        print(f\"   {fname:<34} {size_kb:9.1f} KB\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d904f83f",
   "metadata": {},
   "source": [
    "## Launch the Studio 🚀\n",
    "\n",
    "One line assembles the whole app from its modules and serves it. Locally it opens at `http://127.0.0.1:7860`; on Colab it prints a public share link. Take a lap through **every tab** — a slider that names penguins, a canvas that reads your handwriting, a camera that plays rock-paper-scissors, agents that learned from rewards. **You built all of it.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d20c9d38",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:51.138170Z",
     "iopub.status.busy": "2026-08-17T12:57:51.138022Z",
     "iopub.status.idle": "2026-08-17T12:57:53.030621Z",
     "shell.execute_reply": "2026-08-17T12:57:53.029064Z"
    }
   },
   "outputs": [],
   "source": [
    "if not SMOKE:\n",
    "    from ai_studio.studio import build_demo\n",
    "    demo = build_demo()\n",
    "    demo.launch(share=IN_COLAB)\n",
    "else:\n",
    "    print(\"Smoke test: skipping the live Studio launch (it would block forever).\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6e1ad10",
   "metadata": {},
   "source": [
    "# 🎬 Act II — Tell the story well\n",
    "\n",
    "A demo is not a code dump — it's a **story**. The best researchers make people *feel* why the work matters in 3 minutes. Here's a skeleton you can rehearse from:\n",
    "\n",
    "1. **The hook (1 sentence).** What AI is *not* — magic — and what it *is*: patterns learned from data. Open with that reframe.\n",
    "2. **The tour (pick 2 favorites).** Don't show all seven. Demo the two modules you're proudest of, live. Let people try them.\n",
    "3. **The hardest bug.** Tell the story of the nastiest thing that broke and how you chased it down. Struggle is the interesting part.\n",
    "4. **The surprise.** One thing that genuinely surprised you — a weird failure, an unexpected success.\n",
    "\n",
    "Real labs also publish a **model card** for every model they release: a short, honest fact sheet. Let's write one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e49a4957",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.032396Z",
     "iopub.status.busy": "2026-08-17T12:57:53.032186Z",
     "iopub.status.idle": "2026-08-17T12:57:53.037004Z",
     "shell.execute_reply": "2026-08-17T12:57:53.036083Z"
    }
   },
   "outputs": [],
   "source": [
    "def render_model_card(card):\n",
    "    \"\"\"Pretty-print a model-card dict as a tidy text card.\"\"\"\n",
    "    fields = [(\"task\", \"Task\"), (\"training_data\", \"Trained on\"),\n",
    "              (\"accuracy_or_quality\", \"How good\"),\n",
    "              (\"limitations\", \"Limitations\"),\n",
    "              (\"surprising_behavior\", \"Surprised me\")]\n",
    "    rule = \"═\" * 58\n",
    "    lines = [rule, f\"  📋 MODEL CARD — {card.get('name', '?')}\", rule]\n",
    "    for key, label in fields:\n",
    "        lines.append(f\"  {label:<12}: {card.get(key, '—')}\")\n",
    "    lines.append(rule)\n",
    "    return \"\\n\".join(lines)\n",
    "\n",
    "print(\"Pretty-printer ready — fill in your card in the next cell.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aa7816a",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 2 — write an honest model card\n",
    "\n",
    "Pick your **favorite** module and fill in its card. Be honest — especially about **limitations** and **surprising behavior**. In science, stating what your model *can't* do is a feature, not an admission of defeat.\n",
    "\n",
    "Fields: `name`, `task`, `training_data`, `accuracy_or_quality`, `limitations`, `surprising_behavior`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32cf9e8c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.038887Z",
     "iopub.status.busy": "2026-08-17T12:57:53.038732Z",
     "iopub.status.idle": "2026-08-17T12:57:53.042641Z",
     "shell.execute_reply": "2026-08-17T12:57:53.041499Z"
    }
   },
   "outputs": [],
   "source": [
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: pick YOUR favorite module and fill in every field honestly\n",
    "...\n",
    "print(render_model_card(my_model_card))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "beb98e9d",
   "metadata": {},
   "source": [
    "> 🔎 **Why model cards matter:** a model that is 99% accurate *on MNIST* may be useless on digits photographed in the wild. Naming the training data and the limitations is how honest labs prevent their work from being misused. Limitations aren't the fine print — they're part of the result."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10b73c3a",
   "metadata": {},
   "source": [
    "# 🎬 Act III — The Grand Benchmark ⚙️\n",
    "\n",
    "## One operation to rule them all\n",
    "\n",
    "Time for the reveal. Look back at everything you built and ask: *what is actually being computed?*\n",
    "\n",
    "- **Day 2 — kNN:** distances between feature vectors → **vector math**.\n",
    "- **Day 4 — you built `matmul` by hand:** the triple loop, from scratch.\n",
    "- **Days 5–7 — every neural network layer:** `output = weights @ input + bias` → **matrix multiply**.\n",
    "- **Day 8 — attention in transformers:** queries × keys × values → **matrix multiplies**.\n",
    "- **Day 9 — the DQN:** every forward pass through the network → **matrix multiplies**.\n",
    "\n",
    "Strip away the labels — penguins, pixels, words, rewards — and underneath it is **the same operation**, over and over:\n",
    "\n",
    "$$C = A \\times B \\qquad\\Longleftrightarrow\\qquad \\texttt{C = A @ B}$$\n",
    "\n",
    "Modern AI is, to a first approximation, **matrix multiplication at enormous scale**. So the most important question in high-performance computing is simply: *how fast can we compute `A @ B`?* Let's find out — three ways."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eea3cea4",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 3 — rebuild `matmul` from memory\n",
    "\n",
    "You wrote this on Day 4. Write it again, from memory, with the classic **triple loop**: each entry `C[i][j]` is the dot product of row `i` of `A` with column `j` of `B`. The assert checks you against NumPy on a 32×32 problem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f55e948e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.044161Z",
     "iopub.status.busy": "2026-08-17T12:57:53.044021Z",
     "iopub.status.idle": "2026-08-17T12:57:53.051652Z",
     "shell.execute_reply": "2026-08-17T12:57:53.050599Z"
    }
   },
   "outputs": [],
   "source": [
    "def matmul_by_hand(A, B):\n",
    "    \"\"\"Multiply two 2D lists with three nested loops (the Day 4 way).\"\"\"\n",
    "    n, k, m = len(A), len(B), len(B[0])       # A is n x k, B is k x m, C is n x m\n",
    "    C = [[0.0] * m for _ in range(n)]\n",
    "    # ==================== YOUR CODE HERE ====================\n",
    "    ### HINT: C[i][j] is the dot product of row i of A and column j of B\n",
    "    ...\n",
    "    return C\n",
    "\n",
    "rng = np.random.default_rng(SEED)\n",
    "A = rng.standard_normal((32, 32))\n",
    "B = rng.standard_normal((32, 32))\n",
    "C_hand = np.array(matmul_by_hand(A.tolist(), B.tolist()))\n",
    "assert np.allclose(C_hand, A @ B), \"Your matmul doesn't match NumPy — check the loops!\"\n",
    "print(\"✅ matmul_by_hand matches NumPy on 32x32 — the triple loop is correct.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6913b564",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 4 — time the hand-written version\n",
    "\n",
    "How long does your pure-Python triple loop take as the matrices grow? Time it on each size in `PURE_SIZES` and record the seconds.\n",
    "\n",
    "Use `time.perf_counter()` right before and after the call, then subtract. Even at 128×128 you'll feel it — that's **~2 million** multiply-adds in interpreted Python."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dabec275",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.053093Z",
     "iopub.status.busy": "2026-08-17T12:57:53.052955Z",
     "iopub.status.idle": "2026-08-17T12:57:53.160792Z",
     "shell.execute_reply": "2026-08-17T12:57:53.159668Z"
    }
   },
   "outputs": [],
   "source": [
    "import time\n",
    "pure_times = []\n",
    "for n in PURE_SIZES:\n",
    "    A = rng.standard_normal((n, n)).tolist()\n",
    "    B = rng.standard_normal((n, n)).tolist()\n",
    "    # ==================== YOUR CODE HERE ====================\n",
    "    ### HINT: t = time.perf_counter() - t0, where t0 was read just before the call\n",
    "    ...\n",
    "    pure_times.append(t)\n",
    "    print(f\"  pure Python  {n:>5} x {n:<5}  {t*1000:12.1f} ms\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97c64333",
   "metadata": {},
   "source": [
    "### The same job, industrial-strength\n",
    "\n",
    "NumPy hands the multiply to **BLAS** — decades-old, hyper-optimized C and assembly that respects your CPU's cache and vector units. PyTorch on a **GPU** hands it to thousands of cores at once. Same math, wildly different engineering. We time NumPy (best of 3 runs) and, if a GPU is present, PyTorch too."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c02b2f76",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.162414Z",
     "iopub.status.busy": "2026-08-17T12:57:53.162266Z",
     "iopub.status.idle": "2026-08-17T12:57:53.778086Z",
     "shell.execute_reply": "2026-08-17T12:57:53.776663Z"
    }
   },
   "outputs": [],
   "source": [
    "def _time_once(fn):\n",
    "    t0 = time.perf_counter(); fn(); return time.perf_counter() - t0\n",
    "\n",
    "# NumPy on the CPU (best of 3 runs)\n",
    "numpy_times = []\n",
    "for n in NP_SIZES:\n",
    "    A = rng.standard_normal((n, n))\n",
    "    B = rng.standard_normal((n, n))\n",
    "    best = min(_time_once(lambda: A @ B) for _ in range(3))\n",
    "    numpy_times.append(best)\n",
    "    print(f\"  NumPy   {n:>5} x {n:<5}  {best*1000:12.3f} ms\")\n",
    "\n",
    "# GPU with PyTorch (only if we actually have one)\n",
    "gpu_sizes, gpu_times = [], []\n",
    "if DEVICE.type == \"cuda\":\n",
    "    for n in NP_SIZES:\n",
    "        A = torch.randn(n, n, device=DEVICE)\n",
    "        B = torch.randn(n, n, device=DEVICE)\n",
    "        for _ in range(3):                 # warm-up (first call is slow)\n",
    "            _ = A @ B\n",
    "        torch.cuda.synchronize()           # wait for the GPU to actually finish\n",
    "        t0 = time.perf_counter()\n",
    "        for _ in range(10):\n",
    "            _ = A @ B\n",
    "        torch.cuda.synchronize()\n",
    "        t = (time.perf_counter() - t0) / 10\n",
    "        gpu_sizes.append(n); gpu_times.append(t)\n",
    "        print(f\"  GPU     {n:>5} x {n:<5}  {t*1000:12.3f} ms\")\n",
    "else:\n",
    "    print(\"  (No CUDA GPU here — skipping the GPU line. The story still works on CPU!)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8573aa58",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 5 — the money plot 📈\n",
    "\n",
    "Draw all three lines on one **log-log** chart: time (seconds) versus matrix size `n`. Plot `pure_times` over `PURE_SIZES`, `numpy_times` over `NP_SIZES`, and — if you measured it — `gpu_times` over `gpu_sizes`.\n",
    "\n",
    "`plt.loglog(sizes, times, \"o-\", label=...)` draws one line. Label the axes and add a legend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ed21fdf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:53.780221Z",
     "iopub.status.busy": "2026-08-17T12:57:53.779737Z",
     "iopub.status.idle": "2026-08-17T12:57:54.226904Z",
     "shell.execute_reply": "2026-08-17T12:57:54.225742Z"
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: one plt.loglog(...) call per line; pass label= so the legend fills in\n",
    "...\n",
    "plt.xlabel(\"matrix size n  (multiplying n x n matrices)\")\n",
    "plt.ylabel(\"time (seconds)\")\n",
    "plt.title(\"The same multiplication, three ways\")\n",
    "plt.legend()\n",
    "plt.grid(True, which=\"both\", alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1f1654a",
   "metadata": {},
   "source": [
    "<h3>Operation counts and measured slopes</h3><p>The classical triple loop uses approximately <code>n³</code> multiply-adds, or <code>2n³</code> floating-point operations. Doubling n multiplies this work by about 8; tripling it by 27. This is <code>O(n³)</code> work.</p><p>Wall-clock timing is different: startup overhead, cache, parallelism and hardware utilization can bend the curves. A log-log slope near 3 is a possible large-size regime, not a requirement for all three measured lines. Compare speedups only at matching sizes and precision.</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30ba8f66",
   "metadata": {},
   "source": [
    "### 🧪 Exercise 6 — put a number on the gap\n",
    "\n",
    "At the largest size measured by **both** the pure-Python and NumPy runs, compute two numbers:\n",
    "\n",
    "- **speedup** = (pure-Python time) ÷ (NumPy time) — how many times faster NumPy is.\n",
    "- **GFLOP/s** = $2 n^3 / t / 10^9$ — billions of floating-point operations per second (a matmul does about $2n^3$ of them)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16153877",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:54.230001Z",
     "iopub.status.busy": "2026-08-17T12:57:54.229494Z",
     "iopub.status.idle": "2026-08-17T12:57:54.235398Z",
     "shell.execute_reply": "2026-08-17T12:57:54.234308Z"
    }
   },
   "outputs": [],
   "source": [
    "n_common = max(set(PURE_SIZES) & set(NP_SIZES))    # biggest size both methods ran\n",
    "t_py = pure_times[PURE_SIZES.index(n_common)]\n",
    "t_np = numpy_times[NP_SIZES.index(n_common)]\n",
    "# ==================== YOUR CODE HERE ====================\n",
    "### HINT: a matmul does ~2*n^3 flops; GFLOP/s = (2*n^3) / time / 1e9\n",
    "...\n",
    "\n",
    "print(f\"At {n_common} x {n_common}, the SAME multiplication:\")\n",
    "print(f\"  pure Python : {t_py*1000:12.1f} ms\")\n",
    "print(f\"  NumPy       : {t_np*1000:12.3f} ms   →  {speedup:,.0f}x faster,  {gflops_np:,.1f} GFLOP/s\")\n",
    "if gpu_sizes and n_common in gpu_sizes:\n",
    "    t_gpu = gpu_times[gpu_sizes.index(n_common)]\n",
    "    gflops_gpu = 2 * n_common**3 / t_gpu / 1e9\n",
    "    print(f\"  GPU         : {t_gpu*1000:12.3f} ms   →  {t_py/t_gpu:,.0f}x faster than pure Python,  {gflops_gpu:,.1f} GFLOP/s\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6dd6cb3a",
   "metadata": {},
   "source": [
    "**Same formula. Same answer. A factor of thousands — sometimes ~10,000× — in speed.** Nobody changed the math. They changed *how the hardware runs it*: memory layout, cache blocking, vectorized instructions, and thousands of GPU cores working in parallel.\n",
    "\n",
    "That is exactly what **high-performance computing** is about. **Keep this plot.** When you meet HPC for real, your mission: take the gap you just measured and *close it yourself*."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31845565",
   "metadata": {},
   "source": [
    "<h3>Why efficient computation matters</h3><p>Neural inference combines matrix products with activations, normalization, memory access and other work. Its cost depends on the architecture, context length and generated token count; one FLOP is not one whole matrix multiplication.</p><p>At large scale, improving kernels, memory movement and batching can lower latency and cost. Use the experiment to identify which part of a measured workload is limiting performance, rather than assuming every operation is <code>A @ B</code>.</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56d32eca",
   "metadata": {},
   "source": [
    "# 🎓 Graduation\n",
    "\n",
    "Put your name in and run the cell. You earned this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db7a70da",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-17T12:57:54.237283Z",
     "iopub.status.busy": "2026-08-17T12:57:54.237085Z",
     "iopub.status.idle": "2026-08-17T12:57:54.242816Z",
     "shell.execute_reply": "2026-08-17T12:57:54.241751Z"
    }
   },
   "outputs": [],
   "source": [
    "YOUR_NAME = \"Your Name Here\"      # ← put your name here!\n",
    "\n",
    "MODULES_BUILT = [\n",
    "    \"Prediction Machine   (Day 2 - supervised learning)\",\n",
    "    \"Pattern Finder       (Day 3 - clustering)\",\n",
    "    \"Digit Reader         (Day 5 - neural networks)\",\n",
    "    \"Photo Detective      (Day 6 - convolutional networks)\",\n",
    "    \"RPS Arena            (Day 7 - transfer learning)\",\n",
    "    \"Language Lab         (Day 8 - transformers)\",\n",
    "    \"Game Master          (Day 9 - reinforcement learning)\",\n",
    "]\n",
    "\n",
    "def certificate(name):\n",
    "    W = 58\n",
    "    line = \"=\" * W\n",
    "    def center(s):\n",
    "        return \"|\" + s.center(W) + \"|\"\n",
    "    out  = [\"+\" + line + \"+\", center(\"\"), center(\"*  CERTIFICATE OF COMPLETION  *\"), center(\"\")]\n",
    "    out += [center(\"This certifies that\"), center(\"\"), center(name.upper()), center(\"\")]\n",
    "    out += [center(\"is now a\"), center(\"CERTIFIED AI EXPLORER\"),\n",
    "            center(\"the Lab  -  Class of 2026\"), center(\"\")]\n",
    "    out += [\"+\" + line + \"+\", center(\"\"), center(\"Seven modules, built by hand:\"), center(\"\")]\n",
    "    for m in MODULES_BUILT:\n",
    "        out.append(\"|  \" + m.ljust(W - 2) + \"|\")\n",
    "    out += [center(\"\"), \"+\" + line + \"+\"]\n",
    "    return \"\\n\".join(out)\n",
    "\n",
    "print(certificate(YOUR_NAME))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2ba2084",
   "metadata": {},
   "source": [
    "## 🏁 The whole journey\n",
    "\n",
    "Look how far you came — one concept, one working module, every single day:\n",
    "\n",
    "- **Day 1 — What is AI?** You met models as *patterns learned from data*, not magic.\n",
    "- **Day 2 — Supervised learning** → 🐧 **Prediction Machine** (classify penguins).\n",
    "- **Day 3 — Unsupervised learning** → 🎨 **Pattern Finder** (cluster colors with k-means).\n",
    "- **Day 4 — Neural nets from scratch** — you built `matmul` and a network in pure NumPy. *(Today you saw why that operation matters so much.)*\n",
    "- **Day 5 — PyTorch & MNIST** → ✍️ **Digit Reader** (a network reads your handwriting).\n",
    "- **Day 6 — Convolutional networks** → 🕵️ **Photo Detective** (see the world in 32×32).\n",
    "- **Day 7 — Transfer learning** → 🪨📄✂️ **RPS Arena** (fine-tune a pretrained vision model).\n",
    "- **Day 8 — Transformers** → 💬 **Language Lab** (generate, classify, and fill in text).\n",
    "- **Day 9 — Reinforcement learning** → 🎮 **Game Master** (agents that learn from rewards).\n",
    "- **Day 10 — Demo Day** → you shipped the Studio and found `A @ B` under all of it.\n",
    "\n",
    "**Week 1, you learned how machines learn.** Week 2, you gave them eyes, words, and instincts. And you just discovered the one operation they all run on — and how staggeringly its speed can vary.\n",
    "\n",
    "**Next: make them fast.** The road leads into high-performance computing — squeezing every last FLOP out of the hardware. See you on the other side of the matmul. 🚀\n",
    "\n",
    "*— the Lab*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87b5a425",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "<div align=\"center\" style=\"background:#14161E;border-radius:12px;padding:18px 12px 14px;margin-top:6px\">\n",
    "  <a href=\"https://ahalimi.com\" target=\"_blank\">\n",
    "    <img src=\"data:image/webp;base64,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\" alt=\"Dr. Abdelghafour HALIMI\" width=\"260\"/>\n",
    "  </a>\n",
    "</div>\n",
    "\n",
    "<div align=\"center\" style=\"text-align:center;margin-top:8px\"><sub>The Lab · AI Studio — Intro to AI · © 2026 <b>Dr. Abdelghafour HALIMI</b> · <a href=\"https://ahalimi.com\">ahalimi.com</a> · All rights reserved.</sub></div>"
   ]
  }
 ],
 "metadata": {
  "authors": [
   {
    "name": "Dr. Abdelghafour HALIMI"
   }
  ],
  "copyright": "© 2026 Dr. Abdelghafour HALIMI · ahalimi.com · All rights reserved",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
