<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Cahier</title>
 <link href="https://biplob1ly.github.io/atom.xml" rel="self"/>
 <link href="https://biplob1ly.github.io/"/>
 <updated>2026-06-10T23:58:17+00:00</updated>
 <id>https://biplob1ly.github.io</id>
 <author>
   <name>Biplob Biswas</name>
   <email>biplob1ly@gmail.com</email>
 </author>

 
 <entry>
   <title>LLM Inference with vLLM</title>
   <link href="https://biplob1ly.github.io/2026/05/25/LLM-inference-with-vLLM/"/>
   <updated>2026-05-25T00:00:00+00:00</updated>
   <id>https://biplob1ly.github.io/2026/05/25/LLM-inference-with-vLLM</id>
   <content type="html">&lt;p&gt;Hi there! Biplob here. This article is about serving an LLM with vLLM on Nvidia DGX.&lt;/p&gt;

&lt;h2 id=&quot;check&quot;&gt;Check&lt;/h2&gt;

&lt;p&gt;First check nvidia gpu.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# GPU memory status&lt;/span&gt;
nvidia-smi

&lt;span class=&quot;c&quot;&gt;# Cuda version&lt;/span&gt;
nvcc &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;install-uv&quot;&gt;Install UV&lt;/h2&gt;

&lt;p&gt;Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt;: the project’s preferred package manager (if not already installed):&lt;/p&gt;

&lt;p&gt;Run these commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install uv&lt;/span&gt;
curl &lt;span class=&quot;nt&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh

&lt;span class=&quot;c&quot;&gt;# Reload shell to pick up uv in PATH&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.local/bin/env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-1--create-a-uv-virtual-environment&quot;&gt;Step 1 — Create a uv Virtual Environment:&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Create project folder&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; ~/vllm-server &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/vllm-server

&lt;span class=&quot;c&quot;&gt;# Create venv with Python 3.11 (stable for vLLM)&lt;/span&gt;
uv venv .venv &lt;span class=&quot;nt&quot;&gt;--python&lt;/span&gt; 3.11

&lt;span class=&quot;c&quot;&gt;# Activate it&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class=&quot;c&quot;&gt;# Confirm Python version&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-2--install-pytorch-for-cuda-130--blackwell&quot;&gt;Step 2 — Install PyTorch for CUDA 13.0 / Blackwell&lt;/h2&gt;
&lt;p&gt;The GB10 is a Blackwell GPU. Use the nightly PyTorch build which has Blackwell (sm_100) support:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install PyTorch nightly with CUDA 12.8 wheels&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# (CUDA 12.8 wheels are the latest stable; CUDA 13.0 is forward-compatible)&lt;/span&gt;
uv pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pre&lt;/span&gt; torch torchvision torchaudio &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--index-url&lt;/span&gt; https://download.pytorch.org/whl/nightly/cu128

&lt;span class=&quot;c&quot;&gt;# Verify PyTorch sees your GB10&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;import torch; print(&apos;PyTorch:&apos;, torch.__version__); print(&apos;CUDA available:&apos;, torch.cuda.is_available()); print(&apos;GPU:&apos;, torch.cuda.get_device_name(0))&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-3--install-vllm&quot;&gt;Step 3 — Install vLLM&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install latest vLLM (0.9.x+ has Blackwell/GB10 support)&lt;/span&gt;
uv pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;vllm

&lt;span class=&quot;c&quot;&gt;# Verify&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;import vllm; print(&apos;vLLM version:&apos;, vllm.__version__)&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-4--download-qwen3-14b&quot;&gt;Step 4 — Download Qwen3-14B&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install huggingface_hub&lt;/span&gt;
uv pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;huggingface_hub[cli]

&lt;span class=&quot;c&quot;&gt;# Download model (AWQ quantized — fits better on single GB10)&lt;/span&gt;
hf download Qwen/Qwen3-14B-AWQ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--local-dir&lt;/span&gt; ~/models/Qwen3-14B-AWQ

&lt;span class=&quot;c&quot;&gt;# --- OR --- download full BF16 if your GB10 has enough VRAM&lt;/span&gt;
hf download Qwen/Qwen3-14B &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--local-dir&lt;/span&gt; ~/models/Qwen3-14B
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-5--create-a-starting-script&quot;&gt;Step 5 — Create a starting script&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/projects/vllm-server/.venv/bin/activate

