TAI BUI
Back to curriculum
BuildPython

Compute: GPUs, Cloud & Containers

Find out what silicon you're actually running on, prove it's fast, then trap the whole setup in a box so nobody, including future you, has to guess again.

TIME: ~100 minutesPREREQUISITES: Phase 0, Lesson 01

Learning Objectives

  • Verify local GPU access with `nvidia-smi` and PyTorch's CUDA API, or spin up a free T4 on Google Colab if you don't have one
  • Benchmark matrix multiplication on CPU vs GPU and estimate the largest model your VRAM can hold
  • Build a GPU-enabled Docker image with CUDA, PyTorch, and the NVIDIA Container Toolkit so the setup is reproducible
  • Mount volumes for models and datasets, then orchestrate a multi-service AI stack (inference server + vector database) with Docker Compose

The Problem

Most lessons up to this point run fine on CPU. The moment you start training CNNs, transformers, or LLMs (phase 4 and up), CPU stops being an option. A training run that takes 8 hours on CPU takes 10 minutes on GPU. Here's why: a GPU packs thousands of small cores built to do the same simple operation on many numbers at once, which is exactly what matrix multiplication needs. A CPU has only a handful of cores, optimized for fast sequential logic rather than that kind of raw parallel throughput. You need to know, right now, what compute you actually have: none, a GPU bolted into your laptop, or one you rent by the hour.

That's half the problem. The other half shows up the first time you hand your code to someone else, or to a server. You trained on PyTorch 2.3, CUDA 12.4, Python 3.12. Your colleague has PyTorch 2.1, CUDA 11.8, Python 3.10. Your model crashes on their machine — not because the code is wrong, but because the world underneath it is different. AI stacks are dependency nightmares: Python, PyTorch, CUDA drivers, cuDNN, system-level C libraries, and packages like flash-attn that need an exact compiler version to even build. Misalign any one of those and you lose an afternoon to a stack trace that has nothing to do with your model.

Fix both problems in order: figure out your compute first, then trap the whole environment around it in something portable.

Warm-up0/3
1. Why is a GPU faster than a CPU for training neural networks?Warm-up
Two horizontal bars for one neural network training run: the CPU bar is long and labelled 8 hours (480 min), the GPU bar is a sliver labelled 10 minutes.
Same run, two chips. Bars are to scale.
2. Your code runs fine on your machine but crashes on a colleague's machine, even though the code itself has no bugs. What's the most likely cause?Warm-up
Two terminals, each running md5sum train.py with the same checksum. On the first machine python train.py prints three epoch lines and exits with 0. On the second it prints a Traceback and exits with 1.
Identical file, different outcome.
3. A script needs a different version of a library than another project on the same machine. What is the usual fix?Warm-up
Two requirements files side by side: projA needs pandas 1.5.3 and numpy below 2, projB needs pandas 2.2.0 and numpy 1.26 or newer. A banner says both run on one laptop with one Python and one shared install location currently holding pandas 2.2.0.
Two projects, two incompatible pins, one machine.

The Concept

Three ways to get a GPU

1. Local NVIDIA GPU
   Cost: $0 (you already have it)
   Setup: Install CUDA + cuDNN
   Best for: regular use, large datasets

2. Google Colab (free tier)
   Cost: $0
   Setup: none
   Best for: quick experiments, no GPU at home

3. Cloud GPU (Lambda, RunPod, Vast.ai)
   Cost: $0.20-2.00/hr
   Setup: SSH + install
   Best for: serious training, large models

Pick based on what you're actually doing, not what sounds impressive. A Colab T4 covers almost everything in phases 1-3. Save the paid cloud rental for training runs that would otherwise tie up your laptop for a day.

Once the GPU is worth keeping, containerize around it

A container wraps your code, runtime, libraries, and system tools into one isolated unit. I think of it as a lightweight VM that cheats: it shares the host OS kernel instead of running its own, so it starts in seconds instead of minutes.

