Master Notebook:
ML Systems

Production ML infrastructure and case-study-driven design for FAANG SWE + ML engineer interviews — feature stores, embeddings, recommenders, ranking, LLM serving, and training at scale.

10 Modules • 20+ Visualizations • 7 Case Studies • Complete Cheat Sheet

Module 1

ML Fundamentals Recap

A fast re-grounding in the three things every ML systems interview assumes: tasks & targets, loss functions, and the training loop. No calculus derivations — just the moving parts you must be able to draw on a whiteboard.

Tasks & Targets

An ML system exists to predict a target from a feature vector. The interviewer's very first clarifying question — "what are we predicting?" — is not rhetorical; the answer determines the loss, the metric, and whether you can even collect labels. There are four shapes of prediction problem that cover 95% of interview scenarios.

  • Binary classification — the label is 0/1. Examples: click, spam, churn, fraud. Output layer is a single sigmoid; loss is binary cross-entropy.
  • Multi-class — the label is exactly one of K classes. Examples: MNIST, language ID, intent routing. Softmax head; categorical cross-entropy.
  • Regression — the label is a real number. Examples: ETA, price, ad CPC, latency. Linear output; MSE / MAE / Huber.
  • Ranking / retrieval — there is no single "answer"; you return an ordered list. Examples: search, recommendation, ads. Loss acts on pairs or lists of items; metric is NDCG / MRR / recall@k.

Each of these maps to a different system shape. A click predictor looks like "feature store → DLRM → logistic head → calibration → bidder." A ranker looks like "candidate generator → scorer → re-ranker." If you do not name the task shape in the first two minutes of the interview, the rest of your design will drift.

Losses You Must Know

Losses are the contract between the task and the gradient. Memorize these six; almost every production system is a composition of them.

LossTaskFormWhen to reach for it
Binary cross-entropyClick / fraud / CTR-y·log(p) - (1-y)·log(1-p)Default for 0/1 labels; probabilistic interpretation needed for bidding.
Categorical cross-entropyLanguage ID, image class-Σ y_k·log(p_k)Exclusive K classes, softmax head.
MSE / L2ETA, price(y - ŷ)²Symmetric errors, penalize outliers; watch for label skew.
HuberETA with outliersL2 near 0, L1 at tailsMSE's smoothness with L1's robustness.
HingeSVMs, margin classifiersmax(0, 1 - y·ŷ)Sparse support vectors; rarely production now.
Contrastive / NCE / InfoNCEEmbeddings, retrieval-log(e^s⁺ / Σ e^s)Trains two-towers; pushes positive pair together, negatives apart.
Listwise (LambdaRank, ListNet)Search rankingΔ-NDCG weighted pairwiseOptimizes the ranking metric directly.

Minimal PyTorch reference

import torch
import torch.nn.functional as F

# 1) Binary cross-entropy (logit form is numerically stable)
logits = model(x)                        # shape [B]
loss = F.binary_cross_entropy_with_logits(logits, y.float())

# 2) Categorical (softmax over K)
logits = model(x)                        # shape [B, K]
loss = F.cross_entropy(logits, y.long())

# 3) Contrastive InfoNCE (batch negatives, temperature 0.07)
q = F.normalize(query_tower(x_q), dim=-1)    # [B, D]
k = F.normalize(item_tower(x_k),  dim=-1)    # [B, D]
logits = q @ k.T / 0.07                      # [B, B]
labels = torch.arange(q.size(0), device=q.device)
loss   = F.cross_entropy(logits, labels)     # diagonals are positives

Gradient-Based Training

Every model in this notebook — from two-tower to 70B transformer — is trained by the same five-line loop: sample a batch, forward, compute loss, backward, step the optimizer. What varies is the optimizer, the batch size, and the parallelism. The core loop is non-negotiable.

optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)

for epoch in range(epochs):
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        optimizer.zero_grad(set_to_none=True)
        logits = model(x)
        loss   = loss_fn(logits, y)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)  # stability
        optimizer.step()
    scheduler.step()

Optimizer picks, by era

  • SGD + momentum — still the academic default for ResNets on ImageNet; cheap memory.
  • Adam / AdamW — default for transformers and anything with sparse features. Costs 2× weights in state (first/second moment).
  • Adafactor / 8-bit Adam — when optimizer state memory is the bottleneck on 10B+ params.
  • Lion / Sophia — newer, faster convergence claims; not yet standard in interview answers.
Loss landscape (grid heatmap, lower = better)

Learning rate: the one hyperparameter that matters

If you have one hour to tune a model, spend it on the learning rate. Rule of thumb: Adam likes 1e-4 to 1e-3 for transformers; SGD likes 0.1 with momentum 0.9 for conv nets. A cosine schedule with 1–5% warmup is the modern default. Gradient clipping at norm 1.0 makes transformer training not blow up on the first few steps.

Overfitting & Regularization

Overfitting is when the model memorizes the training set and generalizes poorly. Every regularization technique in production ML is one of three ideas: shrink the hypothesis space, add noise, or stop early.

  • L2 (weight decay) — penalize ||w||². Shrinks weights toward zero; built into AdamW. Default 1e-2 for transformers.
  • L1 — penalize ||w||₁. Produces sparse weights; useful when you want feature selection.
  • Dropout — randomly zero p fraction of activations during training. p=0.1 is default for transformers; p=0.5 for older FC nets.
  • Label smoothing — replace one-hot targets with (1-ε, ε/(K-1)...). Prevents overconfident logits; hurts calibration slightly.
  • Data augmentation — random crops/flips for images, token masking for text. Free regularization via more effective samples.
  • Early stopping — watch the val loss; stop when it starts climbing. Free, always works.

Bias-Variance

Every interview eventually asks "what's bias-variance?" The short answer is: bias is how wrong your model's best guess is on average; variance is how much that guess wobbles when you resample the training set. Total error ≈ bias² + variance + irreducible noise.

SymptomDiagnosisFix
Train loss high, val loss high, similarHigh bias (underfit)Bigger model, more features, less regularization, train longer.
Train loss low, val loss highHigh variance (overfit)More data, stronger regularization, simpler model, early stop.
Val loss low, online metric badDistribution shift / leakageCheck train/serve skew, feature freshness, label definition.
Loss NaN after step 50Exploding gradientsClip grad norm, drop lr 10×, check for division by zero.

Learning Paradigms

Before you reach for a model, name the paradigm — it is fixed by the data you can actually obtain, not by the algorithm you prefer. "Do we have labels, and how many?" decides whether you are in supervised, self-supervised, or RL territory. Getting this wrong is the most expensive early mistake in an ML design interview.

ParadigmData signalExample tasksExample algorithms
SupervisedExplicit input→label pairsClassification, regressionLogistic regression, gradient-boosted trees, CNNs, fine-tuned transformers
UnsupervisedNo labels; structure onlyClustering, dimensionality reduction, density / anomaly detectionk-means, DBSCAN, PCA, Gaussian mixtures, isolation forest
Self-supervisedLabels derived from the data itself (pretext tasks)Pretraining LLMs and modern vision encodersMasked language modeling (BERT), next-token prediction (GPT), contrastive learning (SimCLR) and self-distillation (DINO)
Semi-supervisedA few labels + many unlabeledLabel-scarce classificationPseudo-labeling, consistency regularization (FixMatch), label propagation
ReinforcementReward from environment interactionControl, game-play, sequential decisions, RLHFQ-learning, DQN, REINFORCE, PPO, actor-critic

