Phase 0 · Lesson 6 of 13

Python Environments

Dependency hell is real. Virtual environments are the cure.

Lesson 6 of 13Build~30 minutesShell

Before this: Lesson 1 of Phase 0

On this page

Learning Objectives

  • Create isolated virtual environments using uv, venv, or conda
  • Write a pyproject.toml with optional dependency groups and generate lockfiles for reproducibility
  • Diagnose and fix common pitfalls: global installs, pip/conda mixing, CUDA version mismatches
  • Implement a per-phase environment strategy for projects with conflicting dependencies

The Problem

You install PyTorch 2.4 for a fine-tuning project. Next week, a different project needs PyTorch 2.1 because its CUDA build is pinned. You upgrade globally, and the first project breaks. You downgrade, and the second one breaks.

This is dependency hell. It happens constantly in AI/ML work because:

  • PyTorch, JAX, and TensorFlow each ship their own CUDA bindings
  • Model libraries pin specific framework versions
  • A global pip install overwrites whatever was there before
  • CUDA 11.8 builds don't work with CUDA 12.x drivers (and vice versa)

The fix: every project gets its own isolated environment with its own packages.

The Concept

Diagram (opens full size)
s0-env-isolation

Build It

uv is the fastest Python package manager (10-100x faster than pip). It handles virtual environments, Python versions, and dependency resolution in one tool.

curl -LsSf https://astral.sh/uv/install.sh | sh

uv python install 3.12

cd your-project
uv venv
source .venv/bin/activate

Install packages:

uv pip install torch numpy

Create a project with pyproject.toml in one step:

uv init my-ai-project
cd my-ai-project
uv add torch numpy matplotlib

Option 2: venv (Built-in)

If you can't install uv, Python ships with venv:

python3 -m venv .venv
source .venv/bin/activate  # Linux/macOS
.venv\Scripts\activate     # Windows

pip install torch numpy

Slower than uv, but works everywhere Python is installed.

Option 3: conda (When You Need It)

Conda manages non-Python dependencies like CUDA toolkits, cuDNN, and C libraries. Use it when:

  • You need a specific CUDA toolkit version without installing it system-wide
  • You're on a shared cluster where you can't install system packages
  • A library's install instructions say "use conda"
# Install miniconda (not the full Anaconda)
curl -LsSf https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -o miniconda.sh
bash miniconda.sh -b

conda create -n myproject python=3.12
conda activate myproject

conda install pytorch torchvision torchaudio pytorch-cuda=12.4 -c pytorch -c nvidia

One rule: if you use conda for an environment, use conda for all packages in that environment. Mixing pip install into a conda env causes dependency conflicts that are painful to debug.

For This Course: Per-Phase Strategy

You could create one environment for the whole course. Don't. Different phases need different (sometimes conflicting) dependencies.

Strategy:

ai-engineering-from-scratch/
├── .venv/                    <-- shared lightweight env for phases 0-3
├── phases/
│   ├── 04-neural-networks/
│   │   └── .venv/            <-- PyTorch env
│   ├── 05-cnns/
│   │   └── .venv/            <-- same PyTorch env (symlink or shared)
│   ├── 08-transformers/
│   │   └── .venv/            <-- might need different transformer versions
│   └── 11-llm-apis/
│       └── .venv/            <-- API SDKs, no torch needed

The script in code/env_setup.sh creates the base environment for this course.

pyproject.toml Basics

Every Python project should have a pyproject.toml. It replaces setup.py, setup.cfg, and requirements.txt in one file.

[project]
name = "ai-engineering-from-scratch"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "numpy>=1.26",
    "matplotlib>=3.8",
    "jupyter>=1.0",
    "scikit-learn>=1.4",
]

[project.optional-dependencies]
torch = ["torch>=2.3", "torchvision>=0.18"]
llm = ["anthropic>=0.39", "openai>=1.50"]

Then install:

uv pip install -e ".[torch]"    # base + PyTorch
uv pip install -e ".[llm]"     # base + LLM SDKs
uv pip install -e ".[torch,llm]" # everything

Lockfiles

A lockfile pins every dependency (including transitive ones) to exact versions. This guarantees reproducibility: anyone who installs from the lockfile gets exactly the same packages.

# uv generates uv.lock automatically when using uv add
uv add numpy

# pip-tools approach
uv pip compile pyproject.toml -o requirements.lock
uv pip install -r requirements.lock

Commit your lockfile to git. When someone clones the repo, they install from the lockfile and get identical versions.

Common Mistakes

1. Installing globally

pip install torch  # BAD: installs to system Python

source .venv/bin/activate
pip install torch  # GOOD: installs to virtual environment

Check where your packages go:

which python       # should show .venv/bin/python, not /usr/bin/python
which pip           # should show .venv/bin/pip

2. Mixing pip and conda

conda create -n myenv python=3.12
conda activate myenv
conda install pytorch -c pytorch
pip install some-other-package   # BAD: can break conda's dependency tracking
conda install some-other-package # GOOD: let conda manage everything

If you must use pip inside conda (some packages are pip-only), install all conda packages first, then pip packages last.

3. Forgetting to activate

python train.py           # uses system Python, missing packages
source .venv/bin/activate
python train.py           # uses project Python, packages found

Your shell prompt should show the environment name:

(.venv) $ python train.py

4. Committing .venv to git

echo ".venv/" >> .gitignore

Virtual environments are 200MB-2GB. They're local, not portable between machines. Commit pyproject.toml and the lockfile instead.

5. CUDA version mismatch