Mermaid diagram

This matters more for AI work than for typical software, for three reasons:

  1. GPU drivers are fragile. CUDA 12.4 code does not run on CUDA 11.8. Docker isolates the CUDA toolkit inside the container while sharing the host GPU driver through the NVIDIA Container Toolkit.
  2. Model weights are large. A 7B parameter model is 14 GB in fp16. You do not want to re-download it every time you rebuild, so volumes solve exactly this.
  3. Real AI apps are multi-service. An inference server, a vector database (a database built for similarity search; you'll meet retrieval-augmented generation, or RAG, properly much later in the course), maybe a web frontend. Docker Compose starts all of it with one command.
TermWhat it means
ImageA read-only template. Your recipe. Built from a Dockerfile.
ContainerA running instance of an image. Your kitchen.
DockerfileInstructions to build an image, layer by layer.
VolumePersistent storage that survives container restarts.
docker-composeA tool for defining multi-container applications in YAML.

You'll actually build three container shapes, roughly in the order you'll reach for them:

Dev Container
  Full toolkit. Editor support. Jupyter. Debugging tools.
  Used during development and experimentation.

Training Container
  Minimal. Just the training script and dependencies.
  Runs on GPU clusters. No editor, no Jupyter.

Inference Container
  Optimized for serving. Small image. Fast cold start.
  Runs behind a load balancer (spreads incoming requests across
  multiple copies of your service) in production.
Check: The Concept0/13
1. What is the primary difference between a Docker container and a virtual machine?Define
Two layered diagrams on identical hardware and host OS. Left: three Docker containers, each holding app and libraries, on a Docker Engine. Right: three virtual machines on a hypervisor, each holding app and libraries.
The same three apps, packaged two ways.
2. What is the difference between a Docker image and a container?Define
Terminal: docker build -t ai-dev, then docker images listing one ai-dev image, then two docker run commands naming t1 and t2 from ai-dev, then docker ps listing both t1 and t2 with image ai-dev.
One thing built once, two things running.
3. Which container shape is minimal, with no editor or Jupyter, and meant to run on GPU clusters?Define
A job scheduler box sends arrows to four GPU nodes with 8 GPUs each. Every node runs one container whose only command is python train.py --epochs 90. A note says nobody logs in and there is no screen or browser.
A GPU cluster running unattended batch jobs.
4. What is a Tensor Core?Define
A spec sheet table for an RTX 4090 listing CUDA cores 16,384, Tensor Cores 512, RT cores 128 and VRAM 24 GB.
One row of the table is the unit in question.
5. What is fp16?Define
The number 0.1 as bit patterns: a row of 16 cells labelled fp16 split into 1, 5 and 10 bit fields, and a row of 32 cells labelled fp32 split into 1, 8 and 23 bit fields, each with its stored value.
Bit patterns computed with numpy.
6. Which of the three ways to get a GPU costs nothing and needs no setup, making it the best fit for a quick experiment?Explain
Two cards: a job description for a quick experiment (train a small MLP on MNIST for about 10 minutes, budget 0 dollars, no setup time), and the three kinds of GPU access the lesson lists described in plain words with their costs: 0, 0, and 0.20 to 2.00 dollars per hour.
The job, and the places a GPU can come from.
7. Which workload justifies paying for a cloud GPU rental over a free Colab T4?Explain
A table of what renting a cloud GPU costs at 0.20 to 2.00 dollars per hour: 2 hours costs 0.40 to 4.00, 8 hours 1.60 to 16.00, 24 hours 4.80 to 48.00, 72 hours 14.40 to 144.00. Below, a card for the free Colab T4: 0 dollars, no setup.
Rental cost grows with hours. Colab costs nothing.
8. Why does the 2-bytes-per-parameter rule overstate what you can actually train on a card?Explain
A bar equal to the full 24 GB of a GPU filled by the weights of a 12 billion parameter fp16 model (12 billion times 2 bytes is 24 GB), with a dashed question-mark box beyond its right edge and a note asking what else must live in VRAM during training.
The rule of thumb counts weights only.
9. How does Docker avoid CUDA version conflicts between projects?Explain
Two cards: Project A built for CUDA 11.8 with torch 2.1.0+cu118, and Project B built for CUDA 12.4 with torch 2.3.1+cu124, above a banner saying both are used every week on one workstation with one NVIDIA GPU, and a question about how both can coexist.
Two projects, two CUDA versions, one GPU.
10. Using the fp16 rule of thumb, approximately how many parameters can fit in 24 GB of VRAM?Apply
An empty bar for a GPU's 24 GB of VRAM (24,000,000,000 bytes) and a single model weight stored in fp16 shown as 1 value equals 2 bytes, with a note to count only the weights.
The card and the size of one weight.
11. A teammate fine-tunes a 12B-parameter model in fp16 on a 24 GB card because the weights fit exactly. An hour in, it crashes out of memory. What was the wrong assumption?Analyze
A bar showing 12 billion fp16 parameters as 24 GB of weights filling a 24 GB card, and a terminal of a finetune.py run that logs steps 1, 200 and 900 then ends with torch.cuda.OutOfMemoryError. A note says this happened about one hour after the start.
The weights fit. The run still crashed.
12. You must fine-tune a 7B model on a 24 GB card. Given 2 bytes per parameter and 3-4x training headroom, what is the right call?Design
Two bars: a 24 GB card and the 14 GB of fp16 weights of a 7 billion parameter model (7 billion times 2 bytes). Beneath them a note that real training needs 3 to 4 times the weights, and a question mark for the total the run needs.
Bars to scale.
13. You must ship an inference service behind a load balancer. Which container shape fits?Design
A load balancer receiving requests and spreading them to three replica containers on port 8000, with a dashed box on the right for starting a fourth replica during a traffic spike. A note says copies come and go as traffic rises and falls.
Replicas are added and removed on demand.

Build It

Step 1: Check what GPU you actually have

bash
nvidia-smi

Then confirm PyTorch sees it:

python
import torch
 
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"CUDA version: {torch.version.cuda}")
if torch.cuda.is_available():
    print(f"GPU: {torch.cuda.get_device_name(0)}")
    print(f"Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")