Classic ML (Still Wins on Tabular)

Deep learning dominates text, images, and audio — but on tabular data (rows of mixed numeric/categorical features), gradient-boosted trees usually still beat neural nets, train in minutes on a CPU, and need almost no tuning. Knowing when not to reach for a transformer is a senior signal.

AlgorithmTypeStrengthWatch-out
Linear / logistic regressionSupervisedFast, interpretable coefficients, well-calibrated probabilitiesOnly linear decision boundaries unless you engineer features
Decision treeSupervisedCaptures non-linearities, human-readable splitsHigh variance; overfits badly alone
Random forestSupervisedBagging cuts variance; strong default, little tuningLarger memory; less accurate than boosting on most tabular tasks
Gradient-boosted trees (XGBoost / LightGBM / CatBoost)SupervisedThe tabular default — top accuracy; native missing-value handling, plus native categoricals in CatBoost/LightGBMSequential boosting can overfit; needs early stopping on a val set
SVMSupervisedStrong on small/medium high-dimensional data via the kernel trickScales poorly (≈O(n²–n³)); kernel + C tuning is finicky
kNNSupervisedNo training step; a simple, strong baselineSlow at inference; degrades with dimensionality (curse of dimensionality)
Naive BayesSupervisedExtremely fast; a solid text-classification baselineAssumes conditional feature independence, so probabilities are poorly calibrated
k-meansUnsupervisedFast, simple clusteringMust pick k; assumes round, equal-size clusters; sensitive to initialization
DBSCANUnsupervisedFinds arbitrary-shaped clusters and flags outliers; no k neededSensitive to eps / minPts; struggles with varying densities
PCAUnsupervisedLinear dimensionality reduction; fast, deterministic, reversible projectionCaptures only linear structure; components are not always interpretable
t-SNE / UMAPUnsupervisedNon-linear 2-D/3-D visualization of clustersFor visualization, not features; both distort global distances — UMAP's apparent edge comes mainly from its spectral init, not the algorithm (Kobak & Linderman, 2021)

Reinforcement Learning