&lt;span class=&quot;nb&quot;&gt;nohup &lt;/span&gt;vllm serve ~/models/Qwen3-14B &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 8000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--dtype&lt;/span&gt; bfloat16 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--max-model-len&lt;/span&gt; 131072 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--gpu-memory-utilization&lt;/span&gt; 0.90 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--max-num-seqs&lt;/span&gt; 4 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--enable-prefix-caching&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--served-model-name&lt;/span&gt; qwen3-14b &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ~/projects/vllm-server/vllm.log 2&amp;gt;&amp;amp;1 &amp;amp;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$!&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ~/projects/vllm-server/vllm.pid
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vLLM started with PID: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/projects/vllm-server/vllm.pid&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;meaning-of-the-parameters&quot;&gt;Meaning of the Parameters:&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Parameter&lt;/th&gt;
      &lt;th&gt;What it means&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--host 0.0.0.0&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Listen on all network interfaces (not just localhost). Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt; to restrict to local only&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--port 8000&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Port the HTTP server listens on&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dtype bfloat16&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Weight precision. BF16 is ideal for Blackwell/Ampere — full quality, half the memory of FP32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--max-model-len&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Max context window in tokens (input + output combined)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu-memory-utilization&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Fraction of memory vLLM can use for KV cache (0.0–1.0). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.90&lt;/code&gt; leaves headroom for OS&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--max-num-seqs&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Max concurrent requests being processed. Higher = more throughput, more memory&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--enable-prefix-caching&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Reuses KV cache for repeated prompt prefixes (great for system prompts). Speeds up repeated calls&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--served-model-name&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The model name clients use in API calls. Can be anything — doesn’t have to match folder name&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;step-6---start-monitor-and-check&quot;&gt;Step 6 - Start, Monitor and Check&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ~/projects/vllm-server/vllm.log
&lt;span class=&quot;c&quot;&gt;# You&apos;ll know it&apos;s ready when you see:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# INFO:     Application startup complete.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# INFO:     Uvicorn running on http://0.0.0.0:8000&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x ~/projects/vllm-server/start.sh
~/projects/vllm-server/start.sh

&lt;span class=&quot;c&quot;&gt;# Model loading takes 1–3 minutes. Watch the log:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ~/projects/vllm-server/vllm.log

&lt;span class=&quot;c&quot;&gt;# 1. Health check&lt;/span&gt;
curl http://localhost:8000/health

&lt;span class=&quot;c&quot;&gt;# 2. List loaded models&lt;/span&gt;
curl http://localhost:8000/v1/models

&lt;span class=&quot;c&quot;&gt;# 3. Test inference&lt;/span&gt;
curl http://localhost:8000/v1/chat/completions &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
    &quot;model&quot;: &quot;qwen3-14b&quot;,
    &quot;messages&quot;: [
      {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;You are a helpful assistant.&quot;},
      {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Explain unified memory in one paragraph.&quot;}
    ],
    &quot;max_tokens&quot;: 300,
    &quot;temperature&quot;: 0.7
  }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-7---stop-the-service&quot;&gt;Step 7 - Stop the Service&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Graceful stop using saved PID&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/projects/vllm-server/vllm.pid&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Confirm it&apos;s stopped&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;2 &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; nvidia-smi

&lt;span class=&quot;c&quot;&gt;# Clean up PID file&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; ~/projects/vllm-server/vllm.pid

&lt;span class=&quot;c&quot;&gt;# If the PID file is lost:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Find and kill by process name&lt;/span&gt;
pkill &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vllm serve&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Check if It&apos;s Already Running&lt;/span&gt;
hpgrep &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vllm serve&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To watch nvidia gpu usage on another terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watch &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; nvidia-smi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;| Flag | Meaning |
|—|—|
| &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n 1&lt;/code&gt; | Refresh every &lt;strong&gt;1 second&lt;/strong&gt; |
| &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt; | &lt;strong&gt;Highlight differences&lt;/strong&gt; between refreshes |&lt;/p&gt;

&lt;p&gt;To exit: Press Ctrl + C.&lt;/p&gt;

