MLOps and LLMOps: Classic ML

Sefism.

Chapter 01

From notebook to package

The shape of the whole lifecycle, the tools that fill each step, and how to turn notebook code into a tested, locked, containerised Python project that anyone can run.

The MLOps lifecycle

Also called ML lifecycle, MLOps loop, LLMOps

What
The loop a production model lives in: data is collected and validated, a model is trained and evaluated, approved and deployed, served to users, monitored, and retrained when it decays. LLMOps is the same loop where prompts, eval sets and model versions are the moving parts.
Why
Every tool and practice in this sheet exists to make one arrow in the loop repeatable. Knowing the loop tells you which gap is hurting your project right now.
Code
data -> validate -> train -> evaluate -> register
  ^                                          |
  |                                          v
retrain <- monitor <- serve <- deploy <- approve

Watch out

Treating deployment as the finish line: most of a model's cost and risk comes after it goes live.

Tool map per stage

Also called MLOps stack, MLOps tools

What
A reference of common open source tools for each step of the lifecycle. Pick one per row and learn it well; they are largely interchangeable once you understand the job each row does.
Why
Job adverts list tools, but the skill is the job behind them. A map keeps you from learning three orchestrators and no monitoring.
Code
Package      uv, pytest, Docker
Version      Git, DVC
Track        MLflow tracking and model registry
Orchestrate  Prefect, Airflow, Dagster
Serve        FastAPI, BentoML, vLLM
CI/CD        GitHub Actions, promptfoo
Monitor      Prometheus, Grafana, Evidently
Trace, eval  Langfuse, Arize Phoenix, MLflow

Watch out

Adopting a whole platform before you have one model in production that needs it.

Project layout and pyproject.toml

Also called src layout, pyproject, Python package

What
A repository where notebook code becomes importable modules under src/<package>/, with dependencies, entry points and tool settings declared in one pyproject.toml file. Notebooks stay for exploration and import from the package.
Why
Code in a package can be tested, reused by the training job and the API, and installed the same way on a laptop, in CI and in a container.
Example
src/churn/data.py loads and splits data, src/churn/features.py builds features, src/churn/train.py is the entry point, and tests/ mirrors them.

Watch out

Copying cells from a notebook into the serving code, so training and serving drift apart without anyone noticing.

Dependencies and lockfiles

Also called uv, uv.lock, pinned dependencies, virtual environment

What
A lockfile records the exact version of every package, including indirect ones, that your project resolved to. uv, a fast Python package and project manager, writes uv.lock and recreates the same environment anywhere with one command.
Why
A model trained with one version of a library can behave differently or fail to load with another. Locked environments make training runs and deployments repeatable.
Shell
uv init churn-model --package
cd churn-model
uv add scikit-learn pandas mlflow
uv add --dev pytest ruff
uv run pytest
git add pyproject.toml uv.lock

Watch out

Running pip install by hand on the server, so production has versions nobody wrote down.

Config and secrets

Also called environment variables, params.yaml, 12-factor config

What
Settings that change between runs or environments, such as paths, hyperparameters, model names and API keys, kept out of the code: parameters in a versioned config file, secrets in environment variables or a secrets manager.
Why
The same code can then train on a sample in CI and on full data in production, and no key ever lands in Git or a notebook output.

Watch out

Leaving an API key in a notebook cell that is later committed or shared.

Training entry point

Also called training script, CLI, random seed

What
One command that trains the model from start to finish: it reads config, loads data, fixes random seeds, fits the pipeline and saves the artefact, with no manual steps in between.
Why
A pipeline, a CI job or a colleague can run it unattended. If training needs someone to click through a notebook, it cannot be automated or reproduced.
Python
import argparse, joblib, numpy as np
from churn.data import load_split
from churn.model import build_pipeline
def main() -> None:
    p = argparse.ArgumentParser()
    p.add_argument("--data", required=True)
    p.add_argument("--seed", type=int, default=42)
    args = p.parse_args()
    np.random.seed(args.seed)
    X, y = load_split(args.data, "train")
    model = build_pipeline(seed=args.seed).fit(X, y)
    joblib.dump(model, "artefacts/model.joblib")

Watch out

Saving only the estimator and not the preprocessing, so serving code has to rebuild features by hand.

Model artefact formats

Also called joblib, pickle, skops, ONNX, model serialisation

What
The file a trained model is saved as: joblib or pickle for scikit-learn objects, skops for a safer scikit-learn format, the framework's own format for PyTorch or XGBoost, and ONNX as a portable format many runtimes can load.
Why
The format decides which runtime can serve the model, how fast it loads and whether loading an untrusted file can run code.

Watch out

Loading a pickle or joblib file from an untrusted source: unpickling can execute arbitrary code.

Tests for ML code

Also called pytest, unit tests, smoke test

What
Fast tests with pytest for the deterministic parts: feature functions, data loading, schemas and output shapes, plus a smoke test that trains on a tiny sample and checks predictions have the right type and range.
Why
Most production ML failures are ordinary bugs in data and feature code, not in the maths. Tests catch them before a long training run or a deploy.

Watch out

Testing that accuracy equals an exact number, which fails on every harmless change; test ranges and invariants instead.

Container image

Also called Dockerfile, Docker, image

What
A packaged file system with your code, a Python runtime and locked dependencies, built from a Dockerfile, that runs the same way on a laptop, in CI and in the cloud.
Why
Training jobs and model APIs both ship as images. It removes "works on my machine" and is what orchestrators and serving platforms expect.
Dockerfile
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY src ./src
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "python", "-m", "churn.serve"]

Watch out

Copying the whole repository, including data and secrets, into the image.

01 · From notebook to package1 / 11