In short
This guide answers two questions that keep coming back. What should you install today to do artificial intelligence in Python? And how do you then deliver what you built? We have been writing it since December 2018 and we keep it current. It is in daily use at the MAP5 applied mathematics lab and at several companies of different sizes, public and private, French and international.
It serves several readers, who do not need the same sections.
- You want a working machine tonight. Read section 1, then the recipe for your system in section 9.
- You are torn between a Mac, an NVIDIA card and the cloud. The table in section 3 and section 5 will point the way.
- You are looking for the right libraries. Section 4 sorts them by use.
- You have a tool that works and you want to ship it to a colleague, to a program or to an AI agent. Sections 7 and 8 are for you.
- You have a model in production to maintain. Section 10 is the advanced part.
What we install today
This section is for anyone who wants a working environment right away, without reading the rest. An environment is an isolated folder that holds one version of Python and its libraries. Each project gets its own, so installing a package for one project never breaks another.
You first need Miniconda, the lightweight version of Anaconda. It provides the conda command, which creates these environments. The install line depends on your system: take the one for your machine in section 9, then come back. If conda --version already answers, move on.
The project then needs three small files. The first, environment.yaml, does almost nothing: it pins the Python version and hands over to the second.
name: env4ml
channels:
- conda-forge
dependencies:
- python=3.13
- pip
- pip:
- -r requirements.txt
The second, requirements.txt, is the complete list of libraries. It is the only one.
numpy pandas scikit-learn matplotlib seaborn jupyterlab torch torchvision torchaudio
The third, requirements-dev.txt, adds what the tests need and never ships to users. Its first line pulls in the main list.
-r requirements.txt pytest ruff
Two commands then create the environment and activate it:
conda env create -f environment.yaml conda activate env4ml
You now have classical machine learning (scikit-learn), data tools (pandas, matplotlib, seaborn), notebooks (JupyterLab) and deep learning (PyTorch). On a recent Mac, PyTorch uses Apple's graphics chip with no extra setup. With an NVIDIA card, one detour is needed, described in section 9.
If you would rather install nothing at all, Google Colab opens a notebook in your browser, on Google's machines, graphics card included.
Before you install: four pieces of advice
- Never install system-wide. One environment per project spares you the version conflicts that take a day to untangle.
- Do not chase the newest Python. The version pinned in
environment.yamlis the one for which every library in section 4 publishes compiled packages. A newer version often waits a few months for everyone to catch up. - The graphics card comes last. It is the hardest part to install. If the processor is enough, ship on the processor, then add the card.
- For teaching, reproducibility beats speed. A slightly slow setup that the whole class can install is worth more than a fast one that breaks for a third of the students.
One list, four ways to read it
This section is for anyone who maintains a project over time, alone or in a team. It explains why the three files of section 1 are split this way. It also shows how uv and Docker plug into them without duplicating anything.
The principle is simple: the list of libraries exists in one place only, requirements.txt. Two lists always drift apart. The day a colleague installs from one and you install from the other, the same code gives two results. Nobody knows why. The comment we put at the top of every environment.yaml says it in one line: this file hands over to requirements.txt, so it never falls out of sync.
Four tools then read that same list.
- conda, through
environment.yaml, to create the environment and choose the Python version. - pip, directly:
pip install -r requirements.txt. - uv, an installer written in Rust by Astral that replaces
pipand runs much faster. Inside an activated conda environment, install it once withpip install uv. After that,uv pip install -r requirements.txtdoes the same job as pip. The list stays the same. - Docker, to reproduce the environment on another machine.
Docker and docker compose
Docker is for anyone who needs to run the project somewhere other than their laptop: a server, a compute machine, a client's computer. A container is a small, complete Linux machine described by a text file, the Dockerfile. It starts identically wherever Docker is installed.
The Dockerfile reads the same requirements.txt. It copies and installs that file before the rest of the code. Docker caches each step, so a change to the code does not rerun the library installation.
FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "-m", "my_project"]
One size detail, measured during our test. On Linux, the default PyTorch ships CUDA's libraries, even without an NVIDIA card: our image weighed 10.2 GB. For a server with no graphics card, one option in the Dockerfile alone fetches the processor-only PyTorch, and the image drops to 2.2 GB. The list itself does not change.
RUN pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt
docker compose describes, in a docker-compose.yml file, how to launch one or more containers: the open port, the folder shared with the machine, automatic restarts. A single command then does the job, docker compose up --build. In our repositories, compose launches the project's HTTP interface. Access for AI agents is mounted on that same application (see section 7).
services:
my-project:
build: .
ports:
- "8000:8000"
volumes:
- ./data:/data
restart: unless-stopped
Two limits are worth knowing before you put everything in a container, because they affect the graphics card.
- On a Mac, a container cannot reach Apple's graphics chip. Docker runs a Linux virtual machine there, which sees only the processor. To train on a Mac, stay in the conda environment.
- On Linux, an NVIDIA card requires the NVIDIA Container Toolkit, then the
--gpus alloption at launch. Without it, the container runs on the processor and says nothing.
Which setup for which situation
This section is for anyone who does not fit the general case of section 1. Find your row and follow it.
| Your situation | What we recommend | Why |
|---|---|---|
| Laptop with no graphics card, just starting out. | Section 1 as is, PyTorch on the processor. | The fewest moving parts; it runs everywhere. |
| Apple Silicon Mac (M series). | Section 1 as is. MLX for native experimentation. | Unified memory and silence, but short of breath on very large training runs. |
| You have an NVIDIA card. | The CUDA recipe in section 9. | The most proven path, at every scale. |
| You need a graphics card for a few hours. | Google Colab. | Machines with graphics cards, free or paid, nothing to install. |
| A Linux server with NVIDIA that you administer. | Ubuntu LTS, Miniconda and PyTorch with CUDA, or Docker with the NVIDIA Container Toolkit. | The same foundation as your laptop, with the power on top. |
| You teach a course. | Section 1 without a graphics card, Colab as a fallback. | Every student keeps up; speed comes second. |
The Python foundation for machine learning
This section is for anyone choosing libraries. It sorts them by use and says, for each one, when to reach for it.
Over a few decades, the field moved from Java to MATLAB, then to Python. The big tech companies settled there. Research followed with its funding and its tools. We recommend Python for that reason: it is where the papers, the models and the libraries come out.
The toolkits we recommend
- Computer vision. Five uses, from raw pixels to a hosted service:
- Classical vision: OpenCV. Image processing, geometric transforms, camera calibration, object tracking. For anything that needs no learning, it is still the right answer.
- Detection and segmentation: Detectron2, from Meta, when you want control over the architecture. To move faster, the YOLO line from Ultralytics (
pip install ultralytics) leads the ready-to-use field: detection, segmentation, pose, oriented boxes. AGPL-3.0 license, with a commercial license for closed use. - Annotation: CVAT, under the MIT license, is the open reference for labeling images and video: classes, boxes, segmentation, keypoints, text. With a coding agent beside you, you can set up an annotation pipeline without writing the plumbing yourself.
- Hosted platform: Roboflow covers the whole chain, training, deployment and serving, with Roboflow Universe as a public hub of models and datasets.
- Representations without labels: DINOv3, from Meta, learns excellent image representations with no annotation. Use it as a frozen layer to classify, search or segment when you have few labels.
- Classical machine learning. scikit-learn, the reference library to classify, predict a number, cluster and select a model. Stable interface, sensible defaults, remarkable documentation. Start there, unless your problem truly calls for deep learning.
- Deep learning. Today we would start with PyTorch: research happens there. New architectures arrive there first. TensorFlow keeps its production tooling and mobile deployment through LiteRT. Keras now runs on TensorFlow, JAX or PyTorch. PyTorch Lightning takes the boilerplate out of the training loop.
- Language and large models. Five uses, from word splitting to evaluation:
- Classical processing: spaCy tokenizes, recognizes entities and parses grammar at industrial scale. fastText, from Meta, classifies multilingual text very fast and remains a hard baseline to beat for its cost.
- Transformers and embeddings: Hugging Face Transformers for pretrained models, sentence-transformers to turn a sentence into a vector and search by meaning.
- Running a large model at home: Ollama is the simplest, a local service that wraps llama.cpp. To serve in production, with many users at once, take vLLM. For the model itself, size decides: on a laptop or a phone, the Qwen family is the strongest; with a real graphics card, Gemma holds up better.
- Document retrieval: LangChain or LlamaIndex assemble searches, prompts and tool calls. For the vector index, FAISS, from Meta, is the reference. When memory matters, turbovec compresses vectors to 2 or 4 bits and fits some ten million vectors in 4 GB, with little loss. It is our favorite local index. Qdrant or Chroma when you need a separate service.
- Evaluating a large model: DeepEval writes tests as assertions, with a model as judge. Ragas measures what is specific to retrieval: context relevance, faithfulness, answer correctness.
- Fairness, explainability, causality. To run before the model ships, not on the day a regulator asks:
- Measuring and correcting bias: Fairlearn, from Microsoft.
- Explaining a prediction: SHAP assigns each variable its share of the decision. Shapash, from the French insurer MAIF, adds a layer that business readers understand, with clear labels and exportable reports. Take it when the explanation is for someone outside the team.
- What if the input changed? DiCE, from Microsoft, answers "what minimal change would flip this prediction?" by offering several answers.
- Attribution on deep networks: Captum, from Meta and PyTorch.
- Causality: PyWhy gathers the open tools. Its core library, DoWhy, answers "does X cause Y?" rather than "can we predict Y from X?". An example makes the difference plain: ice cream sales predict sunburns without causing them.
- Preparing tabular data. skrub, from the Probabl and Inria team around scikit-learn, turns messy tables into usable features: joins on misspelled names, categories by the thousand, plus a complete pipeline in one call,
tabular_pipeline(). - Foundation models for tables. Pretrained transformers now match or beat well-tuned decision trees on small to medium tables, with no training at all on your data. Two stand out: TabPFN, from Frank Hutter's lab, then TabICL, from Inria's SODA team. Both read your training table as a series of examples and predict the test rows in a single pass. Try one before you reach for a stack that needs training.
- Pretrained models. Hugging Face: the model hub and the
transformers,diffusersanddatasetslibraries. A one-linefrom_pretrained()plays the role thatpip installused to. - Testing an AI. An accuracy number on a validation set tests one thing. These tools test the rest:
- Scanning a model or an agent: Giskard looks for what is wrong in one call: robustness, hallucination, prompt injection, bias.
- Attacking your own system: PyRIT, from Microsoft, automates attacks on a generative AI. garak, from NVIDIA, probes for data leaks and guardrail bypasses.
- Validating data: Great Expectations and Pandera check the data before it reaches the model. You cannot test a model whose inputs you cannot audit.
- Evaluating and comparing models. skore, from Probabl, sits on top of scikit-learn and returns an opinionated evaluation, a model comparison and diagnostics. Reach for it as soon as you have more than one model to choose between.
AI Helpers: the plumbing we no longer rewrite
AI Helpers gathers seventeen open Python libraries under the BSD-3-Clause license, maintained by Warith Harchaoui with Mohamed Chelali and Bachir Zerroug. Each one covers a precise corner of the AI and media chain: loading and converting audio, transcribing a voice, extracting frames from a video, downloading media, sending a file to a server, converting Markdown to Word, choosing the local large model that fits in your memory, detecting an elbow on a curve. Local is the default: your data stays on the machine, except for the two libraries whose job is to move it, S3 storage and SFTP transfer.
We mention them here mostly for their shape. They all follow the same repository conventions. Those conventions are what sections 7 and 8 describe.
Graphics card: NVIDIA CUDA or Apple Silicon
This section is for anyone choosing a machine, or wondering why a training run is slow. A graphics card (GPU) performs thousands of small calculations at once, exactly what a neural network demands. Two families share the work. They do not serve the same purposes.
NVIDIA CUDA
The reference setup for serious training. CUDA is NVIDIA's software layer that lets programs talk to its cards. It has the largest collection of models and tools. It is also the only realistic path for training at large scale.
- For: large-scale deep learning, big batches, production inference.
- Install: the Ubuntu recipe in section 9, then the official PyTorch selector.
- Free cards for research: the NVIDIA Academic Grant Program.
Apple Silicon (M series)
A serious option for local work. Unified memory is shared between the processor and the graphics chip. Together with the silence of the machine, it makes experimentation, prototyping and teaching pleasant. It is enough for plenty of medium-sized work. When models grow, CUDA takes the lead again.
- For: local development, prototyping, teaching.
- PyTorch through the Metal Performance Shaders backend, device
mps, with nothing else to install. - MLX, developed by Apple, for NumPy-style research on its own chips.
The rule of thumb: develop on the machine you have at hand. When the experiment outgrows the laptop, move to a CUDA machine, a card rented in the cloud or a compute cluster. Installing a graphics card always costs time: move when the move pays off, not before.
Notebooks, editors and coding agents
This section is for anyone wondering where to write their code. Three layers meet every day: notebooks to explore, editors for sustained work, finally coding agents, which now sit on top.
Notebooks
A notebook mixes code, its results and text in a single document, cell by cell.
- JupyterLab: the reference notebook in the browser, to explore, present and teach. It is already in
requirements.txt; runjupyter lab. - marimo: a notebook saved as a plain
.pyfile, so it reads cleanly in a Git history. Its cells recompute on their own when a value changes upstream. No more sessions spent figuring out in what order Jupyter ran things. - Google Colab: a notebook that runs on Google's machines. For a few hours of compute, or for a class where everyone starts from the same environment.
Editors
VS Code remains the safe daily choice. AI-oriented derivatives have appeared since: Cursor, the first of its kind, then Google Antigravity, designed for several agents working in parallel. The open equivalent is Cline, a VS Code extension that accepts any model provider, local Ollama included. This list turns over every two or three months: judge for yourself.
Coding agents, one more layer
Programming has always advanced in layers. Machine code gave way to assembly, assembly to C, C to Python, JavaScript and Rust. Each layer let the programmer express more intent and hand more mechanics to a tool, the compiler. Large language models add the next layer: natural language becomes the source; code becomes what the machine derives from it.
“The hottest new programming language is English.”
Andrej Karpathy, public post of January 24, 2023. Source.
That is the optimist's view. Seen from the other end of the stack, the same observation reads like this, from the creator of Linux:
“AI is a great new tool, but it's a tool, and when I see people saying, 'Hey, 99% of our code is written by AI,' I literally get angry, because those same people, I can pretty much guarantee, that 100% of their code is written by compilers.”
Linus Torvalds, keynote at Open Source Summit North America, Minneapolis, May 2026. Video clip.
Both are right at the same time. Karpathy describes what becomes accessible: most people will get a useful program by writing English rather than Python. Torvalds reminds us what cannot be delegated. Someone understands what the compiler, or the model, actually produced. That person is still the one who will fix it, speed it up and ship it. A coding agent amplifies an intent; it does not supply one. Treat it as the next compiler: say clearly what you want, then read what comes back with a critical eye.
The two agents we use most:
- Claude Code, Anthropic's command-line agent. It reads your repository, runs commands, edits files and follows the skills installed in
~/.claude/skills/. It excels at review, end-to-end debugging and changes that span several files. - OpenCode, an open agent in the terminal that drives Claude, GPT or local models behind the same interface. Pick it to avoid depending on a single provider, or to keep sensitive code on a local model.
Both read the same skills format, described in section 7.
From notebook to five surfaces
This section is for anyone who has a tool that works and wants others to use it. Nearly every project follows the same path:
Notebook → script → library → five surfaces
You first find what works in a notebook. You promote the useful cells to a script, so the experiment can be replayed. You then gather the functions into a library, a Python package that installs with pip and has tests. What remains is to make it reachable.
Streamlit, Gradio and Taipy exist for that: they turn a script into a web form. Gradio has belonged to Hugging Face since 2021. Eight in ten of the site's thousand most-liked Spaces are built with it. We prefer a plainer, freer interface in ordinary HTML, which Sprezzature generates from the command line.
A person, a program and an AI agent do not enter a tool through the same door. We call each of these entry points a surface. We give every library five: a command line, a graphical interface, an HTTP interface, an MCP server and a skill. The rule that ties them together: no surface redoes the computation. They all call the same library function. A fix made once applies to all five. Two surfaces never give two different answers to the same question.
The examples that follow come from elbow-helper, an AI Helpers library that says whether a curve has an elbow and, if so, where.
The command line, with click
The command line serves the colleague who works in a terminal, scripts and continuous integration pipelines. It also goes by CLI, for command-line interface.
Python ships argparse to read a command's options. click does the same job with less code. It adds four things we no longer want to write by hand:
- Subcommands grouped together:
elbow-helper knee,elbow-helper elbow,elbow-helper diagnostics. Each one is an ordinary, decorated Python function. - Generated help:
--helpdescribes every option, its type and its default, without one extra line. - Checked options:
--languageaccepts onlyenorfr; click rejects anything else with a clear message. - Tests without a terminal:
CliRunnerruns the command inside a test and captures its output.
import click
@click.group()
def cli():
"""Find an elbow, or abstain."""
@cli.command()
@click.option("--language", type=click.Choice(["en", "fr"]), default="en")
@click.argument("path")
def diagnostics(path, language):
...
One line of pyproject.toml, elbow-helper = "elbow_helper.cli:main", does the rest: pip install elbow-helper installs the command.
The graphical interface, with Sprezzature
The graphical interface serves the person who will never open a terminal: a client, a colleague from another field, a student.
Sprezzature is an open suite of skills for coding agents, which we develop for everything that ends up on a screen. One of them, sprezzature-cli-gui, reads the code of your command line directly, whether it is written with argparse, click or Typer. It derives an HTML page from it: each option becomes the right form control, a checkbox, a dropdown, a number field. The result is ordinary HTML and Tailwind that you can read and edit, with no framework holding you in.
elbow-helper goes one step further. Its page runs the real library in the browser, thanks to Pyodide, a build of Python for the web. The page can be dropped on any static host, with no server to run. It is the page live at deraison.ai/elbow-helper.
The HTTP interface, with FastAPI
The HTTP interface serves other programs: a website, a mobile app, another team's service, written in any language. It also goes by API, for application programming interface.
FastAPI turns a Python function into a web address. The types declared in the function become the contract: FastAPI checks every request and publishes interactive documentation at /docs, where each route can be tried from the browser. This is the interface that the docker compose file of section 2 starts.
The MCP server, with FastAPI-MCP
The MCP server serves AI agents. MCP, for Model Context Protocol, is an open protocol that lets an agent such as Claude Code discover tools and call them. Without it, the agent guesses a command and reads its output; with it, the agent receives the list of tools, their parameters and their types.
FastAPI-MCP mounts an MCP server on the FastAPI application that already exists. Each MCP tool is an API route. The two surfaces therefore never drift apart: they share the same code, the same validation and the same documentation.
from fastapi_mcp import FastApiMCP mcp = FastApiMCP(app, include_operations=["knee", "elbow", "diagnostics"]) mcp.mount_http() # the MCP server answers on /mcp
One question remains that the protocol leaves open: when should the agent call the tool? It decides from a short description. That is where most tools fail. An agent will not think of elbow-helper when asked "how many clusters should I pick for my k-means?", unless someone wrote down that this very phrase must trigger it. So for every repository we write a TRIGGERS.md file: the full catalog of what the tool can do, the command, function and route behind each operation, then the phrases that should trigger it and those that should not. It exists in two identical copies, one at the repository root for people, the other in the skill folder for the agent.
The skill
The skill serves the coding agent that works with you inside your project. It is a folder containing a SKILL.md file: at the top, a name and a description; below, the instructions for one precise job; alongside, references and scripts. At first the agent reads only the description. It loads the rest only when your request matches, so a hundred installed skills do not weigh it down.
The format was born at Anthropic, which published it as an open standard under the name Agent Skills. The same folder is now read by Claude Code, OpenCode, OpenAI's Codex, Google's Gemini CLI, Cursor, GitHub Copilot and goose. Several of these agents look for skills in a shared folder, .agents/skills. A skill written once therefore works with any model, commercial or local.
The description is the centerpiece: it is the only thing the agent reads before deciding. It stays under 1,024 characters. It says what the tool does, when to use it in the user's own words, then what it does not do. The detail goes into references/triggers.md, the twin of TRIGGERS.md.
To build a skill from a repository still under construction, we apply the method of Anthropic's guide:
- Start from two or three concrete use cases: what the user says, what the agent does, what comes out. The skill is built around them, not around the list of functions.
- Write the description in two parts: what the tool does, then when to use it. A vague "helps with projects" fails; so does a technical description without the user's words.
- Keep
SKILL.mdshort and precise. Numbered steps, with the exact command and the expected output. The detail goes intoreferences/, which the agent opens only when needed. - Hand checks to a script. A script always gives the same answer; an instruction in prose gets interpreted.
- Test the triggering. About twenty requests: obvious ones, paraphrased ones, then off-topic ones that must not trigger it. The guide aims for nine correct triggers out of ten.
- Compare with and without the skill on the same task: fewer calls, fewer errors, or it serves no purpose.
- Ask the agent "When would you use this skill?". Its answer shows what it understood from the description.
The guide does not cover the case of an existing repository. Ours has an advantage: TRIGGERS.md, EXAMPLES.md and the repository's tests already provide the use cases, the trigger phrases and the expected outputs. The skill is built by condensing them. A coding agent does this job very well when given the guide and the repository.
What makes a repository readable
This section is for anyone who publishes code and wants it used without people having to write to them. A repository is first read through its uppercase files at the root. Here are the ones we put everywhere, with what each one proves.
README and LISEZMOI
The README says what the tool does, how to install it and how to use it in one minute. It exists in two languages, README.md and LISEZMOI.md, each linking to the other on its first line. Every uppercase file that follows comes in the same pair.
EXAMPLES and EXEMPLES
An example that never ran ends up lying: an option gets renamed, the example stays. In EXAMPLES.md, every code snippet was executed against the version announced at the top of the file. The block that follows it is its real output, pasted as is. Readers see what they will get, errors and abstentions included.
LANDSCAPE and PAYSAGE
LANDSCAPE.md compares the tool with every other way of doing the same job. A table rates each competitor from one to five stars, criterion by criterion. Then standpoint, another AI Helpers library, turns that table into a positioning map. It publishes the coordinates of every point so the placement can be checked.
Two rules keep this file honest. Each competitor is rated on this project's job, without being penalized for excelling elsewhere. And the list always includes the cheap option: eyeballing it, or asking a large language model. If the tool does no better than that row, it has no reason to exist.
TRIGGERS
Described in section 7: what the tool can do, then the phrases that should trigger it. It is the one file that serves the human reader and the agent alike.
Languages for the interface, the messages and the prompts
A tool documented in two languages that answers in only one stops halfway. We translate three kinds of text. We keep them apart, because they do not follow the same translation rules.
- Command-line and interface messages. Python's standard
gettextmodule reads translation catalogs,.pofiles written by the translator and then compiled to.mo. The better-i18n guide describes the method well. Three of its tips prevent the most common mistakes. Variables stay named inside the string to translate,%(name)s, so the translator can reorder them. Plurals go throughngettext, because each language has its own rules: Polish has four forms. Numbers and dates go through Babel, which knows that French puts a non-breaking space before the percent sign. We compile the.mofiles ourselves on first use, so installation needs no extra tool. - Web page text. One JSON table per language, read by the browser, so the page switches language without Python.
- Prompts sent to a language model. They live in a separate file,
prompts.yaml. An interface label can be translated freely; a prompt cannot. It names fields that the program reads back afterward: a loose translation gets the model's answer rejected.
One last rule depends on who reads the output. The command line does not follow the system language. It answers to a dedicated variable, because scripts read its output: a command that switches language when LANG is fr_FR breaks every one of those scripts. The graphical interface, on the other hand, follows the browser's language: a browser asking for French is a person asking.
Installation recipes
This section is for anyone setting up a new machine. One recipe per system, to paste in order. Whenever you need to open a new terminal or restart, the recipe says so: it is the step everyone skips. It also causes half of all "conda: command not found" errors.
Everywhere, the project contains the three files of section 1; the environment is called env4ml.
macOS (Apple Silicon)
We replayed this recipe in November 2026 on an Apple Silicon Mac, with Miniconda installed in a fresh folder, a fresh environment and a blank terminal. PyTorch sees Apple's graphics chip provided you run macOS 15 or later.
Apple's developer tools. They provide the compiler and Git.
xcode-select --install
Miniconda.
mkdir -p ~/miniconda3 curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.sh bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 rm ~/miniconda3/miniconda.sh ~/miniconda3/bin/conda init zsh
Close the terminal and open a new one. The last command changed the shell configuration, which only a new terminal reads.
The environment, from the project folder:
conda env create -f environment.yaml conda activate env4ml
The check. The command prints mps:0, the name of Apple's graphics chip:
python -c "import torch; x = torch.ones(3, device='mps'); print(x.device)"
One detail surprised us during the test. Right after pip install -r requirements-dev.txt, the pytest command run in the same terminal was an older copy installed elsewhere on the machine. The zsh shell remembers where the commands it knows live. Type rehash, or open a new terminal, after installing a command-line tool.
Ubuntu
Miniconda.
mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 rm ~/miniconda3/miniconda.sh ~/miniconda3/bin/conda init bash
Close the terminal and open a new one.
The environment, from the project folder:
conda env create -f environment.yaml conda activate env4ml
Without an NVIDIA card, you are done.
The NVIDIA driver and CUDA. We follow Google Cloud's instructions, which provide an installer script tested on recent Ubuntu releases:
curl -L https://storage.googleapis.com/compute-gpu-installation-us/installer/latest/cuda_installer.pyz --output cuda_installer.pyz sudo python3 cuda_installer.pyz install_driver
The script sometimes restarts the machine partway through. If it does, run the same command again: it picks up where it left off. Once the driver is installed, restart the machine. The script does not work while Secure Boot is on: Google covers that case in a separate section of the same page.
The check.
nvidia-smi python -c "import torch; print(torch.cuda.is_available())"
The first command shows the card, the second prints True. On Linux, the default PyTorch in requirements.txt already ships with CUDA: nothing to change in the list. Go through the official PyTorch selector only if you need a different CUDA version.
Windows 11
For machine learning on Windows, we strongly recommend WSL2, the Windows Subsystem for Linux. It runs a real Ubuntu inside Windows, so the same commands work on your laptop, on a server and in a container.
The NVIDIA driver, on the Windows side, if you have an NVIDIA card. Get the usual Windows driver from NVIDIA's site. It is the only driver to install: NVIDIA's guide for WSL forbids installing a second one inside Ubuntu.
WSL2, from a PowerShell opened as administrator:
wsl --install
Restart the computer. On restart, Ubuntu finishes installing and asks for a username and a password.
Open Ubuntu from the Start menu. Follow the Ubuntu recipe above, skipping step 3: the Windows driver also serves WSL.
If you really need native Windows, for instance because a development kit exists only there, install Miniconda from the command prompt:
curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -o miniconda.exe start /wait "" miniconda.exe /S del miniconda.exe
Then open the "Anaconda Prompt (miniconda3)" app from the Start menu: the silent install does not add conda to the ordinary command prompt. In that window, the two commands of section 1 work as they are. For an NVIDIA card, use the command the PyTorch selector gives for Windows: the default PyTorch there runs on the processor.
If you are a beginner looking for practice, Kaggle's free courses are a good place to start once the environment is ready.
Going further: MLflow and Airflow
This section is the advanced part. It is for anyone with a model in production to retrain, compare with the previous one and replace without breakage. If you are just discovering PyTorch, come back later.
The problem shows up around the third model. Which one runs in production? What data was it trained on? Why is the new one worse? Without a written trail, nobody can answer. One day the answer becomes "the file final_model_v3_really.onnx, on the laptop of an intern who left long ago."
MLflow keeps the trail. Every training run is recorded with its parameters, its metrics and the model it produced. The best model enters a registry with a version number. The team knows at all times which one is in service. MLflow can record a model in the ONNX format, described in the next section:
import mlflow
with mlflow.start_run():
mlflow.log_param("depth", 6)
mlflow.log_metric("accuracy", 0.93)
mlflow.onnx.log_model(onnx_model, name="model", registered_model_name="detector")
The file that will go out to devices is therefore the one that was measured and versioned, not an export made by hand on a Friday night.
Airflow runs the pipeline without anyone having to think about it. You describe, in Python, a sequence of tasks and their dependencies: fetch the new data, retrain, evaluate, export to ONNX, register in MLflow. Airflow launches it every week, or as soon as new data arrives. If the new model does worse than the old one, the pipeline stops and the old one stays in service. Since its third generation, Airflow also keeps the version of the pipeline each run started with: a change made midway no longer mixes two versions in the same result.
Both tools have competitors, commercial and open, as well as a managed service in every major cloud. We name these two because they are open and widespread. They also install from the same list as everything else.
Open source, the backup generator
This section is for anyone who chooses tooling for a team, a lab or a company. It explains why, next to every commercial service, we keep an open equivalent installed and tested.
“AI is the new electricity.”
Andrew Ng, Stanford Graduate School of Business, January 2017.
The phrase has been repeated until it sounds obvious, but the path it describes is real. Electricity went from cutting-edge technology to public utility, then to something nobody thinks about anymore. AI is on the same road. The comparison helps here for a precise reason: electricity is also the textbook case of how to depend on something well.
In any OECD country, a hospital is connected to a grid that is designed, regulated, monitored and overwhelmingly reliable. It still keeps a generator in the basement. Nobody runs intensive care on it on an ordinary Tuesday: it is there for the day the grid goes down, because "we trusted the provider" is not a defensible answer.
AI is joining the same category as electricity, water and roads. Hospitals, ministries, labs, schools and small businesses already depend on it. The difference with the power grid: AI providers are a handful of private companies, in a handful of countries, each with its own prices, its own rules and its own outage history.
The practical answer is the hospital's. Use a commercial AI when it is the right tool. Keep an open equivalent installed and tested, ideally on your own machines or your own servers. One day, a bill, a change of terms, a rate limit, a revoked key or a political decision will put the commercial option out of reach. Open AI is, at the very least, the backup generator for the paid power plant.
A dual stack
- Commercial side: Claude, ChatGPT, Gemini, Mistral. Frontier capability, hosted compute, service commitments.
- Open side: the open-weight models of Meta Llama, Google Gemma, Qwen, Mistral and DeepSeek, which you run yourself through Ollama, llama.cpp or vLLM.
- Coding agent: the same pattern one layer up. Claude Code for daily work, OpenCode in the basement, driving the same skills with a local model, for the day the upstream service becomes unreachable.
A virtuous circle
Beyond the backup argument, the two sides feed each other. Commercial labs push the frontier; open source catches up, often within months, then pushes it further with ideas that the next commercial release picks up. The lineage is easy to read: TensorFlow and PyTorch opened up by Google and Meta, the founding Transformer paper posted openly, Hugging Face and its model hub, llama.cpp running serious models on a laptop, the reasoning recipes of DeepSeek-R1. Each one brought a cost down or unlocked a technique the commercial world then built on.
Modern AI could not have been built behind closed doors. Its papers, its benchmarks and the worldwide community that reads and improves them needed the door open. Without open source, there is no PyTorch to learn from, no preprint to reproduce on a Friday night, no Kaggle notebook to fork.
The dual stack is therefore two things at once. A good practice: the generator runs once a month, the team knows how to start it. When the grid goes down, the hospital carries on. And an acknowledged debt: every other day, the grid itself runs on open code, open papers and open weights.
From Python to Rust, through ONNX
This last section is for anyone who must run a model somewhere other than Python: on a phone, in an embedded device, in a browser, or in a program shipped as a single file.
Python is the right place to search. Libraries, papers and models appear there first. This whole guide rests on it. But Python does not go everywhere. On an iPhone or an Android phone, nobody embeds a Python interpreter and its scientific stack inside an app. On an industrial machine, nobody asks the operator to install conda.
The chain we follow has three steps.
We develop in Python. That is where we train, measure and compare, with everything the previous sections describe.
We freeze the model in the ONNX format. ONNX, for Open Neural Network Exchange, is an open file format that describes a trained model: its computations and its weights, without the Python code that produced it. PyTorch, TensorFlow and scikit-learn can all export to ONNX. The engine that runs this file, ONNX Runtime, is open source, maintained by a community in which Microsoft plays a large part. It runs on every system.
We deploy in Rust. Rust is a compiled language, as fast as C, that rules out most memory errors at compile time. A Rust program ships as a single executable per platform: nothing to install, no interpreter to start, little memory used. The Rust library ort, still in prerelease, calls ONNX Runtime and provides ready-made binaries for Windows, macOS, Linux, iOS, Android and the browser. On each device, it goes through the local accelerator: Core ML on Apple hardware, NNAPI or the Qualcomm chip on Android.
The argument is portability, not speed. We measured it on the diarization in vocal-helper, which tells who speaks when in a recording. The ONNX model takes 99.8% of the computing time. It runs in the same C++ code whether it is called from Python or from Rust. Rewriting the rest in Rust would gain 0.2% at best. Its rewrite, vocal-helper-rs, exists for another reason: it runs where Python cannot reach. Five other AI Helpers libraries have a Rust twin, published on crates.io, for machines without Python.
Other Rust tools are worth knowing:
- tract, from Sonos, runs an ONNX model in pure Rust, without depending on ONNX Runtime. It is small and self-contained, made for embedded devices.
- candle, from Hugging Face, then burn: two machine learning frameworks written in Rust. Both can import an ONNX model; burn even turns it into ordinary Rust code.
- UniFFI, from Mozilla, generates the bindings for Swift on iOS and Kotlin on Android from a Rust library. The same Rust core then serves the app on each phone.
So the loop closes. This guide starts with a Python environment, because that is where discovery happens. It ends with Rust, because that is how what you discovered leaves Python for every system and every device. In between, ONNX acts as a contract: a single file that the researcher produces and the device runs. The site arewelearningyet.com keeps track of machine learning in Rust.