&lt;p&gt;Good practice to use .env file. Here’s how to set it up:&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;env-file&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For Ollama:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;LLM_API_BASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://localhost:11434
&lt;span class=&quot;nv&quot;&gt;LLM_MODEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ollama/qwen3:14b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;For vLLM:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;LLM_API_BASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://localhost:8000/v1
&lt;span class=&quot;nv&quot;&gt;LLM_MODEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;openai/qwen3-14b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;python-code-unchanged-regardless-of-which-backend&quot;&gt;Python code (unchanged regardless of which backend)&lt;/h3&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dotenv&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;load_dotenv&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;load_dotenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LLM_MODEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;api_base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LLM_API_BASE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;completion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;litellm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dummy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;key-difference-in-the-url&quot;&gt;Key difference in the URL&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Backend&lt;/th&gt;
      &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM_API_BASE&lt;/code&gt;&lt;/th&gt;
      &lt;th&gt;Why&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:11434&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Ollama uses its own format; LiteLLM’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ollama/&lt;/code&gt; prefix handles the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/chat&lt;/code&gt; path internally&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;vLLM&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8000/v1&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;vLLM is OpenAI-compatible; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1&lt;/code&gt; suffix is required for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openai/&lt;/code&gt; prefix to route correctly&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;So to switch backends you only ever edit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file — your Python code stays the same.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Training LLM with LlamaFactory on Nvidia DGX</title>
   <link href="https://biplob1ly.github.io/2026/05/20/LLM-training-with-LlamaFactory/"/>
   <updated>2026-05-20T00:00:00+00:00</updated>
   <id>https://biplob1ly.github.io/2026/05/20/LLM-training-with-LlamaFactory</id>
   <content type="html">&lt;p&gt;Hi there! Biplob here. This article is about training a small LLM on Nvidia DGX with &lt;a href=&quot;https://github.com/hiyouga/LlamaFactory&quot;&gt;LlamaFactory&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;First clone the Llamafactory git repo.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone &lt;span class=&quot;nt&quot;&gt;--depth&lt;/span&gt; 1 https://github.com/hiyouga/LlamaFactory.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;LlamaFactory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;install-uv&quot;&gt;Install UV&lt;/h2&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; isn’t installed yet. Here’s the plan:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; (the project’s preferred package manager)&lt;/li&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv&lt;/code&gt; to install LlamaFactory in an isolated environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run these commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install uv&lt;/span&gt;
curl &lt;span class=&quot;nt&quot;&gt;-LsSf&lt;/span&gt; https://astral.sh/uv/install.sh | sh

&lt;span class=&quot;c&quot;&gt;# Reload shell to pick up uv in PATH&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.local/bin/env

&lt;span class=&quot;c&quot;&gt;# Sync the project (creates a .venv and installs all deps)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/projects/LlamaFactory
uv &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Optionally install metrics extras&lt;/span&gt;
uv pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; requirements/metrics.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, you can run LlamaFactory commands with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run llamafactory-cli webui
&lt;span class=&quot;c&quot;&gt;# or&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# For training&lt;/span&gt;
uv run llamafactory-cli train examples/train_lora/qwen3_lora_sft.yaml

&lt;span class=&quot;c&quot;&gt;# For inference/chat&lt;/span&gt;
uv run llamafactory-cli chat examples/inference/qwen3_lora_sft.yaml

&lt;span class=&quot;c&quot;&gt;# For exporting model&lt;/span&gt;
uv run llamafactory-cli &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;examples/merge_lora/qwen3_lora_sft.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To watch nvidia gpu usage on another terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watch &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 0.5 nvidia-smi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;-n 0.5: Refreshes the window every 0.5 seconds (twice a second).&lt;/p&gt;

&lt;p&gt;To exit: Press Ctrl + C.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How Did I Do This?</title>
   <link href="https://biplob1ly.github.io/2020/06/10/GPT-style-Transformer-LM/"/>
   <updated>2020-06-10T00:00:00+00:00</updated>
   <id>https://biplob1ly.github.io/2020/06/10/GPT-style-Transformer-LM</id>
   <content type="html">&lt;p&gt;Hi there!
In case you are wondering how to make a github page like this, it’s relatively easy! Just introduce yourself with &lt;a href=&quot;https://lanyon.getpoole.com/&quot;&gt;Lanyon&lt;/a&gt; which is a Jekyll theme. You can also modify it according to &lt;a href=&quot;http://patricksteadman.ca/2014/08/04/lanyonsetup/&quot;&gt;this&lt;/a&gt; and &lt;a href=&quot;https://www.nikhita.dev/build-blog-using-github-jekyll&quot;&gt;that&lt;/a&gt; post. A little understanding of markdown and github pages should be sufficient to accomplish all these.&lt;/p&gt;

&lt;p&gt;Hope this helps, Thanks!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>GPT-style decoder-only Transformer</title>
   <link href="https://biplob1ly.github.io/2020/06/08/introducing-lanyon/"/>
   <updated>2020-06-08T00:00:00+00:00</updated>
   <id>https://biplob1ly.github.io/2020/06/08/introducing-lanyon</id>
   <content type="html">&lt;p&gt;Howdy! Biplob Here!
Back with a fully annotated, from-scratch GPT-style decoder-only Transformer.&lt;/p&gt;

&lt;h1 id=&quot;transformer_lmpy&quot;&gt;transformer_lm.py&lt;/h1&gt;

&lt;p&gt;Every tensor operation is commented with its shape transformation so you can
follow the data through the entire forward pass.&lt;/p&gt;