No local GPU? Two more doors:

Google Colab. Go to colab.research.google.com, Runtime > Change runtime type > T4 GPU, then run !nvidia-smi to verify. Upload notebooks from this course directly to Colab.

Cloud GPU (Lambda, RunPod, Vast.ai).

bash
ssh user@your-gpu-instance
 
pip install torch torchvision torchaudio
python -c "import torch; print(torch.cuda.get_device_name(0))"

And if you have none of the above: most lessons work on CPU. The ones that need GPU say so and include a Colab link. Write device-agnostic code from day one:

python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using: {device}")

Step 2: Prove the speedup, don't take it on faith

One thing to know before you read the code below: GPU work is asynchronous. Python queues the operation and moves on immediately, while the GPU finishes the actual computation in the background. That's why the benchmark calls torch.cuda.synchronize() right before it starts the clock and right after it stops: without that wait, you'd be timing how fast Python can queue instructions, not how fast the GPU runs them.

python
import torch
import time
 
size = 5000
 
a_cpu = torch.randn(size, size)
b_cpu = torch.randn(size, size)
 
start = time.time()
c_cpu = a_cpu @ b_cpu
cpu_time = time.time() - start
print(f"CPU: {cpu_time:.3f}s")
 
if torch.cuda.is_available():
    a_gpu = a_cpu.to("cuda")
    b_gpu = b_cpu.to("cuda")
 
    torch.cuda.synchronize()
    start = time.time()
    c_gpu = a_gpu @ b_gpu
    torch.cuda.synchronize()
    gpu_time = time.time() - start
    print(f"GPU: {gpu_time:.3f}s")
    print(f"Speedup: {cpu_time / gpu_time:.0f}x")