RL is the paradigm where an agent learns by acting in an environment and receiving reward, rather than being shown labeled answers. The standard formalism is the Markov Decision Process — a 5-tuple (S, A, P, R, γ): states S, actions A, transition dynamics P(s' | s, a), reward R(s, a), and a discount factor γ ∈ [0, 1] (γ < 1 for continuing tasks, to keep the return finite) that trades off immediate vs. future reward. The goal is a policy π(a | s) maximizing expected discounted return.

  • Value-based — learn the action-value Q(s, a) and act greedily. Q-learning is the tabular off-policy classic; DQN scales it with a neural net plus a replay buffer and a target network for stability.
  • Policy-based — directly parameterize and optimize the policy via the policy gradient. REINFORCE is the pure, no-critic Monte-Carlo example (high variance).
  • Actor-critic — combine both: an actor (policy) plus a critic (value estimate) to cut gradient variance. PPO is the workhorse here — a clipped policy-gradient method with a value critic (often via GAE) that bounds each update for stability.
  • Exploration vs. exploitation — the agent must try new actions to discover reward yet exploit what it knows. ε-greedy (act randomly with probability ε, else greedily) is the simplest schedule.
  • Offline RL — learn purely from a fixed, logged dataset with no live environment interaction; the central challenge is distribution shift between the logged behavior policy and the learned policy.

Deep Learning Architectures

An architecture is a bet about the inductive bias of your data — the structural assumption baked into the network. Matching the bias to the data is what makes deep learning sample-efficient; mismatch it and a transformer will lose to a tree.

ArchitectureInductive biasTypical domain
MLP (fully connected)None — universal approximator, no structural priorTabular, general-purpose heads
CNNLocality + translation equivariance via weight-shared filters (pooling adds approximate invariance)Images, spatial / grid data
RNN / LSTM / GRUSequential recurrence with a hidden stateSequences (largely superseded by transformers)
TransformerAll-pairs self-attention; minimal prior, scales with data + computeText and most modalities — the dominant architecture
GNNPermutation invariance; message passing over graph edgesGraphs, molecules, social / recommendation networks
Autoencoder / VAECompress to a bottleneck (VAE adds a probabilistic latent)Representation learning, generative modeling, anomaly detection
GANAdversarial generator-vs-discriminator gameGenerative images (largely overtaken by diffusion)
DiffusionLearn to reverse a gradual noising processState-of-the-art image / audio / video generation
Picking a learning paradigm from your data
Module 2

Feature Engineering & Feature Stores

Features are where ML systems actually earn their keep. A feature store is the production plumbing that makes sure training and serving compute the same numbers — the single most common silent-failure mode in real ML.

Feature Types

Before encoding, classify every field. The classification dictates storage, memory, and which encoder to reach for.

  • Numeric continuous — price, age, latency. Needs normalization (z-score, min-max, or quantile).
  • Numeric discrete / count — num_clicks_7d, session_length. Log1p transform is the default.
  • Categorical low-card — country, device type. Cardinality < 10k → one-hot or learned embedding table.
  • Categorical high-card — user_id, item_id, url. Cardinality 10⁶+ → hashing trick or embedding table with row-eviction.
  • Text — titles, descriptions. Tokenize (BPE/WordPiece) → token ids → embedding.
  • Embedding / dense — already a vector (e.g., pretrained image CLIP features). Pass through as-is.
  • Sequence — last-N clicks, last-N searches. Feed through attention, mean-pool, or GRU.

Encoding

Every category must become a number before a model can consume it. The four canonical encoders, in order of when to reach for them:

  • One-hot — cardinality ≤ ~500. Simple, but size grows with cardinality.
  • Label encoding + embedding table — assign each category an integer id, look up a D-dim vector. Standard for anything 1k–100M cardinality. D typically 16–256.
  • Hashing trick — h(category) mod B. Collisions, but constant memory. Used by Vowpal Wabbit, ads click models. B = 2²⁰ is common.
  • Target encoding — replace each category with the mean of the target for that category. Powerful but leaks labels — must be computed per-fold with smoothing.
# Hashing trick in 6 lines — O(1) memory per feature
import numpy as np
HASH_BUCKETS = 1 << 20          # 1,048,576 buckets

def encode(category: str) -> int:
    return hash(category) % HASH_BUCKETS

# Embedding lookup for this id
import torch.nn as nn
emb = nn.Embedding(HASH_BUCKETS, 32)      # 32-dim embedding, ~128 MB
vec = emb(torch.tensor([encode("us-west-2")]))

Normalization

Numeric features must live on comparable scales or gradient descent will follow the dominant feature and ignore the rest. Three canonical choices:

  • Z-score: (x - μ) / σ. Default. Assumes roughly Gaussian distribution.
  • Min-max: (x - min) / (max - min). Bounded to [0, 1]; fragile to outliers.
  • Quantile / rank: replace x with its empirical percentile. Robust; destroys scale info.
  • Log1p: log(1 + x) for heavy-tailed counts (session time, clicks).

Rule: always fit normalizer stats on train only, persist them, and reuse at serve time. Computing μ,σ from the live request stream will silently leak future data.

Train/Serve Skew

Train/serve skew is the #1 silent killer of real ML systems. The model was trained on features computed one way, but production computes them slightly differently — so the live numbers are out-of-distribution and the model silently degrades.

The four causes, in order of frequency

  • Code duplication — offline feature code in Spark, online in Python. The two implementations drift. Fix: share one codebase, or use a feature store that runs both.
  • Time travel — offline feature uses "last 7 days of clicks as of now" during training, but "as of now" means the label time, not the prediction time. Fix: point-in-time joins.
  • Missing data handling — offline imputes missing with 0; online passes raw null. Fix: one imputation policy.
  • Normalizer drift — stats recomputed on new data without retraining the model. Fix: bind normalizer version to model version.

Feature Store Architecture

A feature store is a versioned database of ML features that serves both training and serving from the same code path. The canonical architecture (Feast, Tecton, Michelangelo) has four components.

Feature store: offline / online dual-write

Offline vs online stores

AspectOffline storeOnline store
MediumParquet on S3 / GCS, warehouse (BQ, Snowflake)Redis, DynamoDB, Cassandra, ScyllaDB
Access patternBatch scan, point-in-time joinsKey lookup by entity id
LatencySeconds to minutesp50 1 ms, p99 5 ms
SizePetabytes, full historyGB to low TB, latest snapshot
UseTraining, backfills, analyticsServing at prediction time

Online serve SLO

The online store must return a batch of features (for one user + N candidates) in under 10 ms p99, because the feature fetch is one hop in a request that must finish in ~200 ms total. Redis / ScyllaDB with multi-get pipelining is the standard answer.

# Feast-style point-in-time join (pseudocode)
#   label_df  has columns: user_id, ts, clicked
#   feature_view 'user_activity' has rows (user_id, ts, n_clicks_7d, ...)
#
# The join returns the latest feature row where feature_ts <= label_ts.
from feast import FeatureStore
store = FeatureStore(repo_path=".")

training = store.get_historical_features(
    entity_df=label_df,
    features=[
        "user_activity:n_clicks_7d",
        "user_activity:avg_session_min",
        "item_stats:ctr_30d",
    ],
).to_df()
Module 3

Embeddings & Vector DBs

Every modern retrieval system — search, recommendations, RAG — runs on two primitives: turn everything into a vector, then find the nearest neighbors fast. This module is about both halves.

What Is An Embedding

An embedding is a D-dimensional vector that represents a discrete object (word, user, item, image, document) such that similar objects are close in vector space. D is typically 32 for small-scale recs, 128 for items, 384–768 for sentence embeddings, and 1024–4096 for state-of-the-art text retrieval.

  • Two objects being "similar" means their cosine similarity is high (or their L2 distance is small).
  • The embedding table is just an nn.Embedding(V, D) — a V×D matrix. Row i is the embedding for token i.
  • Embeddings are learned: start random, train under some loss that pulls similar objects together and pushes dissimilar ones apart.

How Embeddings Are Trained

There are four training regimes you will be asked about.

Word2Vec / skip-gram (2013)

Predict context words from a center word. Use negative sampling: for each (center, context) positive, sample K=5–20 random words as negatives. Loss is binary cross-entropy. Trains in minutes on CPU for 10⁹ tokens. Still the baseline for cheap text embeddings.

Matrix factorization (2007)

Given a user-item rating matrix R (sparse), factor it as R ≈ U · Vᵀ. User embeddings are rows of U, item embeddings are rows of V. Loss: MSE over observed entries + L2 regularization. Solve with alternating least squares or SGD. Still the right baseline for a recommendation system interview question.

Two-tower contrastive (2019+)

Two neural networks — a query tower and an item tower — each produce a D-dim embedding. Pull (query, positive-item) pairs close, push (query, negative-item) apart with InfoNCE loss. Negatives are sampled from the batch (each row's items are positives for that row and negatives for every other row). This is the modern retrieval training recipe.

SimCLR / contrastive for images (2020)

Take an image, apply two random augmentations → x, x'. Train a network so that f(x) and f(x') are close, while f(x) and f(y) (another image) are far. InfoNCE loss with a large batch (4k–32k). Produces image embeddings without labels.

# Two-tower training loop — the retrieval-system interview special
import torch, torch.nn as nn, torch.nn.functional as F

class Tower(nn.Module):
    def __init__(self, vocab, d_in=64, d_out=128):
        super().__init__()
        self.emb = nn.Embedding(vocab, d_in)
        self.mlp = nn.Sequential(nn.Linear(d_in, 256), nn.ReLU(),
                                 nn.Linear(256, d_out))
    def forward(self, x):
        return F.normalize(self.mlp(self.emb(x).mean(1)), dim=-1)

query_tower, item_tower = Tower(10_000), Tower(1_000_000)
opt = torch.optim.AdamW(list(query_tower.parameters()) +
                        list(item_tower.parameters()), lr=1e-3)

for q_batch, pos_item_batch in loader:           # both shape [B, seq]
    q = query_tower(q_batch)                     # [B, 128]
    k = item_tower(pos_item_batch)               # [B, 128]
    logits = q @ k.T / 0.07                      # [B, B]
    labels = torch.arange(q.size(0))
    loss = F.cross_entropy(logits, labels)
    opt.zero_grad(); loss.backward(); opt.step()

ANN Indexes

Given a query vector q and N item vectors, finding the exact nearest neighbors is O(N·D) — unacceptable at N = 10⁸. Approximate Nearest Neighbor (ANN) indexes buy ~100× speedup for 1–5% recall loss. The three you must know:

HNSW — Hierarchical Navigable Small World

A layered graph: top layer is sparse with long edges, bottom layer is dense. Search greedily descends from top. Build cost high, query cost O(log N). Recall@10 > 0.95 with default params. The current production default (used by Weaviate, Milvus, Qdrant, pgvector).

HNSW layered graph (search descends top-down)

IVF — Inverted File Index

K-means cluster all vectors into n_list centroids (typically √N, so ~10k for 100M vectors). At query, compute distance to every centroid, visit the n_probe closest (e.g. 16), and exhaustively scan their cluster. Recall tunable by n_probe. Memory-efficient; the default in Faiss.

PQ — Product Quantization

Split each D-dim vector into M chunks of D/M dims. K-means each chunk independently with 256 centroids → each chunk becomes 1 byte. A 768-dim float32 vector (3 KB) compresses to M=96 bytes. 32× compression. Usually combined with IVF → IVF-PQ.

IndexBuildQueryMemoryRecallUse
Flat (brute force)FreeO(N·D)4 B × N × D1.0N < 10⁵ or ground truth.
HNSWO(N log N)O(log N)~8× flat0.95+10⁶–10⁸, recall priority.
IVFK-meansO(n_probe · N/n_list)~flat0.85–0.95Tunable, moderate.
IVF-PQDouble quant~O(n_probe · N/n_list)~1/32 flat0.80–0.9010⁹+ vectors, RAM-bound.

Vector DB Choice

SystemIndexScaleSweet spot
pgvectorIVF + HNSW< 10⁷Already have Postgres; one fewer service to run.
Pinecone (managed)Proprietary graph10⁹Zero-ops, SaaS, fast go-to-market.
WeaviateHNSW10⁹Hybrid search (keyword + vector), GraphQL API.
Milvus / ZillizHNSW, IVF, DiskANN10¹⁰Largest scale, most indexes, K8s-native.
QdrantHNSW10⁹Rust, fast, rich payload filter.
Faiss (library)AnythinganyBuild-your-own; no distributed mgmt.
Module 4

Recommenders

The archetypal FAANG ML systems question: "design YouTube recommendations." Every good answer has the same bone structure — candidate generation, ranking, re-ranking — filled with different algorithms per stage.

Collaborative Filtering

Collaborative filtering assumes "users who liked similar things will like similar new things." The signal is the user-item interaction matrix, no content features. Two flavors:

  • User-based: for user u, find the top-K most similar users (cosine over rating vectors), recommend their top items.
  • Item-based: for each item i, precompute the top-K most similar items. At serve time, aggregate the similar items of u's recent interactions. This is what Amazon famously used in the 2000s ("people who bought this also bought").

Item-based wins in practice because you precompute item-item similarities offline (expensive but batched), and online lookup is just a hash join. User-based requires recomputing per user at query time.

Matrix Factorization

Assume each user u has a latent vector p_u ∈ ℝᴰ, each item i has q_i ∈ ℝᴰ, and rating r_ui ≈ p_u · q_i. Solve min Σ (r_ui - p_u · q_i)² + λ(||p||² + ||q||²). ALS and SGD both work; ALS parallelizes better (Spark MLlib).

# Matrix factorization with SGD — the Netflix Prize recipe
import numpy as np
U, I, D = 100_000, 500_000, 64
P = np.random.randn(U, D) * 0.01
Q = np.random.randn(I, D) * 0.01
lr, reg = 0.01, 0.02

for u, i, r in interactions:             # (user, item, rating)
    pred = P[u] @ Q[i]
    err  = r - pred
    P[u] += lr * (err * Q[i] - reg * P[u])
    Q[i] += lr * (err * P[u] - reg * Q[i])

Two-Tower Retrieval

The modern drop-in for MF: one neural net per side. Query tower consumes user features + recent context and emits a D-dim vector. Item tower consumes item features and emits a D-dim vector. Train with InfoNCE. Serve by indexing every item's tower output in an ANN index and doing one ANN lookup per query.

  • Input to query tower: user_id embedding, recent N clicks (sequence), country, device, time-of-day.
  • Input to item tower: item_id embedding, category id, creator id, title text embedding, image embedding.
  • Output: L2-normalized 128-dim vector on both sides. Similarity = dot product.
  • Item tower output is precomputed offline for the full catalog and pushed to the ANN index nightly.
  • Query tower runs at request time per user.

Two-tower trades some accuracy for enormous serving efficiency: a single ANN lookup replaces scoring millions of items.

Deep Retrieval & DCN

Once candidates are retrieved, you rank them with a much richer model that can consume the full cross-product of features. This is where Deep & Cross Networks (DCN v2), DLRM, and Wide & Deep live.

  • Wide & Deep (Google, 2016): Wide arm is a linear model over crossed features (memorization); deep arm is a 3-layer MLP over embeddings (generalization). Sum the logits, apply sigmoid.
  • DCN / DCN-v2: Explicitly learn feature crosses to arbitrary order via a cross layer: x_{l+1} = x_0 · (W·x_l + b) + x_l. Replaces hand-crafted crosses.
  • DLRM (Meta, 2019): Dense features through bottom MLP, sparse features through embedding tables, all pairwise dot products, concat, top MLP. Standard for ads.

3-Stage Pipeline

Candidate generation → Ranking → Re-ranking

Why three stages, not one

A single model that scores all 10⁹ items per request would need 10⁹ × feature_fetch × 10⁶ QPS = impossible. The three-stage funnel trades accuracy for throughput by using progressively more expensive models on progressively smaller candidate sets.

StageInput sizeOutput sizeLatency budgetModel
Candidate generation10⁹ items~100010 msTwo-tower + ANN (HNSW)
Ranking~1000~10020 msDLRM / DCN-v2 / GBDT
Re-ranking~100~105 msHeuristic + LambdaMART + rules

Common candidate-gen sources (union them)

  • Two-tower ANN — personalized 500 items.
  • Item-to-item co-visitation — 200 items similar to what user just watched.
  • Popularity in user's country/language — 100 items, combats cold start.
  • Recent items from subscribed channels — explicit follow signal.
  • Fresh items (uploaded last 24h) — exploration slot.
Module 5

Ranking Systems

Ranking is the middle stage of the funnel: given ~1000 candidates, score them and return the top ~100. Interviewers test whether you know which loss goes with which problem, and why position bias ruins naive models.

Pointwise / Pairwise / Listwise

Three ways to supervise a ranker, in increasing sophistication.

  • Pointwise — treat each (query, item) as an independent classification problem. Predict P(click | q, i). Loss is binary cross-entropy. Simple, fast to train, but the model does not know about ranking structure.
  • Pairwise — sample pairs (q, i⁺, i⁻) where i⁺ is clicked and i⁻ is not. Train the model so score(q, i⁺) > score(q, i⁻). Losses: RankNet (logistic over score difference), pairwise hinge. Matches ranking intuition better.
  • Listwise — loss operates on an entire ranked list. LambdaRank / LambdaMART weights each pair by the Δ-NDCG it would cause. ListNet applies softmax over the list. Highest accuracy but expensive to train.
# Pairwise RankNet loss — four lines
# s1 = score of item1, s2 = score of item2, y = 1 if item1 preferred else 0
import torch.nn.functional as F
def ranknet(s1, s2, y):
    # probability that 1 > 2 under the model
    p = torch.sigmoid(s1 - s2)
    return F.binary_cross_entropy(p, y)

Feature Crosses

A feature cross is a synthetic feature made by combining two raw features (e.g., user_country × item_language). Cross features model interactions that a linear model or simple MLP cannot learn at reasonable width.

  • Wide models (logistic regression, FTRL) rely entirely on hand-crafted crosses. Production ads systems have crossed 10⁴–10⁶ features.
  • DCN / DCN-v2 learn cross interactions up to arbitrary order with a stack of cross layers. No manual crossing.
  • DLRM: pairwise dot products between all embedding-table outputs implicitly produce second-order crosses.

Second-order cross has |A| × |B| cells; above 10⁶ you need the hashing trick.

LambdaMART, DLRM, MMoE

LambdaMART

A GBDT (LightGBM, XGBoost) trained with pairwise gradients weighted by Δ-NDCG. For years the dominant search ranker (Bing, Yandex). Fast at training time, interpretable feature importances. Weakness: can't ingest raw sequences or embeddings directly.

DLRM

Facebook's open-source click-through ranker. Two-part architecture:

  • Bottom MLP over dense features (age, CTR history, prices).
  • Embedding tables for sparse features (user_id, ad_id, page_id). Tables are enormous (100s of GB) and embedding-parallel across GPUs.
  • All embeddings + dense_bottom are pairwise-dotted, concatenated, fed through top MLP → sigmoid.

MMoE — Multi-gate Mixture-of-Experts

A multi-task architecture used by YouTube & Google Ads to jointly predict multiple outcomes (click, watch time, like, share). Shared experts produce N representations; each task has a gate that weights them. Lets correlated tasks benefit each other while avoiding negative transfer on uncorrelated ones.

MMoE shared experts with per-task gates

Position Bias & Exploration

Position bias: the higher an item appears, the more likely the user is to click it, independent of relevance. A naive click model trained on logged clicks learns "ranked first → high CTR," which collapses to a self-fulfilling feedback loop.

Three remedies

  • Position feature at train, zeroed at serve — include the position as an input at training time so the model attributes some CTR to position; at serve, set position = 0 (or a reference value) so the model doesn't use it for ranking.
  • Inverse Propensity Scoring — reweight each training sample by 1/P(observed | position).
  • Counterfactual / bandit approaches — log probabilities at serve time, use doubly robust estimators.

Exploration

An always-greedy ranker never collects data on items it ranks low. Three exploration strategies:

  • ε-greedy — with probability ε=1–5%, serve a random candidate in the top slot. Simple.
  • Thompson sampling — score = model prediction + noise from posterior uncertainty. Decays as confidence grows.
  • Dedicated exploration slots — reserve 1-of-10 result slots for fresh/cold-start items.
Module 6

LLM Serving Infrastructure

Serving a 70B-parameter transformer at thousands of QPS is not a math problem, it's a memory-bandwidth problem. Every optimization — KV cache, paged attention, continuous batching, speculative decoding, quantization — exists to squeeze more tokens per second out of a bandwidth-bound GPU.

KV Cache & Paged Attention

Decoder-only transformers generate one token at a time. At each step, the model computes attention over all previous tokens. Naively, that means re-running the full attention every step — O(L³) for a sequence of length L.

The KV cache is the standard fix. During the prefill pass you compute the Key and Value tensors for every input token and store them. During decoding, each new token only computes its own Q and attends against the cached K,V. Decoding drops to O(L) per step.

Paged Attention (vLLM, 2023)

Classical KV cache allocates one contiguous buffer per request, sized to max context length. That wastes memory: a request producing 100 tokens reserves space for 4096. Paged Attention splits KV cache into fixed-size blocks (typically 16 tokens) and keeps a per-request page table. Requests grab blocks on demand; fragmentation drops from ~60% to ~4%. This is what let vLLM run 2–4× more concurrent requests than early contiguous-cache servers.

Continuous (In-Flight) Batching

Static batching stalls because sequences finish at different times. If you batch 8 requests and one wants 4000 tokens while seven want 100, the GPU idles for those seven after step 100. Continuous batching (also called in-flight batching) evicts finished sequences from the batch and admits waiting requests every iteration.

Static vs continuous batching over time

Continuous batching typically raises throughput 3–5× vs static batching at identical latency, especially under mixed prompt lengths.

Speculative Decoding

Autoregressive decoding is serial: you must generate token N before N+1. Speculative decoding uses a small cheap draft model to guess K tokens, then runs the big model once to verify all K in parallel. If the draft model agrees with the big model on k tokens, you get k tokens in one big-model forward pass.

  • Draft model: a 1B or 7B variant of the big model family.
  • Target model: 70B or larger.
  • Speedup: 2–3× on average; zero quality loss (the big model rejects any incorrect draft token).
# Speculative decoding outline
def spec_decode(target, draft, prompt, K=4):
    x = prompt
    while not finished(x):
        # Draft produces K tokens cheaply
        draft_tokens = draft.generate(x, K)
        # Target verifies all K in ONE forward pass
        target_logits = target(x + draft_tokens)
        # Accept longest prefix where target agrees with draft
        k = longest_accept(draft_tokens, target_logits)
        x = x + draft_tokens[:k]
        # Always append one extra sample from the target's own distribution
        x = x + sample(target_logits[k])
    return x

Quantization

Weights are stored in fp16 or bf16 during training (16 bits each). At serve time, you can drop precision without much accuracy loss — moving less memory per token is the fastest way to speed up a bandwidth-bound GPU.

FormatBitsMemory for 70BQualityNotes
FP16 / BF1616140 GBBaselineTraining + inference default.
INT8 (W8A16)8 weights70 GB~0.1 pt dropLLM.int8, SmoothQuant.
INT4 (W4A16)4 weights35 GB~0.5 pt dropGPTQ, AWQ. Fits 70B on one 80 GB H100.
FP88 w+a70 GB~0 dropH100 native; ideal if hardware supports it.

Serving Stacks

  • vLLM — open-source; paged attention; continuous batching; prefix/prompt caching; tensor-parallel multi-GPU. The default starting point for self-hosting, with the broadest hardware support and largest community.
  • SGLang — open-source; RadixAttention for automatic KV-prefix reuse (big wins on RAG and multi-turn chat); structured/constrained decoding; continuous batching. A top-tier engine alongside vLLM and TensorRT-LLM.
  • NVIDIA Triton + TensorRT-LLM — maximum throughput on NVIDIA hardware; proprietary kernels; in-flight batching; can edge out vLLM 30–50% under high concurrency, but complex to configure.
  • TGI (Hugging Face) — similar features; tighter integration with the HF model hub; Rust router.
  • Ray Serve + vLLM/SGLang backend — when you also need complex routing and scaling.

Across all of these, automatic prefix/prompt caching and disaggregated prefill/decode (running the compute-bound prefill and the bandwidth-bound decode on separate GPU pools) are now table-stakes — name them when asked how to push throughput further.

Module 7

Training Pipelines

A production training pipeline is a dataflow graph — data lake, processing, training, eval, registry, promotion — that runs on a schedule and emits a new model blob that gets canary-promoted into serving. Every box is a failure point.

Lake → Registry

Training pipeline: 7 stages
  • Data lake: S3/GCS Parquet, partitioned by date. Append-only, immutable; the source of truth.
  • Processing: Spark, Beam, or Flink jobs; compute features + labels; write tfrecords or parquet for trainer consumption.
  • Training: orchestrated by Airflow / Kubeflow / Flyte; runs on GPU cluster; writes to object store.
  • Eval: offline metrics on held-out data; slice metrics (new users vs tenured users, each locale, etc.).
  • Model registry: MLflow / SageMaker / Vertex. Stores weights + metadata + lineage (data version, code sha).
  • Canary: deploy to 1% traffic; automated guardrails on error rate, latency, online metric delta.
  • Promotion: if guardrails clean for 24–72h, ramp to 100%. Otherwise rollback.

Hardware

AcceleratorHBMFP16 TFLOPsInterconnectWorkload
A100 80GB80 GB312NVLink 600 GB/s, InfiniBand HDR 200 Gbps7B–30B dense, any recs training.
H100 80GB80 GB HBM31000 (FP16), 2000 (FP8)NVLink 900 GB/s, IB NDR 400 GbpsFrontier LLM training.
H200141 GB HBM3e1000Same as H100Long-context inference.
TPU v4 / v532 / 96 GB275 / 459ICI torus 3DGoogle-only; large batches.
MI300X (AMD)192 GB1300Infinity FabricHBM-rich inference.

DDP / FSDP / ZeRO

Data-parallel (DDP)

Replicate the whole model on every GPU. Each GPU sees a different batch slice. After backward, AllReduce the gradients. Works until the model fits in one GPU's memory.

FSDP / ZeRO

Shard the model parameters, gradients, and optimizer state across GPUs. During forward, AllGather the needed weights; during backward, ReduceScatter the gradients. Memory drops linearly with world size. ZeRO has three stages:

  • ZeRO-1: shard optimizer state only (Adam state is 12 B/param vs weights at 2 B/param fp16).
  • ZeRO-2: shard gradients too.
  • ZeRO-3 (= FSDP): shard parameters too. Equivalent to fully-sharded data-parallel.

Tensor / Pipeline parallel

Very large models (70B+) don't fit even under FSDP. Tensor parallel splits matmuls across GPUs (Megatron-LM). Pipeline parallel splits layers across GPUs, introducing bubble overhead. Combine with DP as 3D parallelism.

# FSDP in PyTorch — the one-line upgrade from DDP
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy

model = FSDP(
    model,
    auto_wrap_policy=transformer_auto_wrap_policy,
    mixed_precision=MixedPrecision(
        param_dtype=torch.bfloat16,
        reduce_dtype=torch.float32,     # keep reductions in fp32 for stability
        buffer_dtype=torch.bfloat16,
    ),
)

Mixed Precision & Checkpointing

  • Mixed precision (bf16 / fp16): keep a master copy of weights in fp32, cast to bf16 for forward/backward. Throughput doubles; bf16 has the same exponent range as fp32 so it rarely overflows (fp16 needs loss scaling).
  • Gradient accumulation: effective batch size = micro_batch × accumulation_steps × world_size. Lets you match a target batch size on small hardware.
  • Gradient / activation checkpointing: recompute forward activations during backward instead of storing them. Trades ~30% extra compute for ~50% less activation memory. Mandatory past ~7B params.
Module 8

Evaluation & A/B Testing

Offline metrics tell you whether a model is better on old logs. Online A/B tests tell you whether users actually react. Every FAANG launch gets gated on an A/B test — know how to design one, not just how to read one.

Offline Metrics

MetricRangeTaskWhat it measures
AUC / ROC-AUC0.5–1.0Binary classificationP(score(pos) > score(neg)). Ranking quality, ignores calibration.
Log-loss0–∞Prob. classificationBoth ranking and calibration. Required for bidding models.
NDCG@k0–1RankingDiscounted gain with logarithmic position decay. Default for search.
MRR0–1Single correct answerReciprocal rank of the first relevant item. QA, nav queries.
MAP0–1Multi-label rankingMean average precision. Document retrieval.
Recall@k0–1RetrievalFraction of relevant items in top-k. Candidate-gen eval.
RMSE / MAE0–∞RegressionScale-dependent; report alongside baseline.

Online Metrics

  • CTR (click-through rate) — short-term signal; easy to game via clickbait.
  • Watch time / session length — medium-term engagement; the canonical YouTube metric.
  • Retention / DAU / WAU — long-term; slow and noisy; the ultimate product metric.
  • Revenue per mille (RPM) — ads; directly ties model quality to dollars.
  • Guardrail metrics — latency p99, error rate, complaint rate, blocked-content rate. Must not regress.

A/B Design

A well-formed A/B test has six pieces:

  • Unit of randomization — user_id for most ML (session_id for logged-out). Never per-request; within-user spillover contaminates both arms.
  • Hypothesis — "new ranker increases watch-time by ≥ 0.5% with p < 0.01, no guardrail regression."
  • Primary metric — one pre-registered metric. Adjust for multiple comparisons if you're tracking more.
  • Sample size / power — computed from MDE (minimum detectable effect) and baseline variance. Typical FAANG A/B: 7–14 days, millions of users.
  • Traffic split — 50/50 if no risk; 95/5 with 1% canary first if guardrails unknown.
  • Stopping rule — fixed horizon or sequential (always-valid p-values, mSPRT).

Sample size, back-of-envelope

# For a continuous metric, two-sided test, α=0.05, power=0.80:
# n per arm ≈ 16 × σ² / Δ²
# σ = stdev of metric per user
# Δ = minimum detectable effect (absolute)
#
# For watch time: σ ≈ 30 min, Δ = 0.6 min → n ≈ 16 × 900 / 0.36 = 40,000/arm

CUPED & Interference

CUPED (Controlled-experiment Using Pre-Existing Data)

Variance reduction via pre-experiment covariates. Compute ŷ = y - θ · (x_pre - E[x_pre]) where x_pre is each user's metric value before the experiment. Lowers variance 30–50%, which cuts required sample size 30–50%. Standard at Microsoft, Netflix, Booking.

Interference

Classical A/B assumes units are independent. ML violates this in three ways:

  • Two-sided marketplaces (Uber, Airbnb): treating some riders affects driver availability for control. Fix: cluster randomization (by city / day).
  • Social spillover (Facebook feed): treated user's post seen by control user. Fix: ego-network randomization.
  • Learning loops: both arms write to the same candidate-gen training data → pollution. Fix: separate log streams.
MODULE 9

ML System Design Case Studies

Production pipelines at FAANG scale.

YouTube Recommendations

Scale: 2B DAU, billions of watch hours per day, > 500 hrs uploaded per minute. Candidate corpus 10⁸ videos.

Pipeline: Two-stage candidate generation → ranker → re-ranker. Candidate gen: two-tower (user tower + video tower) + ANN (HNSW) retrieval ~O(10³) candidates. Ranker: heavy DNN (Deep & Cross) on ~10³ cands returns score per item. Re-ranker: diversity / fairness / policy filters.

Features: user demographics, watch history embeddings, context (device, geo, time-of-day, query if searched), video side features (category, uploader, length, freshness). Training data: implicit (watch time > threshold) and explicit (like, subscribe).

YouTube 2-stage recommendation pipeline

Google Search Ranking

Lexical + semantic hybrid. BM25 on inverted index for initial top-K (10⁴), neural ranker (BERT-like cross-encoder) for re-rank top 10². Quality signals: PageRank, user engagement clicks, freshness, authority.

Netflix Personalization

Row-level selection: each shelf ("Trending", "Because you watched X", "New releases") has own candidate gen + ranker. Artwork personalization: multi-armed bandit picks per-user thumbnail.

TikTok For-You Page

Dense retrieval + heavy engagement feedback loop. Candidate generation uses user/video twin-tower embeddings + content-based + social graph. Ranker predicts finish-watch probability + like + share. Tight feedback: < 1 hr model refresh on recent engagement.

Ads Ranking

Expected revenue = bid × pCTR × pCVR. Second-price auction on ranked candidates. pCTR model is logistic regression on feature crosses (historically) → DCN / DLRM (modern). Budget pacing: smooth spend across day via throttling.

Ads ranking — expected revenue auction

GitHub Copilot Serving

Client keystroke → context extraction (cursor + nearby files + imports) → serverless LLM call with KV cache + speculative decoding. Latency target: < 300ms first token. Quality gates: license filtering, secrets detection on output.

Real-Time Fraud Detection (Fintech)

This is the canonical fintech ML case study, and the one where naive metric choices fail loudest. You must authorize or decline a payment in the time it takes a card terminal to spin, learn from labels that arrive weeks late, and defend every decision to a regulator. Frame it as a cost-sensitive, extremely-imbalanced binary classifier embedded in a hard-real-time decisioning loop.

Context & Requirements

Functional: for each transaction, return one of approve, decline, step-up auth (e.g. 3-D Secure / OTP), or route to manual review. A rules engine and a model jointly produce the decision; the model emits a calibrated fraud probability plus reason codes.

  • Latency: the fraud-scoring and decision service budget is < 100ms p99 — this is the internal slice, not the full card-auth round trip (issuer↔network↔acquirer is typically ~1–2s). The model must score inside a sliver of that window so rules, retries, and network hops still fit the authorization timeout.
  • Maximize fraud recall at a strict cap on the false-positive (false-decline) rate: a blocked legitimate purchase is a lost sale, a support call, and a churned customer. The business objective is dollar-weighted, not accuracy.
  • Extreme class imbalance: fraud is typically ~0.1–1% of transactions (varies by segment, channel, and geography). Naive accuracy is useless — a "always approve" model scores > 99%.
  • Adversarial + concept drift: fraudsters actively probe and adapt, and spend patterns shift (holidays, new merchants). The data distribution is non-stationary and partly chosen by an opponent.
  • Explainability + compliance: decisions must produce reason codes for disputes, fair-lending / UDAAP scrutiny, and model governance; the modeling lifecycle is itself audited.

High-Level Architecture

Flow: transaction → online feature service (real-time velocity / aggregation features, device & IP signals, and graph / entity-link features served from a feature store) → model scoringhybrid with a rules engine (hard rules for known-bad BINs, sanctioned entities, blocklists; model handles the fuzzy middle) → decision (approve / decline / step-up / manual review) → delayed labels (chargebacks & disputes) flow back into training.

  • Online vs offline parity: the same feature definitions must produce identical values at training time (batch) and serving time (online store) — train/serve skew is the #1 silent killer here.
  • Velocity features: windowed counts/sums ("cards used on this device in 1h", "amount spent in 10m") computed in a streaming layer and read at single-digit-ms latency from the online store.
  • Entity / graph layer: link card ↔ device ↔ IP ↔ merchant ↔ shipping address to surface shared fraud infrastructure.
Real-time fraud decision pipeline (with delayed-label feedback)

Modeling Deep Dive

Imbalance handling: reach for class weights (up-weight the positive/fraud class in the loss), focal loss (its (1−p_t)^γ modulator down-weights easy, well-classified examples so gradients concentrate on hard ones — in fraud, the rare positives plus hard negatives), and careful undersampling of the majority (but re-calibrate probabilities afterward, since sampling distorts the base rate). Avoid blind oversampling/SMOTE on high-cardinality categorical and graph features — it fabricates implausible entities.

  • Features: velocity/aggregation windows, device/IP fingerprints, entity resolution (collapsing aliases of the same actor), and graph features (shared-device degree, connected-component size).
  • Models: gradient-boosted trees (XGBoost / LightGBM) are the strong tabular baseline and frequently the production workhorse — fast to score and easy to explain. GNNs earn their keep for ring / collusion fraud, where the signal lives in the graph topology (mule networks, bust-out rings) rather than any single transaction.
  • Cold-start: new cards/users/merchants lack velocity history — fall back to population priors, device reputation, and rules until enough events accumulate.

Serving & Monitoring

  • Online feature store for low-latency reads; precompute and stream the expensive velocity/graph aggregations rather than computing them on the request path.
  • Champion / challenger + shadow: run a new model in shadow on live traffic (scored, not acted on) and as a challenger against the champion before promotion, since you cannot freely A/B with real money at risk.
  • Monitor: data drift and score drift (the distribution shifts adversarially), fairness / disparate-impact across protected segments, and the bottom line — dollar-loss: fraud $ caught vs. $ of false declines. Optimize the money, not the F1.

Regulatory & Governance

  • Model risk management: SR 11-7 (Fed/OCC, 2011) governs model development, validation, and ongoing monitoring — applies cleanly here and is the framework interviewers expect you to name.
  • Explainability: SHAP-style attributions drive reason codes for disputes, manual-review queues, and fair-lending/UDAAP review.
  • Adverse-action nuance: formal adverse-action notices under ECOA / Reg B (and FCRA when a third-party consumer report is used) attach to credit decisions, not cleanly to a real-time fraud decline. So the hard reason-code mandate lives mostly on the credit-scoring sibling problem — which shares the imbalance and explainability constraints (and, for instant / BNPL decisions, the latency pressure too) — while fraud teams invest in reason codes for disputes, fairness scrutiny, and CX rather than a strict ECOA requirement.

Failure Modes

  • Feedback delay / label leakage: chargebacks land weeks after the transaction, so recent data is partly unlabeled — using time-aware splits and avoiding future features is essential.
  • Train/serve skew from divergent feature pipelines silently degrades the live model.
  • Feedback loop bias: declined transactions never produce a "would-have-been-fine" label, so the model only sees the outcomes of its own approvals (selection bias).
  • Adversarial adaptation: a static model decays fast as fraudsters reverse-engineer the boundary.
MODULE 10

Cheat Sheet

ML interview framework + memorizable numbers.

ML System Design Framework

  1. Clarify — business metric, user segment, scale (QPS, corpus size), latency budget, fairness / policy constraints.
  2. Data — label source (implicit/explicit), training window, sampling bias, feedback loop.
  3. Features — user, item, context; feature store (online vs offline); crosses.
  4. Model — baseline (logistic / GBDT) → deep (two-tower / DCN / Transformer). Justify upgrade with offline lift.
  5. Serving — batch vs online, ANN vs exact, KV cache, quantization, GPU vs CPU.
  6. Evaluation — offline (AUC, NDCG@k), online A/B (primary metric + guardrails).
  7. Monitoring — feature drift, model decay, train/serve skew, engagement anomalies.

Numbers to Memorize

  • Embedding dim: 64–256 common for retrieval; 768 for BERT-base; 8192 hidden for Llama 3.3-70B (4096 for the 7B/8B class).
  • ANN latency: HNSW M=16, ef=200 → P99 < 10ms on 10⁸ vectors.
  • Transformer FLOPs: ~6 × N × D² per token for decoder.
  • KV cache size: 2 × L × H × d_h × seq_len × batch × bytes_per_val.
  • GPU utilization: well-tuned training should hit > 50% FLOP utilization.
  • A/B test sample size: ~10⁴–10⁶ users per arm for most product metrics (with CUPED lowering this 30–50%).
MODULE 11

Drift Monitoring, RAG Design & Fine-Tuning

Production monitoring, the RAG case study, and adaptation methods the page skips.

Data & Prediction Drift: PSI and the KS Test

A model does not "break" — the world moves under it. Data drift is a shift in the input distribution P(X); concept drift is a shift in P(Y|X), the actual input→label relationship. You cannot always measure concept drift immediately (labels lag), so you continuously watch input drift and prediction drift (the distribution of the model's own outputs) as early proxies.

  • PSI (Population Stability Index) — bins a feature (or score) into ~10 buckets from a reference window, then compares the current window's mass per bucket. PSI = Σ (actual% − expected%) · ln(actual% / expected%).
  • KS test — non-parametric; the KS statistic D is the max vertical gap between the reference and current empirical CDFs. Great for continuous features; a low p-value flags a distribution change.
  • Prediction drift — if your fraud model's mean score jumps from 0.03 to 0.11 with no code change, something upstream shifted (a feature pipeline bug or a real population change).
PSI valueInterpretationAction
< 0.1No significant shiftKeep serving
0.1 – 0.25Moderate shiftInvestigate, raise a warning
> 0.25Major shiftPage on-call, consider retrain
import numpy as np
from scipy.stats import ks_2samp

def psi(expected, actual, bins=10):
    # Fixed bin edges from the reference (training) distribution
    edges = np.quantile(expected, np.linspace(0, 1, bins + 1))
    edges[0], edges[-1] = -np.inf, np.inf
    e = np.histogram(expected, edges)[0] / len(expected)
    a = np.histogram(actual,   edges)[0] / len(actual)
    e = np.clip(e, 1e-4, None)   # avoid div-by-zero / log(0)
    a = np.clip(a, 1e-4, None)
    return float(np.sum((a - e) * np.log(a / e)))

ref, live = np.random.normal(0, 1, 10000), np.random.normal(0.4, 1, 5000)
print(psi(ref, live))                 # e.g. 0.27  -> major shift
print(ks_2samp(ref, live).pvalue)     # e.g. 1e-40 -> distributions differ

Model Decay, Alerting & Feedback Loops

Drift metrics are leading indicators; quality metrics are the ground truth. The problem is that labels arrive late — a loan default is known months later, a chargeback in 30–90 days. Build the monitoring stack in layers so you catch problems before labels confirm them.

  1. Operational — latency p50/p99, error rate, throughput, null-feature rate. Fastest signal; a broken feature job shows here first.
  2. Input/prediction drift — PSI/KS per feature and on the score. Minutes-to-hours latency.
  3. Delayed quality — AUC, precision@k, calibration, computed as labels land. The truth, but slow.
  • Alert on rate-of-change, not raw values — a static "AUC < 0.8" threshold pages constantly. Alert when AUC drops > 3 points vs a 7-day trailing baseline.
  • Segment your metrics — aggregate AUC can look flat while a new geography or device cohort silently degrades. Slice by the dimensions that matter.
  • Feedback loops — logging model scores that influence future labels creates bias. A credit model only sees repayment for people it approved (selection bias); a recommender's clicks reflect what it already showed (exposure bias). Reserve a small random-action holdout to collect unbiased labels.

Shadow & Canary Deploys for Models

Offline metrics (AUC on a held-out set) rarely predict online lift. Never flip 100% of traffic to a new model. Use progressive rollout so a bad model is caught on 1% of traffic, not all of it.

PatternHow it worksCatches
Shadow (dark launch)New model scores live traffic in parallel; its output is logged but not servedLatency, crashes, prediction drift vs prod — zero user risk
CanaryRoute 1% → 5% → 25% → 100% of real traffic to the new model, watch metrics at each stepReal online quality & business KPIs
A/B / interleavingRandomized split, measure business metric with significanceTrue causal lift (the metric that matters)
Blue-greenTwo full stacks; flip the router, instant rollbackFast recovery from a bad release
# Canary router keyed on a stable hash so a user sticks to one variant
import hashlib

def route(user_id: str, canary_pct: int) -> str:
    h = int(hashlib.md5(user_id.encode()).hexdigest(), 16) % 100
    return "canary" if h < canary_pct else "stable"

RAG System Design: Chunk → Embed → Retrieve → Rerank

Retrieval-Augmented Generation grounds an LLM in an external corpus so it answers from your data with citations, without retraining. The end-to-end path is: ingest & chunk → embed → index → retrieve → rerank → assemble context → generate. Each stage has a real failure mode.

  • Chunking — split on semantic boundaries (headings, paragraphs), not arbitrary byte offsets. Target ~256–512 tokens with 10–20% overlap so a fact split across a boundary survives. Too large → diluted embeddings and wasted context; too small → lost context.
  • Embedding + index — encode each chunk to a dense vector (e.g. 768–1536 dims) and store in a vector DB (FAISS, pgvector, Qdrant) with HNSW/IVF for approximate-NN search. Use the same model for query and document.
  • Retrieval — fetch top-k (k≈20–50) by cosine similarity. Add lexical BM25 and fuse (hybrid search) so exact IDs, error codes, and rare terms — which dense vectors miss — still match.
  • Reranking — a cross-encoder scores each (query, chunk) pair jointly and re-sorts, keeping the top 3–5. Far more accurate than bi-encoder cosine because it attends across the pair; too slow to run on the whole corpus, perfect for reranking 50 candidates.
# Two-stage retrieval: cheap dense recall, then precise cross-encoder rerank
cands = vector_db.search(embed(query), top_k=50)          # bi-encoder recall
pairs = [(query, c.text) for c in cands]
scores = cross_encoder.predict(pairs)                     # e.g. bge-reranker
top = [c for _, c in sorted(zip(scores, cands),
                            key=lambda x: x[0], reverse=True)][:5]
context = "\n\n---\n\n".join(f"[{i+1}] {c.text}" for i, c in enumerate(top))

Context Assembly, Eval & Hallucination Guards

Retrieval quality and generation quality fail independently, so you must evaluate both. The RAG triad decomposes correctness into three measurable axes.

MetricQuestion it answersFailure it catches
Context relevanceAre the retrieved chunks about the query?Bad retrieval / chunking
Faithfulness (groundedness)Is every claim supported by the context?Hallucination
Answer relevanceDoes the answer address the question?On-topic but useless replies
  • Retrieval metrics — Recall@k and MRR/nDCG on a labeled query→chunk set tell you if the answer was even reachable. If Recall@k is low, no prompt engineering saves you.
  • Hallucination guards — (1) instruct "answer only from the context; if it is not there, say you don't know"; (2) require inline citations [1][2] and verify each cited chunk exists; (3) run an LLM-judge or NLI faithfulness check that flags unsupported sentences; (4) set a similarity floor — if the best chunk scores below threshold, refuse rather than guess.
  • Context assembly — dedupe near-identical chunks, order by rerank score, and prepend a system instruction + the user question. Trim to fit the context budget, keeping highest-scored chunks.

Fine-Tuning & Adaptation: LoRA, RLHF vs DPO, Distillation

Adaptation techniques form a ladder of cost. Full fine-tuning updates all weights (billions of params, expensive, risks catastrophic forgetting). PEFT methods freeze the base and train a tiny add-on.

  • LoRA (Low-Rank Adaptation) — freeze W, learn a low-rank update ΔW = B·A where B is d×r and A is r×d with rank r≈8–64. You train ~0.1–1% of params, and adapters are ~MBs you can swap per tenant. QLoRA adds 4-bit base quantization so a 7B model fine-tunes on a single 24 GB GPU.
  • RLHF (PPO) — train a reward model on human preference pairs, then RL-optimize the policy against it. Powerful but a 3-model dance (policy, reward, reference), unstable, reward-hackable.
  • DPO (Direct Preference Optimization) — skips the reward model; a closed-form loss directly raises the log-prob of the "chosen" reply over the "rejected" one, regularized against a frozen reference. Simpler, cheaper, more stable — now the default for preference tuning.
  • Knowledge distillation — train a small "student" to match a large "teacher's" soft output distribution (or generated data). Ships a fast, cheap model at ~90%+ of teacher quality.
from peft import LoraConfig  # attach LoRA adapters to attention projections
cfg = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                 target_modules=["q_proj", "v_proj"], task_type="CAUSAL_LM")
# scale applied to B*A is alpha/r; only A,B train, base weights stay frozen
NeedReach forWhy
Fresh/changing facts, citationsRAGUpdate the index, no training; grounded & attributable
New format, tone, or skill/behaviorFine-tune (LoRA)Teaches style/behavior weights, not facts
Small tweak, fast iterationPrompt / few-shotZero training cost, instant to change

Streaming Feature Pipelines: Kafka, Flink & Point-in-Time Correctness

Real-time models (fraud, ranking, recommendations) need features computed from events seconds old — "purchases in the last 5 minutes," "clicks this session." That means a streaming pipeline, and it introduces two hard correctness problems: freshness and training/serving skew.

  • Kafka → Flink — Kafka is the durable, partitioned event log; Flink (or Spark Structured Streaming) consumes it and maintains stateful windowed aggregations (tumbling/sliding/session windows) that write to an online store (Redis/DynamoDB) for low-latency reads.
  • Freshness & TTL — an online feature has a staleness budget. A "count in last 5m" feature must update within seconds; set a TTL so a stale value expires rather than serving a frozen number after the stream stalls. Monitor feature lag (event time − processing time).
  • Event time vs processing time — use event-time windows with a watermark to tolerate out-of-order/late events; processing-time windows silently miscount when a consumer falls behind.
-- Point-in-time join: newest feature value at or before each event
SELECT e.event_id, e.label, f.txn_count_5m
FROM   events e
LEFT JOIN LATERAL (
  SELECT txn_count_5m
  FROM   features f
  WHERE  f.user_id = e.user_id
    AND  f.feature_ts <= e.event_ts        -- no peeking into the future
  ORDER BY f.feature_ts DESC
  LIMIT  1
) f ON true;