267 lines
8.2 KiB
Markdown
267 lines
8.2 KiB
Markdown
# Python Environments
|
|
|
|
> Dependency hell is real. Virtual environments are the cure.
|
|
|
|
**Type:** Build
|
|
**Languages:** Shell
|
|
**Prerequisites:** Phase 0, Lesson 01
|
|
**Time:** ~30 minutes
|
|
|
|
## 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
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph without["Without virtual environments"]
|
|
SP[System Python] --> T24["torch 2.4.0 (CUDA 12.4)\nProject A needs this"]
|
|
SP --> T21["torch 2.1.0 (CUDA 11.8)\nProject B needs this"]
|
|
SP --> CONFLICT["CONFLICT: only one\ntorch version can exist"]
|
|
end
|
|
|
|
subgraph with["With virtual environments"]
|
|
PA["Project A (.venv/)"] --> PA1["torch 2.4.0 (CUDA 12.4)"]
|
|
PA --> PA2["transformers 4.44"]
|
|
PB["Project B (.venv/)"] --> PB1["torch 2.1.0 (CUDA 11.8)"]
|
|
PB --> PB2["diffusers 0.28"]
|
|
end
|
|
```
|
|
|
|
## Build It
|
|
|
|
### Option 1: uv venv (Recommended)
|
|
|
|
`uv` is the fastest Python package manager (10-100x faster than pip). It handles virtual environments, Python versions, and dependency resolution in one tool.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
uv pip install torch numpy
|
|
```
|
|
|
|
Create a project with `pyproject.toml` in one step:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```bash
|
|
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"
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```toml
|
|
[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:
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
which python # should show .venv/bin/python, not /usr/bin/python
|
|
which pip # should show .venv/bin/pip
|
|
```
|
|
|
|
### 2. Mixing pip and conda
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
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
|
|
|
|
## 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 |
|