Run it a few times. The exact speedup varies with your GPU, but on anything from the last several years you should see 20-100x for a 5000x5000 multiply, not a modest improvement.

Step 3: Install Docker

bash
# macOS
brew install --cask docker
open /Applications/Docker.app
 
# Ubuntu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group change to take effect

Verify:

bash
docker --version
docker run hello-world

Step 4: Install the NVIDIA Container Toolkit (Linux with an NVIDIA GPU)

This is what lets a container reach through to your GPU. macOS users, and Windows users on WSL2 (Windows's Linux compatibility layer), can skip this: Docker Desktop handles GPU passthrough differently there.

bash
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
 
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Test it:

bash
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi

If you see your GPU info, the toolkit is working.

Step 5: Pick the right base image

This single choice saves or costs you hours of debugging later.

nvidia/cuda:12.4.1-devel-ubuntu22.04
  Full CUDA toolkit. Compilers included.
  Use for: building packages that need nvcc (flash-attn, bitsandbytes)
  Size: ~4 GB

nvidia/cuda:12.4.1-runtime-ubuntu22.04
  CUDA runtime only. No compilers.
  Use for: running pre-built code
  Size: ~1.5 GB

pytorch/pytorch:2.3.1-cuda12.4-cudnn9-runtime
  PyTorch pre-installed on top of CUDA.
  Use for: skipping the PyTorch install step
  Size: ~6 GB

python:3.12-slim
  No CUDA. CPU only.
  Use for: inference on CPU, lightweight tools
  Size: ~150 MB

Step 6: Write the Dockerfile

code/Dockerfile:

dockerfile
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04
 
# noninteractive stops apt from pausing mid-build to ask questions (like a timezone prompt)
# that nobody is there to answer during an automated build
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
 
# rm -rf /var/lib/apt/lists/* below clears apt's package index cache after install;
# you don't need it, and leaving it in just makes the image bigger
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3.12 \
    python3.12-venv \
    python3.12-dev \
    python3-pip \
    git \
    curl \
    build-essential \
    && rm -rf /var/lib/apt/lists/*
 
# Ubuntu ships several Python versions side by side; this points the plain `python`
# command at 3.12 specifically
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1
 
RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel
 
RUN python -m pip install --no-cache-dir \
    torch==2.3.1 \
    torchvision==0.18.1 \
    torchaudio==2.3.1 \
    --index-url https://download.pytorch.org/whl/cu124
 
RUN python -m pip install --no-cache-dir \
    numpy \
    pandas \
    scikit-learn \
    matplotlib \
    jupyter \
    transformers \
    datasets \
    accelerate \
    safetensors
 
WORKDIR /workspace
 
VOLUME ["/workspace", "/models"]
 
EXPOSE 8888
 
CMD ["python"]

Build it:

bash
docker build -t ai-dev -f phases/00-setup-and-tooling/03-compute-gpus-cloud-and-containers/code/Dockerfile .

The first build takes a while because it's downloading the CUDA base image and PyTorch. Later builds reuse cached layers and are fast.

Run it:

bash
docker run --rm -it --gpus all \
    -v $(pwd):/workspace \
    -v ~/models:/models \
    ai-dev python -c "import torch; print(f'PyTorch {torch.__version__}, CUDA: {torch.cuda.is_available()}')"

Or run Jupyter inside it:

bash
docker run --rm -it --gpus all \
    -v $(pwd):/workspace \
    -v ~/models:/models \
    -p 8888:8888 \
    ai-dev jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root

Step 7: Mount volumes so nothing gets re-downloaded

Without a volume mount, everything inside a container disappears when it stops, including a model checkpoint you just spent an hour downloading.

bash
# Mount your code
-v $(pwd):/workspace
 
# Mount a shared models directory
-v ~/models:/models
 
# Mount datasets
-v ~/datasets:/data

Load from the mounted path in your training script:

python
from transformers import AutoModel
 
model = AutoModel.from_pretrained("/models/llama-7b")

The model lives on your host filesystem. Rebuild the container as often as you like without re-downloading anything.

Step 8: Docker Compose for multi-service AI apps

A real RAG app is more than one script: at minimum, an inference server plus a vector database. code/docker-compose.yml runs both with one command.

If YAML is new to you: it expresses nested data through indentation instead of brackets, key: value pairs hold settings, and a leading - marks a list item, like the ports: list a few lines down. Indentation is meaningful, so match it exactly, or the file quietly means something you didn't intend.

yaml
services:
  ai-dev:
    build:
      context: .
      dockerfile: Dockerfile
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    volumes:
      - ../../../:/workspace
      - ~/models:/models
      - ~/datasets:/data
    ports:
      - "8888:8888"
    stdin_open: true
    tty: true
    command: jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root
 
  qdrant:
    image: qdrant/qdrant:v1.12.5
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage
 
volumes:
  qdrant_data:

The deploy.resources.reservations.devices block under ai-dev is how you tell Compose "this container needs GPU access." It's doing the same job as the --gpus all flag you passed to plain docker run earlier, just expressed in YAML.

Start everything:

bash
cd phases/00-setup-and-tooling/03-compute-gpus-cloud-and-containers/code
docker compose up -d

Your ai-dev container can now reach the vector database at http://qdrant:6333 by service name. Compose creates a shared network automatically, so no manual networking is required:

python
from qdrant_client import QdrantClient
 
client = QdrantClient(host="qdrant", port=6333)
print(client.get_collections())

Stop everything:

bash
docker compose down

Add -v to also delete the qdrant volume:

bash
docker compose down -v

Step 9: Commands you'll actually reach for

bash
# List running containers
docker ps
 
# List all images and their sizes
docker images
 
# Remove unused images (reclaim disk space)
docker system prune -a
 
# Check GPU usage inside a running container
docker exec -it <container_id> nvidia-smi
 
# Copy a file from container to host
docker cp <container_id>:/workspace/results.csv ./results.csv
 
# View container logs
docker logs -f <container_id>
Check: Build It0/24
1. What does VRAM refer to?Define
Terminal on a desktop with one NVIDIA card: grep MemTotal shows 32781216 kB, and nvidia-smi query shows the card name NVIDIA GeForce RTX 4090 with memory.total 24564 MiB. A note says two memory totals show up, about 31.3 GiB and 24.0 GiB.
Two memory totals from one machine.
2. What is CUDA?Define
Terminal on a deep learning Ubuntu box: nvcc --version printing Cuda compilation tools release 12.4, torch.version.cuda printing 12.4, and ls /usr/local showing cuda and cuda-12.4 folders.
The same name shows up in a compiler, in PyTorch and in a folder.
3. What command verifies that your NVIDIA GPU is detected and shows its current status?Define
A card describing a new Ubuntu machine with a freshly installed NVIDIA driver, listing what you want to see in one screen: GPU model, driver version, memory used and total, temperature, utilization and processes. Below it an empty terminal prompt.
What do you type?
4. In a Dockerfile, what does the FROM image determine?Define
Two Dockerfiles with identical last three lines (WORKDIR /workspace, COPY train.py, CMD python train.py). The first line is FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 in Dockerfile A and FROM python:3.12-slim in Dockerfile B, both highlighted.
Same recipe, different first line.
5. Why do later `docker build` runs finish faster than the first one?Define
The six steps of the lesson's Dockerfile, then a terminal running the same docker build -t ai-dev . command twice: the first build downloads the CUDA base image and PyTorch and takes a while, a later build in the same folder with no edits is fast.
Same command, same files, two very different waits.
6. What is a volume mount?Define
Terminal running docker run with -v ~/models:/models, and two boxes: the host disk with /home/tai/models/llama-7b holding 14 GB of weights, and the inside of ai-dev showing /models/ with unknown contents, joined by a dashed line labelled -v.
One -v argument, two paths.
7. What does the NVIDIA Container Toolkit actually do?Define
Terminal on an Ubuntu host: apt-get install nvidia-container-toolkit, nvidia-ctk runtime configure --runtime=docker, systemctl restart docker, then cat /etc/docker/daemon.json showing a runtimes entry named nvidia with path nvidia-container-runtime.
The install steps and the config file they change.
8. You benchmark a matrix multiply and forget to call `torch.cuda.synchronize()` before reading the elapsed time. What does the number you get actually measure?Explain
Two code panels. Your benchmark: start = time.time(), c_gpu = a_gpu @ b_gpu, gpu_time = time.time() - start, print, with no torch.cuda.synchronize calls. The lesson's version for comparison has torch.cuda.synchronize() before start and again right after the matrix multiply.
Same benchmark, with and without the two synchronize calls.
9. In `docker-compose.yml`, why can the `ai-dev` service reach the `qdrant` service by name with no manual network configuration?Explain
An excerpt of docker-compose.yml with services ai-dev and qdrant (port 6333) and a qdrant_data volume, beside a Python snippet in ai-dev calling QdrantClient with host qdrant and port 6333. A note says the file has no networks key, no custom network and no IP address.
The excerpt has no networking settings at all.
10. Why are volume mounts critical for AI development with Docker?Explain
Terminal: du -sh ~/models reporting 14G, then a docker run --rm -it --gpus all command with three -v mounts (current folder to /workspace, ~/models to /models, ~/datasets to /data) running python train.py. Notes say the weights took 25 minutes to download and containers are thrown away several times a day.
A run command with three mounts.
11. What does the `deploy.resources.reservations.devices` block do for `ai-dev` in Compose?Explain
Lines 2 to 12 of docker-compose.yml with the deploy, resources, reservations and devices block (driver nvidia, count all, capabilities gpu) highlighted under the ai-dev service. A note says qdrant has no such block.
Highlighted: lines 6 to 12 of the lesson's compose file.
12. On a machine with no NVIDIA GPU, what does `print(f"Using: {device}")` show after `device = torch.device("cuda" if torch.cuda.is_available() else "cpu")`?Apply
A three-line script device_check.py: import torch, device equals torch.device cuda if torch.cuda.is_available() else cpu, and a print of Using device. Below, a terminal where nvidia-smi reports command not found and python device_check.py has not printed anything yet.
The script and the machine it runs on.
13. A benchmark prints `CPU: 3.000s` and `GPU: 0.060s`. What does the speedup line print?Apply
The last three print lines of benchmark.py (CPU time, GPU time, and Speedup as cpu_time divided by gpu_time formatted with .0f then x), and a terminal showing CPU: 3.000s, GPU: 0.060s and an unfinished line Speedup: ?.
The format spec .0f rounds to a whole number.
14. You need to build `flash-attn` from source inside your container, which requires `nvcc`. Which base image should you start from?Apply
A Dockerfile whose first line is FROM with question marks, followed by ENV, a pip install of torch 2.3.1 and a pip install flash-attn --no-build-isolation. Below, a terminal comment says the build step needs nvcc, the CUDA compiler, and a note says flash-attn is compiled from source during docker build.
The recipe is waiting for a base image.
15. The Dockerfile ends with `CMD ["python"]`. What starts if you run `docker run --rm -it --gpus all ai-dev` with no command?Apply
The last seven lines of the Dockerfile (WORKDIR /workspace, VOLUME, EXPOSE 8888, CMD python) and below it the command docker run --rm -it --gpus all ai-dev with no further arguments and no output yet.
Lines 37 to 43 of the lesson's Dockerfile.
16. Which Dockerfile line makes the plain `python` command run Python 3.12?Apply
Terminal in the half-built image: /usr/bin/python3.12 exists, /usr/bin/python does not, and python --version gives command not found. A goal card says plain python must run Python 3.12 and that python3.12 is already installed by an earlier line.
python3.12 exists. python does not.
17. With `-v ~/models:/models`, where must the files be for `AutoModel.from_pretrained("/models/llama-7b")` to find them?Apply
A docker run command with two mounts (current folder to /workspace and ~/models to /models) running load.py, which calls AutoModel.from_pretrained on /models/llama-7b. Below, two folder lists: the host with ~/ai-course/, ~/models/ and ~/datasets/, and the container with /workspace/, /models/ and /data/.
The current folder is ~/ai-course.
18. After `docker compose down -v`, which data is deleted?Apply
Terminal inventory before a command: docker images shows the ai-dev image, docker volume ls shows code_qdrant_data, ls shows ~/models containing llama-7b and ~/ai-course containing README.md, phases and train.py. Below it the command docker compose down -v about to run.
What exists just before docker compose down -v.
19. From inside the `ai-dev` container, which address reaches the vector database?Apply
The docker-compose.yml excerpt with services ai-dev and qdrant (port 6333) and the qdrant_data volume, and a shell inside ai-dev where a curl command has a blank host in front of :6333/collections. A box asks which address reaches the vector database.
The names in the file, and a command with the host left blank.
20. nvidia-smi works on the host, but inside `ai-dev` `torch.cuda.is_available()` prints False. The run command was `docker run --rm -it -v $(pwd):/workspace ai-dev python ...`. What is missing?Analyze
Host terminal: nvidia-smi query shows NVIDIA GeForce RTX 4090 with driver 550.54.14. Second terminal on the same machine: docker run --rm -it -v $(pwd):/workspace ai-dev running torch.cuda.is_available(), which prints False.
Host sees the GPU, the container does not.
21. Every rebuild re-downloads a 14 GB model even though the script loads from `/models/llama-7b`. The run command has only `-v $(pwd):/workspace`. What is missing?Analyze
Terminal with the single run command docker run --rm -it -v $(pwd):/workspace ai-dev python infer.py, and three rebuild bars each labelled downloads 14 GB again, totalling 42 GB for one 14 GB model.
Three rebuilds, three full downloads.
22. Your image only runs pre-built code but is about 4 GB. Which change fits best?Analyze
A Dockerfile starting FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 and ending CMD python serve.py, a card giving the image size as about 4 GB, and a card saying the image only runs serve.py with pip-installed wheels and never calls nvcc or gcc.
A 4 GB image that only runs.
23. `torch.cuda.is_available()` returns False on the bare host. A teammate wraps the setup in a Dockerfile. What will the container give them?Analyze
A host terminal where torch.cuda.is_available() prints False, a teammate's three-line Dockerfile (FROM nvidia/cuda:12.4.1-devel-ubuntu22.04, pip install torch 2.3.1, CMD python), and the command docker build -t ai-dev.
The host check fails before the Dockerfile exists.
24. You add a second stateful service, such as another database, to the Compose file. Its data must survive `docker compose down`. Which design follows the lesson's pattern?Design
Today's docker-compose.yml excerpt with the qdrant service, its qdrant_data volume mapped to /qdrant/storage and a top-level volumes section, and a new postgres:16 service with port 5432 and a comment asking where /var/lib/postgresql/data lives. A note says rows must survive docker compose down.
The existing service, and the one being added.

Use It

You now have a reproducible AI development environment, with a proven GPU story underneath it. For the rest of this course:

  • Use docker compose up to start your dev environment and vector database together.
  • Mount code, models, and data as volumes so nothing is lost between rebuilds.
  • When a lesson needs a new Python package, add it to the Dockerfile and rebuild. Don't pip install inside a running container and forget you did it.
  • Share your Dockerfile with teammates. They get the exact same environment, not an approximation of it.

No GPU?

Drop the --gpus all flag and the NVIDIA deploy block from Compose. The container still works for CPU-based lessons: PyTorch detects the absence of CUDA and falls back to CPU automatically, and the same device = torch.device(...) line from Step 1 handles it either way.

Check: Use It0/3
1. Why add a new package to the Dockerfile and rebuild instead of running `pip install` in a live container?Explain
Two code panels. Way 1: add flask to the Dockerfile's pip install list and run docker build -t ai-dev. Way 2: run docker exec -it <id> pip install flask in the running container with no Dockerfile change and no rebuild. A note says the repo is shared with teammates.
Two ways to get flask into ai-dev.
2. You're running the Dockerfile and Compose stack from this lesson, but you don't have a GPU. What should you change to still use the same setup for CPU-based lessons?Apply
A laptop terminal where lspci shows only an Intel UHD Graphics controller, followed by the lesson's docker run command with --gpus all, and the ai-dev part of docker-compose.yml with its deploy and devices block. A note says the next lessons are CPU-based.
The lesson's GPU setup on a machine without a GPU.
3. Last week you ran `pip install flask` in a running container. After a fresh start flask is gone, and a teammate never had it. What should you have done?Analyze
Two terminals. Last week: docker exec -it ai-dev pip install flask succeeds and import flask works. Today after docker compose down and up: import flask raises ModuleNotFoundError: No module named flask. A note says a teammate who cloned the repo never had flask.
pip output taken from a real pip install flask.
Author's Note

The fp16 parameter math is the one calculation from this lesson worth memorizing rather than looking up. A model's weights are just numbers, and how much space each one takes depends on the precision format you store them in: fp16 uses 2 bytes per number, fp32 uses 4, so total memory is roughly parameters times bytes per parameter. At 2 bytes per parameter, a 24 GB card holds roughly 12 billion parameters, and that's before a single activation, gradient, or optimizer state gets loaded, which is why real training needs 3-4x that headroom. I've watched people learn this the expensive way, an hour into a run they never should have attempted on that card. Do the arithmetic before you queue the job, not after the OOM (out-of-memory crash).

The same discipline applies to the order of these two topics. Don't reach for Docker before you've confirmed the GPU story works bare-metal: nvidia-smi and torch.cuda.is_available() both returning what you expect, on the host, no container involved. A Dockerfile that wraps a broken setup just gives you a portable, shareable version of the same problem.

Exercises

  1. Run the CPU vs GPU benchmark and compare times. No local GPU? Run it on Google Colab instead.
  2. Check how much GPU memory you have and estimate the largest model you could fit, using the fp16 rule of thumb (2 bytes per parameter).
  3. Build the Dockerfile and run python -c "import torch; print(torch.__version__)" inside the container.
  4. Start the docker-compose stack and verify Qdrant is reachable from the AI container at http://qdrant:6333/collections.
  5. Add flask to the Dockerfile, rebuild, and run a simple API server on port 5000. Map the port with -p 5000:5000.
  6. Measure the image size with docker images. Switch the base image from devel to runtime and compare sizes.

Key Terms

TermWhat people sayWhat it actually means
CUDA"GPU programming"NVIDIA's parallel computing platform that lets you run code on the GPU
VRAM"GPU memory"Video RAM on the GPU, separate from system RAM. Limits model size.
fp16"Half precision"16-bit floating point, uses half the memory of fp32 with minimal accuracy loss
Tensor Core"Fast matrix hardware"Specialized GPU cores for matrix multiplication, 4-8x faster than regular cores
Container"Lightweight VM"An isolated process using the host kernel, with its own filesystem and network
Image layer"Cached step"Each Dockerfile instruction creates a layer. Unchanged layers are cached, so rebuilds are fast.
NVIDIA Container Toolkit"GPU in Docker"A runtime hook that exposes host GPUs to containers via the --gpus flag
Volume mount"Shared folder"A directory on the host mapped into the container. Changes persist after the container stops.
Base image"Starting point"The FROM image your Dockerfile builds on top of. Determines what is pre-installed.

Code