&lt;p&gt;Notation used throughout:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;B  = batch size&lt;/li&gt;
  &lt;li&gt;T  = sequence length (number of tokens)&lt;/li&gt;
  &lt;li&gt;D  = d_model  (hidden / embedding dimension)&lt;/li&gt;
  &lt;li&gt;H  = n_heads  (number of attention heads)&lt;/li&gt;
  &lt;li&gt;Dh = D // H   (per-head dimension, also called head_dim)&lt;/li&gt;
  &lt;li&gt;V  = vocab_size&lt;/li&gt;
  &lt;li&gt;F  = d_ffn    (feed-forward inner dimension, typically 4 * D)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import math
import torch
import torch.nn as nn
import torch.nn.functional as F


# ─────────────────────────────────────────────────────────────────────────────
# 1.  TOKEN + POSITIONAL EMBEDDINGS
# ─────────────────────────────────────────────────────────────────────────────

class Embeddings(nn.Module):
    &quot;&quot;&quot;
    Converts a sequence of integer token IDs into a sequence of continuous
    vectors by summing a learned token embedding with a learned positional
    embedding.

    Token embedding:    maps each vocabulary index to a D-dimensional vector.
    Positional embedding: maps each sequence position (0 … T-1) to a
                          D-dimensional vector so the model knows *where*
                          each token sits in the sequence.
    &quot;&quot;&quot;

    def __init__(self, vocab_size: int, d_model: int, seq_len: int, dropout: float):
        super().__init__()

        # Lookup table: V rows, each D-wide.
        # Indexing with a token id returns its embedding vector.
        self.token_emb = nn.Embedding(vocab_size, d_model)   # shape: (V, D)

        # Lookup table: seq_len rows, each D-wide.
        # Indexing with position index 0…T-1 returns positional embedding.
        self.pos_emb   = nn.Embedding(seq_len,    d_model)   # shape: (T_max, D)

        self.dropout   = nn.Dropout(dropout)
        self.d_model   = d_model

    def forward(self, idx: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        idx  : (B, T)  — integer token IDs
        return (B, T, D)
        &quot;&quot;&quot;
        B, T = idx.shape

        # ── token embeddings ─────────────────────────────────────────────────
        # Each integer in idx is replaced by its D-dimensional row from the
        # token embedding table.
        #   idx          : (B, T)
        #   tok_emb      : (B, T, D)
        tok_emb = self.token_emb(idx)                        # (B, T, D)

        # ── positional embeddings ─────────────────────────────────────────────
        # Build position indices [0, 1, 2, …, T-1] and look them up.
        #   positions    : (T,)   → broadcast to (1, T) → look up → (1, T, D)
        positions = torch.arange(T, device=idx.device)      # (T,)
        pos_emb   = self.pos_emb(positions)                  # (T, D) → broadcast (1,T,D)

        # ── sum and scale ─────────────────────────────────────────────────────
        # Adding positional to token embedding is the standard GPT approach.
        # Scaling by sqrt(D) keeps the magnitude stable (from &quot;Attention is
        # All You Need&quot;; sometimes omitted in GPT implementations).
        x = tok_emb + pos_emb                                # (B, T, D)  broadcast add
        x = self.dropout(x)                                  # (B, T, D)
        return x


# ─────────────────────────────────────────────────────────────────────────────
# 2.  MULTI-HEAD CAUSAL SELF-ATTENTION
# ─────────────────────────────────────────────────────────────────────────────

class CausalSelfAttention(nn.Module):
    &quot;&quot;&quot;
    Scaled dot-product self-attention with a causal (autoregressive) mask so
    that position t can only attend to positions 0 … t.

    The QKV projections are fused into one linear layer for efficiency:

        [Q, K, V] = X @ W_qkv      (one big matmul instead of three)

    Then the output projection merges the heads back into D dimensions.

    Head splitting:
        D  total dimensions are split into H heads of Dh = D/H each.
        Each head learns to attend to a different subspace of the embedding.
    &quot;&quot;&quot;

    def __init__(self, d_model: int, n_heads: int, seq_len: int, dropout: float):
        super().__init__()
        assert d_model % n_heads == 0, &quot;d_model must be divisible by n_heads&quot;

        self.d_model  = d_model          # D
        self.n_heads  = n_heads          # H
        self.head_dim = d_model // n_heads  # Dh = D / H

        # ── fused QKV projection ──────────────────────────────────────────────
        # Input  (B, T, D)  × weight (D, 3D)  →  output (B, T, 3D)
        # The factor-of-3 gives us Q, K, and V concatenated in one go.
        self.qkv_proj = nn.Linear(d_model, 3 * d_model, bias=False)

        # ── output projection ─────────────────────────────────────────────────
        # After concatenating all heads: (B, T, D) → (B, T, D)
        self.out_proj = nn.Linear(d_model, d_model, bias=False)

        self.attn_dropout = nn.Dropout(dropout)
        self.resid_dropout = nn.Dropout(dropout)

        # ── causal mask ───────────────────────────────────────────────────────
        # Upper-triangular matrix of -inf so softmax zeroes out future tokens.
        # Registered as a buffer so it moves to the right device automatically
        # but is NOT a learned parameter.
        #   mask shape: (1, 1, T_max, T_max)  — broadcastable over (B, H)
        causal_mask = torch.triu(
            torch.full((seq_len, seq_len), float(&apos;-inf&apos;)),
            diagonal=1
        )                                                    # (T_max, T_max)
        self.register_buffer(
            &apos;causal_mask&apos;,
            causal_mask.unsqueeze(0).unsqueeze(0)            # (1, 1, T_max, T_max)
        )

    def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        x      : (B, T, D)
        return   (B, T, D)
        &quot;&quot;&quot;
        B, T, D = x.shape

        # ── step 1: project input to Q, K, V ─────────────────────────────────
        #   x        : (B, T, D)
        #   qkv_proj : weight (D, 3D)
        #   qkv      : (B, T, 3D)
        qkv = self.qkv_proj(x)                               # (B, T, 3D)

        # Split along the last axis into three equal chunks.
        #   q, k, v  : each (B, T, D)
        q, k, v = qkv.split(self.d_model, dim=-1)            # 3 × (B, T, D)

        # ── step 2: split into heads ──────────────────────────────────────────
        # Reshape from (B, T, D) to (B, T, H, Dh) then transpose to
        # (B, H, T, Dh) so the head axis is second — each head sees
        # a full sequence of Dh-dimensional vectors.
        #
        #   view(B, T, H, Dh)    : unstack D into H heads of Dh each
        #   transpose(1, 2)      : swap T and H axes
        #   → (B, H, T, Dh)
        def split_heads(t):
            return (t.view(B, T, self.n_heads, self.head_dim)  # (B, T, H, Dh)
                     .transpose(1, 2))                         # (B, H, T, Dh)

        q = split_heads(q)   # (B, H, T, Dh)
        k = split_heads(k)   # (B, H, T, Dh)
        v = split_heads(v)   # (B, H, T, Dh)

        # ── step 3: scaled dot-product attention ──────────────────────────────
        # Attention scores: how much should each position attend to each other?
        #
        #   q          : (B, H, T, Dh)
        #   k.transpose: (B, H, Dh, T)   ← swap last two dims
        #   scores     : (B, H, T, T)    ← (T queries) × (T keys)
        #
        # Dividing by sqrt(Dh) prevents the dot products from growing large
        # in magnitude (which would push softmax into near-zero gradient regions).
        scale  = math.sqrt(self.head_dim)
        scores = torch.matmul(q, k.transpose(-2, -1)) / scale  # (B, H, T, T)

        # ── step 4: apply causal mask ─────────────────────────────────────────
        # Add -inf to all future positions so softmax assigns them zero weight.
        # Slice the pre-built mask to the current sequence length T
        # (at inference time T can be shorter than the training seq_len).
        #   mask slice : (1, 1, T, T)  broadcasts over (B, H, T, T)
        scores = scores + self.causal_mask[:, :, :T, :T]      # (B, H, T, T)

        # ── step 5: softmax → attention weights ──────────────────────────────
        #   weights : (B, H, T, T)   — each row sums to 1 (over key positions)
        weights = F.softmax(scores, dim=-1)                    # (B, H, T, T)
        weights = self.attn_dropout(weights)

        # ── step 6: weighted sum of values ───────────────────────────────────
        #   weights : (B, H, T, T)
        #   v       : (B, H, T, Dh)
        #   attn_out: (B, H, T, Dh)   — each position is now a mixture of values
        attn_out = torch.matmul(weights, v)                    # (B, H, T, Dh)

        # ── step 7: merge heads back ──────────────────────────────────────────
        # Reverse the split_heads operation:
        #   transpose(1,2) : (B, T, H, Dh)
        #   contiguous()   : make memory layout contiguous before reshape
        #   view(B, T, D)  : flatten H * Dh back into D
        attn_out = (attn_out.transpose(1, 2)                   # (B, T, H, Dh)
                             .contiguous()
                             .view(B, T, D))                   # (B, T, D)

        # ── step 8: output projection ─────────────────────────────────────────
        # Linear mix of all head outputs: (B, T, D) → (B, T, D)
        out = self.out_proj(attn_out)                          # (B, T, D)
        out = self.resid_dropout(out)
        return out                                             # (B, T, D)


# ─────────────────────────────────────────────────────────────────────────────
# 3.  FEED-FORWARD NETWORK  (position-wise MLP)
# ─────────────────────────────────────────────────────────────────────────────

class FeedForward(nn.Module):
    &quot;&quot;&quot;
    Two-layer MLP applied independently to each token position.

    Architecture:
        x  →  Linear(D → 4D)  →  GeLU  →  Linear(4D → D)  →  Dropout

    The 4× expansion gives the model extra capacity to learn non-linear
    feature interactions. The bottleneck structure means each token&apos;s
    representation is projected up, transformed, then projected back down.
    &quot;&quot;&quot;

    def __init__(self, d_model: int, dropout: float):
        super().__init__()
        d_ffn = 4 * d_model              # inner dimension  (commonly 4 × D)

        # ── first linear: expand D → 4D ──────────────────────────────────────
        # Applied to each token independently: (B, T, D) → (B, T, 4D)
        self.fc1     = nn.Linear(d_model, d_ffn)

        # ── second linear: project 4D → D ────────────────────────────────────
        # Brings representation back to model dimension: (B, T, 4D) → (B, T, D)
        self.fc2     = nn.Linear(d_ffn, d_model)

        self.dropout = nn.Dropout(dropout)

    def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        x      : (B, T, D)
        return   (B, T, D)
        &quot;&quot;&quot;
        # fc1 + GeLU: (B, T, D) → (B, T, 4D)
        x = F.gelu(self.fc1(x))          # (B, T, 4D)

        # fc2 + dropout: (B, T, 4D) → (B, T, D)
        x = self.dropout(self.fc2(x))    # (B, T, D)
        return x


# ─────────────────────────────────────────────────────────────────────────────
# 4.  TRANSFORMER DECODER BLOCK
# ─────────────────────────────────────────────────────────────────────────────

class TransformerBlock(nn.Module):
    &quot;&quot;&quot;
    One decoder block: pre-norm self-attention + pre-norm feed-forward.

    Pre-LayerNorm (as used in GPT-2 / GPT-3):
        x  →  LN  →  Attention  →  + residual  →  LN  →  FFN  →  + residual

    The residual connections are critical — they let gradients flow directly
    from the loss to every layer without vanishing, and they mean that
    each block only needs to learn a *correction* to the existing representation.

    Pre-LN (normalise before the sub-layer) is preferred over Post-LN
    (normalise after) because it produces more stable gradients during
    training of very deep networks.
    &quot;&quot;&quot;

    def __init__(self, d_model: int, n_heads: int, seq_len: int, dropout: float):
        super().__init__()

        # Layer norms applied BEFORE each sub-layer (pre-LN convention)
        self.ln1  = nn.LayerNorm(d_model)   # normalises (B, T, D) → (B, T, D)
        self.ln2  = nn.LayerNorm(d_model)

        self.attn = CausalSelfAttention(d_model, n_heads, seq_len, dropout)
        self.ffn  = FeedForward(d_model, dropout)

    def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        x      : (B, T, D)
        return   (B, T, D)
        &quot;&quot;&quot;
        # ── sub-layer 1: masked self-attention with residual ──────────────────
        #   ln1(x)      : (B, T, D)   normalise across D for each token
        #   attn(...)   : (B, T, D)   attend across T positions
        #   x + ...     : (B, T, D)   residual skip connection
        x = x + self.attn(self.ln1(x))   # (B, T, D)

        # ── sub-layer 2: position-wise FFN with residual ──────────────────────
        #   ln2(x)      : (B, T, D)
        #   ffn(...)    : (B, T, D)
        #   x + ...     : (B, T, D)
        x = x + self.ffn(self.ln2(x))    # (B, T, D)

        return x                         # (B, T, D)


# ─────────────────────────────────────────────────────────────────────────────
# 5.  FULL LANGUAGE MODEL
# ─────────────────────────────────────────────────────────────────────────────

class TransformerLM(nn.Module):
    &quot;&quot;&quot;
    GPT-style decoder-only language model.

    Full forward pass pipeline:
        token IDs (B, T)
             ↓
        Embeddings: token + position  →  (B, T, D)
             ↓
        N × TransformerBlock          →  (B, T, D)   [each block: LN+Attn+res, LN+FFN+res]
             ↓
        Final LayerNorm               →  (B, T, D)
             ↓
        LM head Linear(D → V)         →  (B, T, V)   [logits over vocabulary]

    Weight tying:
        The LM head weight is shared with the token embedding matrix.
        This is standard practice in language models (Press &amp;amp; Wolf 2017).
        It saves V × D parameters and often improves perplexity because
        the model uses the same geometric space for both input and output.
    &quot;&quot;&quot;

    def __init__(
        self,
        vocab_size : int   = 50257,
        d_model    : int   = 768,
        n_heads    : int   = 12,
        n_layers   : int   = 12,
        seq_len    : int   = 1024,
        dropout    : float = 0.1,
    ):
        super().__init__()

        self.seq_len  = seq_len
        self.d_model  = d_model

        # ── embedding layer ───────────────────────────────────────────────────
        # Maps (B, T) integer ids to (B, T, D) float vectors.
        self.embeddings = Embeddings(vocab_size, d_model, seq_len, dropout)

        # ── N transformer blocks ──────────────────────────────────────────────
        # nn.ModuleList ensures PyTorch tracks all parameters.
        self.blocks = nn.ModuleList([
            TransformerBlock(d_model, n_heads, seq_len, dropout)
            for _ in range(n_layers)
        ])

        # ── final layer norm ──────────────────────────────────────────────────
        # Applied after all blocks before the language model head.
        # Stabilises the magnitude of hidden states before the final projection.
        self.ln_final = nn.LayerNorm(d_model)

        # ── language model head ───────────────────────────────────────────────
        # Projects each token&apos;s D-dimensional state to a distribution over V.
        #   input  (B, T, D)  →  output (B, T, V)
        # bias=False is conventional when using weight tying.
        self.lm_head = nn.Linear(d_model, vocab_size, bias=False)

        # ── weight tying ──────────────────────────────────────────────────────
        # Share the lm_head weights with the token embedding weights.
        # After this, lm_head.weight IS embeddings.token_emb.weight —
        # they are the same tensor in memory.
        self.lm_head.weight = self.embeddings.token_emb.weight

        # ── weight initialisation ─────────────────────────────────────────────
        self._init_weights()

    # ──────────────────────────────────────────────────────────────────────────

    def _init_weights(self):
        &quot;&quot;&quot;
        Standard GPT-2 initialisation:
          - Linear and Embedding layers: N(0, 0.02)
          - LayerNorm: weight=1, bias=0
          - Residual projections scaled down by 1/sqrt(2 * n_layers)
            to prevent the residual stream from growing in magnitude
            with depth.
        &quot;&quot;&quot;
        n_layers = len(self.blocks)

        for name, module in self.named_modules():
            if isinstance(module, nn.Linear):
                nn.init.normal_(module.weight, mean=0.0, std=0.02)
                if module.bias is not None:
                    nn.init.zeros_(module.bias)

            elif isinstance(module, nn.Embedding):
                nn.init.normal_(module.weight, mean=0.0, std=0.02)

            elif isinstance(module, nn.LayerNorm):
                nn.init.ones_(module.weight)
                nn.init.zeros_(module.bias)

        # Scale output projections of attention and FFN blocks
        # (the layers that feed directly into residual connections)
        for block in self.blocks:
            nn.init.normal_(
                block.attn.out_proj.weight,
                mean=0.0,
                std=0.02 / math.sqrt(2 * n_layers)
            )
            nn.init.normal_(
                block.ffn.fc2.weight,
                mean=0.0,
                std=0.02 / math.sqrt(2 * n_layers)
            )

    # ──────────────────────────────────────────────────────────────────────────

    def forward(
        self,
        idx     : torch.Tensor,             # (B, T)  integer token IDs
        targets : torch.Tensor | None = None  # (B, T)  integer token IDs (optional)
    ):
        &quot;&quot;&quot;
        Forward pass.

        idx     : (B, T)  — input token indices
        targets : (B, T)  — next-token targets for computing loss (optional)

        Returns:
            logits : (B, T, V)
            loss   : scalar (cross-entropy) if targets provided, else None
        &quot;&quot;&quot;
        B, T = idx.shape
        assert T &amp;lt;= self.seq_len, (
            f&quot;Sequence length {T} exceeds maximum {self.seq_len}&quot;
        )

        # ── 1. embeddings ─────────────────────────────────────────────────────
        #   idx : (B, T)
        #   x   : (B, T, D)
        x = self.embeddings(idx)              # (B, T, D)

        # ── 2. transformer blocks ─────────────────────────────────────────────
        # Each block: (B, T, D) → (B, T, D)
        # Residual connections inside each block mean x is incrementally refined.
        for block in self.blocks:
            x = block(x)                      # (B, T, D)

        # ── 3. final layer norm ───────────────────────────────────────────────
        #   (B, T, D) → (B, T, D)
        x = self.ln_final(x)                  # (B, T, D)

        # ── 4. language model head ────────────────────────────────────────────
        # Project each token&apos;s D-dimensional state to vocabulary logits.
        #   x      : (B, T, D)
        #   weight : (V, D)   (shared with token embedding, transposed in Linear)
        #   logits : (B, T, V)
        logits = self.lm_head(x)              # (B, T, V)

        # ── 5. loss (training only) ───────────────────────────────────────────
        # Cross-entropy between predicted logit distribution and target tokens.
        # We predict token t+1 from position t, so targets are shifted by 1
        # (that shift is handled in the Dataset, not here — targets[:,i] is
        # the token that should follow input[:,i]).
        loss = None
        if targets is not None:
            # Flatten (B, T, V) → (B*T, V) and (B, T) → (B*T,) for F.cross_entropy
            loss = F.cross_entropy(
                logits.view(-1, logits.size(-1)),   # (B*T, V)
                targets.view(-1),                   # (B*T,)
            )

        return logits, loss

    # ──────────────────────────────────────────────────────────────────────────

    @torch.no_grad()
    def generate(
        self,
        idx         : torch.Tensor,   # (B, T)  prompt token IDs
        max_new_tokens : int = 100,
        temperature : float = 1.0,    # &amp;gt;1 = more random, &amp;lt;1 = more focused
        top_k       : int   = 50,     # keep only top-k logits before sampling
    ) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        Autoregressive text generation.

        At each step:
          1. Run the model on the current context
          2. Take the logits at the LAST position only (the next-token prediction)
          3. Apply temperature scaling and top-k filtering
          4. Sample the next token
          5. Append it to the sequence and repeat

        Unlike training (which processes all T positions in parallel via
        teacher forcing), inference must be sequential because each new token
        depends on the previous one.
        &quot;&quot;&quot;
        self.eval()

        for _ in range(max_new_tokens):

            # Crop context to seq_len if it has grown too long
            idx_cond = idx if idx.size(1) &amp;lt;= self.seq_len else idx[:, -self.seq_len:]

            # Forward pass — we only care about the last position&apos;s logits
            #   logits : (B, T_cond, V)
            logits, _ = self(idx_cond)

            # Take logits at the final position: (B, V)
            logits = logits[:, -1, :] / temperature   # (B, V)

            # Top-k filtering: zero out all but the top-k logits
            if top_k is not None:
                # values shape: (B, k); indices not needed here
                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
                # Set everything below the k-th value to -inf
                logits[logits &amp;lt; v[:, [-1]]] = float(&apos;-inf&apos;)

            # Softmax → probability distribution over vocabulary
            probs = F.softmax(logits, dim=-1)          # (B, V)

            # Sample one token from the distribution
            idx_next = torch.multinomial(probs, num_samples=1)  # (B, 1)

            # Append to the running sequence
            idx = torch.cat([idx, idx_next], dim=1)    # (B, T+1)

        return idx   # (B, T + max_new_tokens)


# ─────────────────────────────────────────────────────────────────────────────
# 6.  PARAMETER COUNT HELPER
# ─────────────────────────────────────────────────────────────────────────────

def count_parameters(model: nn.Module) -&amp;gt; dict:
    &quot;&quot;&quot;
    Returns a breakdown of parameter counts by component.
    &quot;&quot;&quot;
    total = sum(p.numel() for p in model.parameters())
    breakdown = {
        name: sum(p.numel() for p in module.parameters())
        for name, module in model.named_children()
    }
    return {&quot;total&quot;: total, **breakdown}


# ─────────────────────────────────────────────────────────────────────────────
# 7.  QUICK SANITY CHECK
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == &quot;__main__&quot;:
    torch.manual_seed(42)

    # Instantiate GPT-2 small configuration
    model = TransformerLM(
        vocab_size = 50257,
        d_model    = 768,
        n_heads    = 12,
        n_layers   = 12,
        seq_len    = 1024,
        dropout    = 0.1,
    )

    # Dummy batch: 2 sequences of length 16
    B, T = 2, 16
    idx     = torch.randint(0, 50257, (B, T))   # (2, 16)
    targets = torch.randint(0, 50257, (B, T))   # (2, 16)

    logits, loss = model(idx, targets)

    print(&quot;── TransformerLM sanity check ──────────────────────────&quot;)
    print(f&quot;  Input shape    : {idx.shape}          (B, T)&quot;)
    print(f&quot;  Logits shape   : {logits.shape}   (B, T, V)&quot;)
    print(f&quot;  Loss           : {loss.item():.4f}&quot;)
    print()

    params = count_parameters(model)
    print(&quot;── Parameter counts ────────────────────────────────────&quot;)
    for k, v in params.items():
        print(f&quot;  {k:20s}: {v:&amp;gt;12,}&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 

</feed>