nvidia-smi                # shows driver CUDA version (e.g., 12.4)
python -c "import torch; print(torch.version.cuda)"  # shows PyTorch CUDA version

# These must be compatible.
# PyTorch CUDA version must be <= driver CUDA version.

Use It

Run the setup script to create your course environment:

bash phases/00-setup-and-tooling/06-python-environments/code/env_setup.sh

This creates a .venv at the repo root with core dependencies installed and verified.

Exercises

  1. Run env_setup.sh and verify all checks pass
  2. Create a second virtual environment, install a different version of numpy in it, and confirm the two environments are isolated
  3. Write a pyproject.toml for a project that needs both PyTorch and the Anthropic SDK
  4. Deliberately install a package globally (without activating a venv), notice where it goes, then uninstall it

Hints

Exercise 1. Run bash phases/00-setup-and-tooling/06-python-environments/code/env_setup.sh from the repository root. Read each line of output; when a check fails, fix that one item and run the script again until no check fails.

Exercise 2. Make the second environment in a new folder with uv venv, activate it, and install an older numpy with a version pin such as numpy==1.26.4. Run python -c "import numpy; print(numpy.__version__)" in each environment; the two versions must be different.

Exercise 3. Start from the example in pyproject.toml Basics and list torch and anthropic under dependencies. Run uv pip install -e . or uv sync in a new environment; the file is correct when both packages import without an error.

Exercise 4. Deactivate every environment, run pip install --user <small package>, then run pip show <package> and read the Location line. Uninstall it with pip uninstall <package> and run pip show again to confirm that it is gone.

Key Terms

Term What people say What it actually means
Virtual environment "A venv" An isolated directory containing a Python interpreter and packages, separate from the system Python
Lockfile "Pinned dependencies" A file listing every package and its exact version, guaranteeing identical installs across machines
pyproject.toml "The new setup.py" The standard Python project configuration file, replacing setup.py/setup.cfg/requirements.txt
Transitive dependency "A dependency of a dependency" Package B depends on C; if you install A which depends on B, C is a transitive dependency of A
CUDA mismatch "My GPU isn't working" PyTorch was compiled for a different CUDA version than what your GPU driver supports

Check yourself

Quick quiz

  1. What problem do virtual environments solve?

    1. They provide a graphical interface for managing Python scripts
    2. They isolate project dependencies so different projects can use different package versions
    3. They automatically update packages to the latest versions
    4. They make Python code run faster by optimizing the interpreter
    Show the answer

    B. They isolate project dependencies so different projects can use different package versions Virtual environments give each project its own isolated set of packages. Without them, installing PyTorch 2.4 for one project would overwrite PyTorch 2.1 needed by another.

  2. What is a lockfile in the context of Python dependency management?

    1. A file that locks the Python interpreter version
    2. A file that encrypts your project dependencies
    3. A file that prevents other users from editing your code
    4. A file that pins every package to an exact version for reproducible installs
    Show the answer

    D. A file that pins every package to an exact version for reproducible installs A lockfile records the exact version of every package (including transitive dependencies) so anyone installing from it gets identical packages, ensuring reproducibility.

  3. How can you verify that your pip and python commands are using the virtual environment and not the system Python?

    1. Run 'python --check-env' to verify
    2. Run 'pip --version' and check the version number
    3. Run 'which python' and confirm it shows .venv/bin/python, not /usr/bin/python
    4. Check if the terminal background color has changed
    Show the answer

    C. Run 'which python' and confirm it shows .venv/bin/python, not /usr/bin/python 'which python' (or 'where python' on Windows) shows the full path to the interpreter. If it points to .venv/bin/python, you are in the virtual environment.

  4. Why is mixing pip and conda in the same environment problematic?

    1. Pip packages are incompatible with conda's Python interpreter
    2. Pip installs can break conda's dependency tracking, causing hard-to-debug conflicts
    3. It doubles the disk space used by every package
    4. Conda cannot install packages that pip has already installed
    Show the answer

    B. Pip installs can break conda's dependency tracking, causing hard-to-debug conflicts Conda maintains its own dependency solver. Pip installs bypass it, so conda no longer knows the true state of the environment. This leads to dependency conflicts that are painful to resolve.

  5. Your PyTorch code reports 'CUDA not available' despite having an NVIDIA GPU. What is the most likely cause?

    1. PyTorch was installed with a CUDA version incompatible with your GPU driver
    2. Your GPU does not support CUDA
    3. You forgot to import the torch.cuda module
    4. Virtual environments cannot access GPU hardware
    Show the answer

    A. PyTorch was installed with a CUDA version incompatible with your GPU driver PyTorch ships CUDA bindings compiled for specific CUDA versions. If the PyTorch CUDA version exceeds your driver's CUDA version, CUDA will not be available. Check with nvidia-smi and torch.version.cuda.

Questions people ask

Should I use venv, conda or uv for Python projects?

venv ships with Python and works for most projects. conda also manages non-Python libraries and is common in data science. uv is a newer, very fast tool that handles environments, installs and lock files. Pick one per project and stick with it.

Why does pip say a package is installed but Python cannot import it?

Usually pip and python point to different environments. Activate the right environment first, or run python -m pip install so the install goes to the same interpreter you run. In editors, check that the selected interpreter matches your project's environment.

Next lessonDocker for AIPrevious: Jupyter Notebooks

Lessons from AI Engineering from Scratch by Rohit Ghumare and contributors, MIT license. Notes for owners by Lozo. License

Next step

Rather have it built for you?

Reading is optional. Tell me what's stuck and I'll build it into the business you already run.

  • A real conversation, not a pitch
  • You keep your accounts
  • Nothing sends without your yes