mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-04-30 17:48:27 -05:00
2701 lines
228 KiB
Plaintext
2701 lines
228 KiB
Plaintext
---
|
||
quiz: introduction_quizzes.json
|
||
concepts: introduction_concepts.yml
|
||
glossary: introduction_glossary.json
|
||
engine: jupyter
|
||
|
||
---
|
||
|
||
# Introduction {#sec-introduction}
|
||
|
||
<!-- Calculation stencil: PIPO (Purpose → Input → Process → Output).
|
||
Markdown uses only *_str or *_math variables. -->
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: chapter-start
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ CHAPTER START
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Chapter initialization — must be the first cell
|
||
# │
|
||
# │ Goal: Initialize the chapter and register it with the mlsys registry.
|
||
# │ Show: Correct registration for cross-chapter constant resolution.
|
||
# │ How: Invoke start_chapter from the mlsys registry module.
|
||
# │
|
||
# │ Imports: mlsys.registry (start_chapter)
|
||
# │ Exports: (none — side effect only)
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.registry import start_chapter
|
||
|
||
start_chapter("vol1:introduction")
|
||
```
|
||
|
||
::: {layout-narrow}
|
||
::: {.column-margin}
|
||
\chapterminitoc
|
||
:::
|
||
|
||
\noindent
|
||
{fig-alt="Illustration of a textbook titled Machine Learning Systems showing a winding road map connecting various AI and ML concepts including data systems, training, optimization, and deployment stages."}
|
||
|
||
:::
|
||
|
||
## Purpose {.unnumbered}
|
||
|
||
\begin{marginfigure}
|
||
\mlsysstack{30}{30}{30}{30}{30}{30}{30}{30}
|
||
\end{marginfigure}
|
||
|
||
_Why does building machine learning systems require engineering principles so different from those governing traditional software?_
|
||
|
||
Machine learning systems have a *physics*. Data must move through memory hierarchies governed by bandwidth limits. Arithmetic must execute on silicon governed by power budgets. Predictions must arrive within latency windows governed by the speed of light. These are not implementation details to be abstracted away; they are permanent constraints that shape every design decision from model architecture to deployment target. At the same time, ML systems have a property no traditional software shares: their behavior is defined by data, not code. *When* a traditional program misbehaves, engineers trace the bug to specific lines of source; *when* a machine learning system misbehaves, there is often no bug to find. The code executes correctly, but the learned behavior is wrong because the training data was incomplete, biased, or stale. This means ML engineers must simultaneously manage the statistical uncertainty inherent in learned behavior and the physical constraints of executing that behavior on real hardware. A model that fits comfortably in a datacenter may be useless on a phone. A training pipeline that *converges* (reaches a stable trained state) in a week on one accelerator may take a month on another. An accurate model trained on last year's data may silently degrade as the world changes. The engineering principles that made traditional software reliable—testing, modularity, version control—remain necessary but are no longer sufficient. What is needed is a discipline grounded in the physics of computation, where decisions at the algorithm level impact the full stack down to the silicon, and hardware constraints flow back up to reshape model design.
|
||
|
||
::: {.content-visible when-format="pdf"}
|
||
\newpage
|
||
:::
|
||
|
||
::: {.callout-tip title="Learning Objectives"}
|
||
|
||
- Explain the **AI Triad** (Data, Algorithm, Machine) and apply the **D·A·M taxonomy** to diagnose ML system bottlenecks
|
||
- Explain AI's evolution from symbolic reasoning to deep learning and *why* computational scale outperforms encoded expertise (the **Bitter Lesson**)
|
||
- Distinguish ML systems from traditional software based on silent degradation, data dependencies, and continuous iteration
|
||
- Apply the **Iron Law of ML Systems** to decompose performance into data movement, computation, and overhead terms
|
||
- Describe the three dimensions of efficiency and identify how the five **Lighthouse Models** stress-test different bottlenecks
|
||
- Distinguish the ML development lifecycle from traditional software development and compare deployment contexts from cloud to TinyML
|
||
- Apply the **Degradation Equation** to reason about drift in ML systems
|
||
- Apply the **five-pillar framework** to organize ML systems engineering
|
||
|
||
:::
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: ai-moment-stats
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ AI MOMENT STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: "The AI Moment" section - opening statistics about AI scale
|
||
# │
|
||
# │ Goal: Demonstrate the massive scale divergence between traditional software and AI.
|
||
# │ Show: Google Search volume vs. the GPU/CPU compute performance gap.
|
||
# │ How: Compare daily search counts (SI units) and TFLOPS density (A100 vs. CPU).
|
||
# │
|
||
# │ Imports: mlsys.constants (GOOGLE_SEARCHES_PER_DAY, H100/CPU FLOPS)
|
||
# │ Exports: google_search_b_str, h100_fp16_tflops_str, cpu_fp32_tflops_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.constants import (
|
||
GOOGLE_SEARCHES_PER_DAY,
|
||
H100_FLOPS_FP16_TENSOR,
|
||
CPU_FLOPS_FP32,
|
||
TFLOPs,
|
||
second,
|
||
BILLION, MILLION, TRILLION, THOUSAND
|
||
)
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class AIMomentStats:
|
||
"""
|
||
Namespace for opening statistics in 'The AI Moment' section.
|
||
Establishes the scale of modern AI (searches) and hardware asymmetry (GPU vs CPU).
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
searches_per_day = GOOGLE_SEARCHES_PER_DAY
|
||
h100_flops = H100_FLOPS_FP16_TENSOR
|
||
cpu_flops = CPU_FLOPS_FP32
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
searches_b = searches_per_day / BILLION
|
||
gpu_tflops = h100_flops.m_as(TFLOPs / second)
|
||
cpu_tflops = cpu_flops.m_as(TFLOPs / second)
|
||
gpu_cpu_ratio = gpu_tflops / cpu_tflops
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(searches_b >= 5, f"Google searches ({searches_b:.1f}B) unexpectedly low.")
|
||
check(gpu_cpu_ratio >= 500, f"GPU/CPU ratio ({gpu_cpu_ratio:.1f}x) too low for 'massive parallelism'.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
google_search_b_str = fmt(searches_b, precision=1)
|
||
h100_fp16_tflops_str = fmt(gpu_tflops, precision=0, commas=False)
|
||
cpu_fp32_tflops_str = fmt(cpu_tflops, precision=1, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
google_search_b_str = AIMomentStats.google_search_b_str
|
||
h100_fp16_tflops_str = AIMomentStats.h100_fp16_tflops_str
|
||
cpu_fp32_tflops_str = AIMomentStats.cpu_fp32_tflops_str
|
||
```
|
||
|
||
## AI Moment {#sec-introduction-ai-moment-37f1}
|
||
|
||
Artificial intelligence has moved from research laboratories to the fabric of daily life. Consider asking your phone a question: an AI system converts your speech to text, interprets your intent, and generates a response. Scrolling through social media, AI systems decide which posts appear and in what order. Applying for a loan, AI systems assess your creditworthiness. Driving a modern car, AI systems monitor lane position, detect pedestrians, and adjust cruise control. In each case, the system is not merely retrieving information but making decisions under uncertainty, often controlling physical outcomes that affect safety, finances, or access to opportunity. These are not future possibilities; they are present realities affecting billions of people daily.
|
||
|
||
*What* makes building these systems an engineering challenge distinct from traditional software? The answer lies in a **Dual Mandate**\index{Dual Mandate}. Every ML system must simultaneously manage statistical uncertainty, because the model's predictions are probabilistic, and physical constraints, because executing those predictions requires moving terabytes of data and performing quintillions of arithmetic operations, often within milliseconds. The difference becomes clearest at failure boundaries. When a traditional program crashes, an engineer traces the bug to specific lines of code. When an ML system's accuracy drops by five percentage points, there may be no bug to find: the code executes correctly, but the learned behavior has changed\index{Silent Degradation}. Concretely, a code bug causes a crash (a loud failure), whereas a data bug causes a wrong prediction (a silent failure). The training data may have shifted. The hardware may have run out of memory mid-training. The model may not have converged. Debugging, testing, and architectural design all change when a system's behavior is defined by data rather than by code.
|
||
|
||
This dual mandate is visible in every large-scale AI deployment. ChatGPT coordinates thousands of GPUs[^fn-gpu-parallel] across data centers, executing trillions of operations per query while managing memory, network bandwidth, and thermal constraints. Tesla's collision avoidance relies on dozens of neural networks[^fn-neural-network-origin] processing data from cameras, radar, and ultrasonic sensors simultaneously, fusing their outputs into a control decision within milliseconds. Google processes `{python} google_search_b_str` billion searches per day, each one triggering multiple AI systems for ranking, knowledge extraction, and spell-checking, all while meeting strict latency targets on globally distributed infrastructure. These systems do not merely run algorithms. They orchestrate data, computation, and hardware under tight physical constraints to deliver statistically reliable results at scale.
|
||
|
||
[^fn-gpu-parallel]: **GPU (Graphics Processing Unit)**: Originally designed for rendering video game graphics, a workload requiring thousands of simple, parallel pixel calculations. This hardware-algorithm alignment proved decisive for neural networks, where the same massively parallel arithmetic structure maps directly onto matrix multiplication, making GPUs the primary physical enabler of modern training scale (see @sec-hardware-acceleration). \index{GPU!etymology}
|
||
|
||
[^fn-neural-network-origin]: **Neural Network**: A differentiable function approximator whose compute and memory demands scale with its learned parameter count. Running *dozens* of them simultaneously, as Tesla's perception stack does, multiplies the memory footprint and scheduling complexity on fixed vehicle hardware, making the system's bottleneck not any single model's accuracy but the aggregate resource budget required to execute all models within a shared latency window. \index{Neural Network!etymology}
|
||
|
||
This textbook teaches the engineering principles for building, optimizing, and deploying these systems. At the core of our approach is a simple observation: every ML system is a three-way interaction between the *Algorithm* (what the system is learning), the *Data* (what it is learning from), and the *Machine* (the physical hardware executing the computation). These three elements, which we formalize as the **Data · Algorithm · Machine (D·A·M) taxonomy**\index{D·A·M taxonomy}, are inseparable. Compressing a model to fit on a mobile device changes its accuracy. Doubling the training data demands more compute and storage. Switching from a CPU to a GPU reshapes which algorithms are practical. Understanding ML systems engineering means learning to reason about all three simultaneously.
|
||
|
||
Before we can build that engineering framework, we need precise definitions and a shared analytical vocabulary. This chapter lays the foundation for the entire book in three movements. First, we establish *what* machine learning systems are: we distinguish artificial intelligence as a long-term research vision from machine learning as the engineering methodology we use today, trace the paradigm shifts that brought the field from rule-based expert systems to data-driven deep learning, and examine the **Bitter Lesson**, the empirical finding that general methods using computation ultimately outperform hand-engineered approaches. Second, we establish *what makes ML systems different*: we define ML systems precisely, analyze how they diverge from traditional software in testing, debugging, deployment, and maintenance, and develop the **Iron Law of ML Systems**, a quantitative framework that decomposes performance into data movement, computation, and overhead. Third, we establish *how to organize the engineering effort*: we define ML systems engineering as a discipline, trace the system lifecycle from conception through deployment, examine deployment case studies at both extremes (datacenter and microcontroller), and develop the **Five-Pillar Framework** that structures the rest of this book.
|
||
|
||
Machine learning represents a specific approach to artificial intelligence: rather than programming explicit rules, engineers design systems that learn patterns from data. However, this simple description conceals a deep reconception of what software *is*. Understanding the nature of that shift, and *why* it demands entirely new engineering practices, is where we begin.
|
||
|
||
## Data-Centric Paradigm Shift {#sec-introduction-datacentric-paradigm-shift-4eca}
|
||
|
||
The shift from rule-based to data-driven systems constitutes a deep reconception of computing. Andrej Karpathy[^fn-karpathy-sw2] formalized this distinction as the shift from **Software 1.0** to **Software 2.0**\index{Software 2.0} [@karpathy2017software], a framing that captures *why* ML systems require entirely new engineering approaches. @tbl-software-1-vs-2 summarizes this paradigm shift.
|
||
|
||
[^fn-karpathy-sw2]: **Andrej Karpathy**: A founding member of OpenAI and former Director of AI at Tesla who pioneered the application of deep learning to autonomous vehicle fleets. His "Software 2.0" thesis (2017) crystallized the insight that neural network weights are the new "source code," forcing a new engineering reality: instead of debugging explicit logic, engineers must curate and version the *data* that defines program behavior, since a model with millions of parameters cannot be patched or reasoned about directly. \index{Karpathy, Andrej!Software 2.0}
|
||
|
||
| **Feature** | **Software 1.0 (Traditional)** | **Software 2.0 (Machine Learning)** |
|
||
|:-----------------|:-------------------------------|:------------------------------------|
|
||
| **Source Code** | C++, Python, Java | Training Data + Labels |
|
||
| **Compiler** | GCC, LLVM | Training Loop (SGD) |
|
||
| **Logic** | Explicit (Hand-coded) | Implicit (Learned) |
|
||
| **Failure Mode** | Loud (Crash, Exception) | Silent (Metric Degradation) |
|
||
| **Debugging** | Trace execution path | Inspect data distribution |
|
||
|
||
: **The Paradigm Shift from Software 1.0 to Software 2.0**: In Software 2.0, the "programmer" does not write the logic; they curate the dataset that the optimization process uses to write the logic. Debugging therefore moves upstream from code to data. Note that the "compiler" analogy is approximate: unlike a deterministic compiler, the training process is stochastic and may produce different "executables" from the same "source code." {#tbl-software-1-vs-2}
|
||
|
||
\index{Technical Debt}
|
||
\index{Glue Code}
|
||
Google researchers quantified these consequences in a landmark 2015 paper.
|
||
|
||
::: {.callout-example title="The Hidden Technical Debt of ML Systems"}
|
||
**The Context**: In 2015, Google engineers published a landmark paper [@sculley2015hidden] that changed how the industry views ML engineering.
|
||
|
||
**The Insight**: They demonstrated that in mature ML systems, the *ML Code* (the model itself) is only a tiny fraction ($\approx 5\%$) of the total code base. The rest is *Glue Code*: data collection, verification, feature extraction, resource management, monitoring, and serving infrastructure.
|
||
|
||
**The Systems Lesson**: "Machine Learning" is easy; **"Machine Learning Systems"** are hard. The friction in deployment rarely comes from the matrix multiplication (the 5%); it comes from the interface between that math and the messy reality of the other 95%. If you optimize only the model, you are optimizing the smallest part of the problem.
|
||
:::
|
||
|
||
The critical implication: *Data is Source Code* (Principle \ref{pri-data-as-code}).\index{Software 2.0} In traditional software, a programmer writes explicit logic (`if x > 0 then y`). In machine learning, the programmer writes the *optimization meta-logic* (the training algorithm), but the actual operational logic is "compiled" from the training dataset through stochastic gradient descent[^fn-sgd-sampling] and related optimization methods. The dataset serves as source code, the training pipeline as compiler, and the model weights[^fn-model-weights-inference] as binary executable.
|
||
|
||
[^fn-sgd-sampling]: **Stochastic Gradient Descent (SGD)**: The algorithm implements the "compilation" of logic from data by processing one small, random data sample (a "batch") at a time, instead of the entire dataset. This trade-off—statistical noise for computational speed—is the core engine of the training "compiler." The choice of batch size becomes a critical compilation flag; a batch too small (e.g., < 32) fails to saturate the parallel processors of a GPU, wasting over 90% of its potential computation. \index{Stochastic Gradient Descent (SGD)!systems trade-off}
|
||
|
||
[^fn-model-weights-inference]: **Model Weights**: The learned numerical parameters of a neural network, one value per connection between units. A GPT-3-scale model stores 175 billion such values, consuming 350 GB in FP16 precision. Because every inference request must load these weights through the memory hierarchy, weight count is the single largest determinant of both memory footprint ($D_{vol}$) and serving cost (see @sec-neural-computation). \index{Model Weights!definition}
|
||
|
||
From a systems perspective, this represents a transition from *instruction-centric* to *data-centric* computing\index{Data-Centric Computing}:
|
||
|
||
- *Instruction-centric computing* (traditional): Systems optimized for the efficient execution of hand-crafted logic. The programmer's job is to write correct instructions.
|
||
- *Data-centric computing* (ML): Systems optimized for the efficient ingestion of data and the iterative refinement of model parameters. The programmer's job is to curate correct data.
|
||
|
||
Debugging an ML system therefore means debugging the *data*, not the Python scripts. Version control must track *datasets*, not just git commits. Testing must validate data distributions, not just code paths. Yet even thorough testing cannot close what amounts to a structural *verification gap*\index{Verification Gap} between finite test sets and the vast continuous input spaces that ML systems encounter in production.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: verification-gap-calc
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ VERIFICATION GAP CALCULATION
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: "The Verification Gap" callout (Data-Centric Paradigm section)
|
||
# │
|
||
# │ Goal: Quantify the impossibility of exhaustive testing in high-dimensional ML.
|
||
# │ Show: The astronomical size of the image input space vs. test set coverage.
|
||
# │ How: Calculate total possible pixel configurations for 224x224 RGB images.
|
||
# │
|
||
# │ Imports: math (standard library), mlsys.constants, mlsys.formatting
|
||
# │ Exports: vg_digits_str, imagenet_test_images_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
import math
|
||
from mlsys.constants import (
|
||
IMAGE_DIM_RESNET,
|
||
IMAGE_CHANNELS_RGB,
|
||
COLOR_DEPTH_8BIT,
|
||
IMAGENET_TEST_IMAGES,
|
||
)
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class VerificationGap:
|
||
"""
|
||
Calculates the dimensionality of the ImageNet input space to illustrate
|
||
the 'Verification Gap' between test sets and real-world input spaces.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
width = IMAGE_DIM_RESNET
|
||
height = IMAGE_DIM_RESNET
|
||
channels = IMAGE_CHANNELS_RGB
|
||
depth = COLOR_DEPTH_8BIT
|
||
test_size = IMAGENET_TEST_IMAGES.m_as('count')
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
total_pixels = width * height * channels
|
||
# Space size is depth^total_pixels. We want log10(depth^total_pixels).
|
||
digits = total_pixels * math.log10(depth)
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(digits > 300_000, f"Verification gap ({digits:.0f} digits) unexpectedly small.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
vg_digits_str = fmt(digits, precision=0, commas=True)
|
||
imagenet_test_images_str = fmt(test_size, precision=0, commas=True)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
vg_digits_str = VerificationGap.vg_digits_str
|
||
imagenet_test_images_str = VerificationGap.imagenet_test_images_str
|
||
```
|
||
|
||
::: {.callout-perspective title="The Verification Gap"}
|
||
***Why* we cannot just 'test' ML systems**\index{Verification Gap}: In Software 1.0, logic is discrete. We can write unit tests that cover edge cases because the input space is often enumerable or partitionable.
|
||
|
||
In Software 2.0, the input space is **high-dimensional** (e.g., all possible images). Although technically discrete, it is so vast that it is practically unsamplable. Consider an image classifier: a $224\times224$ RGB image has $256^{150{,}528}$ possible pixel configurations, a number with over `{python} vg_digits_str` digits. ImageNet's entire test set covers only `{python} imagenet_test_images_str` of them. Let **Total Input Space** denote the number of possible inputs and **Test Set Coverage** denote the number of inputs a test suite actually evaluates. No test suite can sample this space meaningfully. @eq-verification-gap captures this disparity:
|
||
|
||
$$ \text{Verification Gap} = \text{Total Input Space} - \text{Test Set Coverage} \approx \text{Total Input Space} $$ {#eq-verification-gap}
|
||
|
||
This gap means we must rely on **statistical monitoring** in production (@sec-ml-operations develops the monitoring infrastructure that makes this feasible) rather than pre-deployment verification alone. We trade *guaranteed correctness* for *statistical reliability*.
|
||
:::
|
||
|
||
The Verification Gap is symptomatic of a deeper shift: from *deterministic* systems where correctness can be proven to *probabilistic* systems where it can only be bounded\index{Probabilistic Engineering}. In classic systems engineering, success is defined by *determinism*: the same input always yields the same output. In AI Engineering, variance is inherent; the "squishiness" of data (its noise, its drift, its hidden patterns) is the source of the system's intelligence but also its unpredictability. Traditional systems achieve robustness through *resistance* to change, while ML systems achieve robustness through *adaptation* to change. True robustness in AI therefore comes from engineering *observability* and *adaptation* rather than rigidity.
|
||
|
||
::: {.callout-checkpoint title="The Paradigm Shift" collapse="false"}
|
||
Before tracing the history of AI, verify your understanding of the paradigm shift in how we build software:
|
||
|
||
- [ ] Can you distinguish **Software 1.0** (explicit instructions) from **Software 2.0** (optimization objectives)?
|
||
- [ ] Do you understand why "Data is Source Code" implies that debugging must move from code inspection to dataset inspection?
|
||
- [ ] Can you explain the **Verification Gap**: why we cannot mathematically guarantee correctness for ML systems in the same way we can for traditional logic?
|
||
:::
|
||
|
||
This data-centric paradigm requires rethinking the entire computing stack. The shift from instruction-centric to data-centric computing did not happen overnight. It emerged through seven decades of paradigm transitions, each overcoming the bottlenecks of its predecessor. Each era of AI faced a characteristic bottleneck, and understanding these bottlenecks reveals *why* systems engineering became central to progress.
|
||
|
||
## AI Paradigm Evolution {#sec-introduction-ai-paradigm-evolution-ae2b}
|
||
|
||
AI's evolution reveals a progression of bottlenecks, each overcome by systems innovations that expanded what was computationally possible. The field's origin is often traced to Alan Turing's[^fn-turing-outputs] 1950 paper "Computing Machinery and Intelligence" [@turing1950computing], which posed the foundational question: *Can machines think?* Early systems that attempted to answer this question, such as the Perceptron (1957) and ELIZA[^fn-eliza-brittleness] (1966), were limited by manual logic and the constraints of mainframes, resulting in brittleness. Subsequent eras were limited by manual knowledge entry, creating scalability issues. Modern systems face a different bottleneck: computational throughput.
|
||
|
||
[^fn-turing-outputs]: **Alan Turing**: His 1950 "Imitation Game" reframed intelligence as an output-measurement problem: judge a system by what it *does*, not by what it *is*. This engineering-first stance persists in every ML systems metric we use today—accuracy, latency, throughput, and FLOPS-per-watt are all output measurements—and explains why the Iron Law decomposes performance into observable, measurable terms rather than internal architectural properties. \index{Turing, Alan!Imitation Game}
|
||
|
||
[^fn-eliza-brittleness]: **ELIZA**: A 1966 natural language program that ran on 256 KB mainframes using pattern-matching rules with no learned state — its brittleness was a direct systems consequence of zero memory across turns. Every new input variation required a new hand-written rule, making maintenance cost grow faster than capability and foreshadowing the knowledge bottleneck that killed expert systems a decade later. \index{ELIZA!brittleness}
|
||
|
||
The timeline in @fig-ai-timeline reveals a recurring pattern: periods of intense optimism followed by "AI winters"[^fn-ai-winters-systems] when funding collapsed, each triggered by systems limitations that algorithms alone could not overcome. This boom-and-bust rhythm across seven decades: notice how each winter arrives precisely when the dominant paradigm hits its systems ceiling, and each resurgence follows a breakthrough in engineering infrastructure rather than in algorithms alone. Each era represents a paradigm shift attempting to overcome the limitations of the previous approach.
|
||
|
||
[^fn-ai-winters-systems]: **AI Winters as Systems Failures**: The first AI winter (1974--1980) was precipitated by the 1973 Lighthill Report, which concluded that AI had failed to deliver on its promises --- but the underlying cause was a mismatch between algorithm ambition and available compute. The second winter (1987--1993) was triggered by the collapse of the Lisp Machine market when cheaper general-purpose workstations undercut specialized AI hardware. Both winters are explicitly systems failures, not algorithmic dead ends: the algorithms were mathematically sound but required hardware that did not yet exist. The same compute-constraint pattern drove neural network research underground from 1969 (Minsky's perceptron critique) to 1986 (backpropagation revival on faster hardware). \index{AI Winters!systems failure}
|
||
|
||
::: {#fig-ai-timeline fig-env="figure" fig-pos="t!" fig-cap="**AI Development Timeline.** A chronological curve traces AI research activity from the 1950s to the 2020s, with gray bands marking the two AI Winter periods (1974 to 1980, 1987 to 1993). Callout boxes highlight key milestones including the Turing Test [@turing1950computing], the Dartmouth conference [@mccarthy1956dartmouth], the Perceptron, ELIZA, Deep Blue, and GPT-3." fig-alt="Timeline from 1950 to 2020 with red line showing AI publication frequency. Gray bands mark two AI Winters (1974-1980, 1987-1993). Callout boxes mark milestones: Turing 1950, Dartmouth 1956, Perceptron 1957, ELIZA 1966, Deep Blue 1997, GPT-3 2020."}
|
||
```{.tikz}
|
||
\begin{tikzpicture}[line join=round,font=\usefont{T1}{phv}{m}{n}\small]
|
||
\definecolor{bluegraph}{RGB}{0,102,204}
|
||
\pgfmathsetlengthmacro\MajorTickLength{
|
||
\pgfkeysvalueof{/pgfplots/major tick length} * 1.5
|
||
}
|
||
\tikzset{%
|
||
textt/.style={line width=0.5pt,draw=bluegraph,text width=26mm,align=flush center,
|
||
font=\usefont{T1}{phv}{m}{n}\footnotesize,fill=cyan!7},
|
||
Line/.style={line width=0.85pt,draw=bluegraph,dash pattern=on 3pt off 2pt,
|
||
{Circle[bluegraph,length=4.5pt]}- }
|
||
}
|
||
|
||
\begin{axis}[clip=false,
|
||
axis line style={thick},
|
||
axis lines*=left,
|
||
axis on top,
|
||
width=18cm,
|
||
height=20cm,
|
||
xmin=1950,
|
||
xmax=2023,
|
||
ymin=0.000000,
|
||
ymax=0.00033,
|
||
xtick={1950,1960,1970,1980,1990,2000,2010,2020},
|
||
extra x ticks={1955,1965,1975,1985,1995,2005,2015},
|
||
extra x tick labels={},
|
||
xticklabels={1950,1960,1970,1980,1990,2000,2010,2020},
|
||
ytick={0.0000,0.00005, 0.00010, 0.00015, 0.00020, 0.00025, 0.00030},
|
||
yticklabels={0.0000,0.00005, 0.00010, 0.00015, 0.00020, 0.00025, 0.00030},
|
||
grid=none,
|
||
tick label style={/pgf/number format/assume math mode=true},
|
||
xticklabel style={font=\footnotesize\usefont{T1}{phv}{m}{n},
|
||
},
|
||
yticklabel style={
|
||
font=\footnotesize\usefont{T1}{phv}{m}{n},
|
||
/pgf/number format/fixed,
|
||
/pgf/number format/fixed zerofill,
|
||
/pgf/number format/precision=5
|
||
},
|
||
scaled y ticks=false,
|
||
tick style = {line width=1.0pt},
|
||
tick align = outside,
|
||
major tick length=\MajorTickLength,
|
||
]
|
||
\fill[fill=BrownL!70](axis cs:1974,0)rectangle(axis cs:1980,0.00031)
|
||
node[above,align=center,xshift=-7mm]{1st AI \ Winter};
|
||
\fill[fill=BrownL!70](axis cs:1987,0)rectangle(axis cs:1993,0.00031)
|
||
node[above,align=center,xshift=-7mm]{2nd AI \ Winter};
|
||
\addplot[line width=2pt,color=RedLine,smooth,samples=100] coordinates {
|
||
(1950,0.0000006281)
|
||
(1951,0.0000000683)
|
||
(1952,0.0000003056)
|
||
(1953,0.0000002927)
|
||
(1954,0.0000004296)
|
||
(1955,0.0000004593)
|
||
(1956,0.0000016705)
|
||
(1957,0.0000006570)
|
||
(1958,0.0000021902)
|
||
(1959,0.0000032832)
|
||
(1960,0.0000126863)
|
||
(1961,0.0000063721)
|
||
(1962,0.0000240680)
|
||
(1963,0.0000141502)
|
||
(1964,0.0000111442)
|
||
(1965,0.0000143832)
|
||
(1966,0.0000147726)
|
||
(1967,0.0000169539)
|
||
(1968,0.0000167880)
|
||
(1969,0.0000175559)
|
||
(1970,0.0000155680)
|
||
(1971,0.0000206809)
|
||
(1972,0.0000223804)
|
||
(1973,0.0000218203)
|
||
(1974,0.0000256138)
|
||
(1975,0.0000282924)
|
||
(1976,0.0000247784)
|
||
(1977,0.0000404966)
|
||
(1978,0.0000358032)
|
||
(1979,0.0000436903)
|
||
(1980,0.0000472788)
|
||
(1981,0.0000561471)
|
||
(1982,0.0000767864)
|
||
(1983,0.0001064465)
|
||
(1984,0.0001592212)
|
||
(1985,0.0002133700)
|
||
(1986,0.0002559067)
|
||
(1987,0.0002608470)
|
||
(1988,0.0002623321)
|
||
(1989,0.0002358150)
|
||
(1990,0.0002301105)
|
||
(1991,0.0002051343)
|
||
(1992,0.0001789229)
|
||
(1993,0.0001560935)
|
||
(1994,0.0001508219)
|
||
(1995,0.0001401406)
|
||
(1996,0.0001169577)
|
||
(1997,0.0001150365)
|
||
(1998,0.0001051385)
|
||
(1999,0.0000981740)
|
||
(2000,0.0001010236)
|
||
(2001,0.0000976966)
|
||
(2002,0.0001038084)
|
||
(2003,0.0000980004)
|
||
(2004,0.0000989412)
|
||
(2005,0.0000977251)
|
||
(2006,0.0000899964)
|
||
(2007,0.0000864005)
|
||
(2008,0.0000911872)
|
||
(2009,0.0000852932)
|
||
(2010,0.0000822649)
|
||
(2011,0.0000913442)
|
||
(2012,0.0001104912)
|
||
(2013,0.0001023061)
|
||
(2014,0.0001022477)
|
||
(2015,0.0000919719)
|
||
(2016,0.0001134797)
|
||
(2017,0.0001384348)
|
||
(2018,0.0002057324)
|
||
(2019,0.0002328642)
|
||
}
|
||
;
|
||
|
||
\node[textt,text width=20mm](1950)at(axis cs:1957,0.00014){\textcolor{red}{1950}\
|
||
Alan Turing publishes \textbf{`Computing Machinery and Intelligence`} in the journal \textit{Mind}.};
|
||
\node[red,align=center,above=2mm of 1950]{Milestones\ in AI};
|
||
\draw[Line] (axis cs:1950,0) -- (1950.235);
|
||
%
|
||
\node[textt,text width=19mm](1956)at(axis cs:1958,0.00007){\textcolor{red}{Summer 1956}\
|
||
\textbf{Dartmouth Workshop} A formative conference organized by AI pioneer John McCarthy.};
|
||
\draw[Line] (axis cs:1956,0) -- (1956.255);
|
||
%
|
||
\node[textt](1957)at(axis cs:1969,0.00022){\textcolor{red}{1957}\
|
||
\textbf{Cornell psychologist Frank Rosenblatt invents the perceptron}, laying the groundwork for
|
||
modern neural networks.};
|
||
\draw[Line] (axis cs:1957,0) -- ++(0mm,17mm)-|(1957.248);
|
||
%
|
||
\node[textt,text width=21mm](1966)at(axis cs:1972,0.00012){\textcolor{red}{1966}\
|
||
\textbf{ELIZA chatbot} An early example of natural-language programming created by
|
||
MIT professor Joseph Weizenbaum.};
|
||
\draw[Line] (axis cs:1966,0) -- ++(0mm,17mm)-|(1966);
|
||
%
|
||
\node[textt,text width=20mm](1979)at(axis cs:1985,0.00012){\textcolor{red}{1979}\
|
||
Hans Moravec builds the \textbf{Stanford Cart}, one of the first autonomous vehicles.};
|
||
\draw[Line] (axis cs:1979,0) -- ++(0mm,17mm)-|(1979.245);
|
||
%
|
||
\node[textt,text width=21mm](1981)at(axis cs:1990,0.00006){\textcolor{red}{1981}\
|
||
Japanese \textbf{Fifth-Generation Computer Systems} project begins. The infusion of
|
||
research funding helps end first "AI winter."};
|
||
\draw[Line] (axis cs:1981,0) -- ++(0mm,10mm)-|(1981);
|
||
%
|
||
\node[textt,text width=15mm](1997)at(axis cs:2001,0.00007){\textcolor{red}{1997}\
|
||
\textbf{IBM's Deep Blue} beats world chess champion Garry Kasparov};
|
||
\draw[Line] (axis cs:1997,0) -- ++(0mm,10mm)-|(1997);
|
||
%
|
||
\node[textt,text width=15mm](2011)at(axis cs:2014,0.00003){\textcolor{red}{2011}\
|
||
\textbf{IBM's Watson} wins at Jeopardy!};
|
||
\draw[Line] (axis cs:2011,0) -- (2011);
|
||
%
|
||
\node[textt,text width=19mm](2005)at(axis cs:2012,0.00009){\textcolor{red}{2005}\
|
||
\textbf{DARPA Grand Challenge} Stanford wins the agency's second driverless-car
|
||
competition by driving 212 kilometers on an unrehearsed trail};
|
||
\draw[Line] (axis cs:2005,0) -- (2005);
|
||
%
|
||
\node[textt,text width=30mm](2020)at(axis cs:2010,0.00017){\textcolor{red}{2020}\
|
||
\textbf{OpenAI introduces GPT-3}. The enormously powerful natural-language model
|
||
later causes an outcry when it begins spouting bigoted remarks};
|
||
\draw[Line] (axis cs:2020,0) |- (2020);
|
||
%
|
||
\draw[Line,solid,-] (axis cs:1991,0.0002) --++(50:35mm)
|
||
node[bluegraph,above,align=center,text width=30mm]{Percent of U.S.-published books
|
||
in Google's database that mention artificial intelligence};
|
||
\end{axis}
|
||
\end{tikzpicture}
|
||
```
|
||
:::
|
||
|
||
### The Pre-Learning Era: Logic and Knowledge Bottlenecks {#sec-introduction-prelearning-era-logic-knowledge-bottlenecks-f312}
|
||
|
||
Before machine learning existed as a discipline, engineers attempted to build intelligent systems through two successive paradigms, each of which hit a fundamental scaling barrier. Symbolic AI encoded intelligence as logical rules and hit the *logic bottleneck*: rules could not capture real-world ambiguity. Expert systems encoded intelligence as domain knowledge and hit the *knowledge bottleneck*: acquiring and maintaining that knowledge became more expensive than the systems were worth. Together, these two eras reveal a pattern that motivates everything that follows: hand-crafted representations do not scale.
|
||
|
||
#### Symbolic AI Era: The Logic Bottleneck {#sec-introduction-symbolic-ai-era-logic-bottleneck-a250}
|
||
|
||
The first era of AI engineering (1950s–1970s) attempted to reduce intelligence to **Symbolic AI**\index{Symbolic AI} manipulation. Researchers at the 1956 **Dartmouth Conference**\index{Dartmouth Conference}[^fn-dartmouth-systems] [@mccarthy1956dartmouth] hypothesized that if they could formalize the rules of logic, machines could "think." Even then, some saw a different path: Arthur Samuel at IBM demonstrated in 1959 that a checkers program could improve through self-play, coining the very term "**machine learning**\index{Machine Learning}." But the dominant paradigm remained symbolic. Daniel Bobrow's *STUDENT*[^fn-bobrow-student] system [@bobrow1964student] (1964) exemplifies this approach.
|
||
|
||
[^fn-dartmouth-systems]: **Dartmouth Conference (1956)**: The workshop where John McCarthy coined "artificial intelligence" — but its participants focused almost entirely on algorithmic logic while ignoring the physical constraints of storage and compute. The same compute-agnostic assumption — that a better algorithm could always overcome a hardware limit — is precisely what this book exists to correct: every chapter that follows argues that systems constraints are first-class design variables, not afterthoughts. \index{Dartmouth Conference!systems oversight}
|
||
|
||
[^fn-bobrow-student]: **STUDENT**: Daniel Bobrow's 1964 MIT system exposed the core failure mode of symbolic AI — complexity grows faster than capability. Every new problem type required new hand-written parsing rules, so the system's maintenance burden scaled superlinearly with coverage. Data-driven approaches break this trap by learning the mapping from examples rather than encoding it as rules, which is why the shift to statistical ML in the 1980s–90s was fundamentally a scaling breakthrough, not merely an accuracy improvement. \index{STUDENT!symbolic AI failure mode}
|
||
|
||
::: {.callout-example title="STUDENT (1964)"}
|
||
```{.text}
|
||
Problem: "If the number of customers Tom gets is twice the
|
||
square of 20% of the number of advertisements he runs, and
|
||
the number of advertisements is 45, what is the number of
|
||
customers Tom gets?"
|
||
|
||
STUDENT would:
|
||
|
||
1. Parse the English text
|
||
2. Convert it to algebraic equations
|
||
3. Solve the equation: n = 2(0.2 x 45)^2
|
||
4. Provide the answer: 162 customers
|
||
```
|
||
:::
|
||
|
||
\index{Moravec's Paradox}
|
||
While impressive in demonstrations, these systems were operationally **brittle**. They relied on manually coded rules for every possible state. A minor variation in input phrasing (e.g., "Tom's client count") would cause system failure. The engineering lesson: explicit logic cannot scale to handle real-world ambiguity. The complexity of the "rule base" grows exponentially until it becomes unmaintainable. This limitation extended beyond language: Hans Moravec's[^fn-moravec-paradox] work on autonomous navigation at Stanford revealed that tasks humans find trivial---seeing, walking, grasping---were far harder to engineer than tasks humans find difficult, like chess or algebra.
|
||
|
||
[^fn-moravec-paradox]: **Moravec's Paradox**: Carnegie Mellon roboticist Hans Moravec observed that high-level reasoning (chess) requires little compute while low-level perception (walking) requires massive parallelism. This paradox explains a central fact of ML systems engineering: the tasks that seem "easy" to humans (vision, speech, motor control) are the ones that demand the most FLOPS, memory bandwidth, and specialized hardware, driving the accelerator revolution that defines modern ML infrastructure. \index{Moravec's Paradox!compute implications}
|
||
|
||
#### Expert Systems Era: The Knowledge Bottleneck {#sec-introduction-expert-systems-era-knowledge-bottleneck-f928}
|
||
|
||
\index{Expert Systems}\index{Knowledge Acquisition Bottleneck}
|
||
In the 1980s, engineers pivoted from general logic to capturing deep domain expertise. MYCIN [@shortliffe1976mycin] (1976), designed to diagnose blood infections, encoded approximately 600 rules derived from interviews with medical experts.
|
||
|
||
::: {.callout-example title="MYCIN (1976)"}
|
||
```{.text}
|
||
Rule Example from MYCIN:
|
||
IF
|
||
The infection is primary-bacteremia
|
||
The site of the culture is one of the sterile sites
|
||
The suspected portal of entry is the gastrointestinal tract
|
||
THEN
|
||
Found suggestive evidence (0.7) that infection is bacteroid
|
||
```
|
||
:::
|
||
|
||
MYCIN outperformed junior doctors in specific tests but revealed the **Knowledge Acquisition Bottleneck**[^fn-knowledge-bottleneck-scale]. Extracting implicit intuition from human experts and formalizing it into IF-THEN rules proved slow, error-prone, and contradictory.
|
||
|
||
[^fn-knowledge-bottleneck-scale]: **Knowledge Acquisition Bottleneck**: Named by Feigenbaum in 1982, this bottleneck was fundamentally a throughput problem: knowledge elicitation consumed 70–80% of total expert system project time, and roughly 60% of expert system initiatives failed outright because the acquisition process could not scale. Unlike computational bottlenecks that yield to faster hardware, this one was bound by the serial bandwidth of human experts—the original "does not scale" constraint in AI, and the direct motivation for the data-driven paradigm that followed. \index{Knowledge Acquisition Bottleneck!scale}
|
||
|
||
Maintaining a system with thousands of conflicting rules became an intractable systems engineering problem. This failure demonstrated that scalable AI required systems to learn rules from data, rather than having them manually injected by engineers.
|
||
|
||
### Statistical Learning Era: The Feature Engineering Bottleneck {#sec-introduction-statistical-learning-era-feature-engineering-bottleneck-8cf5}
|
||
|
||
\index{Statistical Learning}\index{Feature Engineering Bottleneck} The 1990s marked the shift to probabilistic systems. Instead of hard-coded logic, systems estimated probabilities from data ($P(Y|X)$). This transition was driven by the availability of digital data and the "unreasonable effectiveness"[^fn-unreasonable-data] of large datasets.
|
||
|
||
[^fn-unreasonable-data]: **Unreasonable Effectiveness of Data**: The principle that a simple statistical model fed with massive amounts of data often outperforms a more sophisticated model with less data. This validated the pivot from brittle, hand-crafted expert systems to probabilistic models by showing that engineering investment in data scaling yielded more accuracy than investment in algorithmic complexity alone. For language tasks of that era, increasing a training dataset by 10x often reduced error rates more than switching to a completely new, more complex algorithm. \index{Unreasonable Effectiveness of Data!systems impact}
|
||
|
||
Spam filtering illustrates this shift. Rather than maintaining lists of forbidden words, statistical filters learned the probability that a word implies spam based on millions of examples.
|
||
|
||
::: {.callout-example title="Early Spam Detection Systems"}
|
||
```{.text}
|
||
Rule-based (1980s):
|
||
IF contains("viagra") OR contains("winner") THEN spam
|
||
|
||
Statistical (1990s):
|
||
P(spam|word) = (frequency in spam emails) / (total frequency)
|
||
|
||
Combined using Naive Bayes:
|
||
P(spam|email) ~ P(spam) x product of P(word|spam)
|
||
```
|
||
:::
|
||
|
||
This era faced the **Feature Engineering Bottleneck**\index{Feature Engineering Bottleneck}. Algorithms like Support Vector Machines (SVMs) could learn robustly, but only *after* humans converted raw data into structured "features." The system's performance was bounded by human ingenuity in preprocessing, not by the data itself. The *traditional computer vision pipeline* illustrates the depth of this manual effort, where multiple hand-crafted stages preceded any learning at all.
|
||
|
||
This hybrid approach combined human-engineered features with statistical learning. The Viola-Jones algorithm [@viola2001rapidobject][^fn-viola-jones-earlyexit] (2001) exemplifies this era, achieving real-time face detection using simple rectangular features and cascaded classifiers. This algorithm powered digital camera face detection for nearly a decade, demonstrating that well-engineered features could enable practical applications, but only within narrow domains where experts could hand-craft the right representations.
|
||
|
||
::: {.callout-example title="Traditional Computer Vision Pipeline"}
|
||
1. Manual Feature Extraction
|
||
- SIFT (Scale-Invariant Feature Transform)
|
||
- HOG (Histogram of Oriented Gradients)
|
||
- Gabor filters
|
||
2. Feature Selection/Engineering
|
||
3. "Shallow" Learning Model (e.g., SVM)
|
||
4. Post-processing
|
||
:::
|
||
|
||
[^fn-viola-jones-earlyexit]: **Viola-Jones Algorithm**: The algorithm's real-time speed came from a classifier cascade that used simple, hand-engineered features to immediately reject non-face regions. This tight coupling to hand-crafted features, however, is precisely why the method failed to generalize beyond the specific geometry of faces. The first two layers alone could discard over 80% of negative sub-windows while using just 11 of the 6,000+ total features. \index{Viola-Jones Algorithm!compute efficiency}
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: alexnet-breakthrough
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ ALEXNET BREAKTHROUGH STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: "Deep Learning Era" section and @fig-alexnet caption
|
||
# │
|
||
# │ Goal: Demonstrate AlexNet's breakthrough as a systems co-design achievement.
|
||
# │ Show: The 42% relative error reduction driven by GPU-algorithm alignment.
|
||
# │ How: Calculate top-5 error improvement over second place.
|
||
# │
|
||
# │ Imports: mlsys.constants (ALEXNET_PARAMS)
|
||
# │ Exports: alexnet_relative_improvement_str, alexnet_params_m_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Models
|
||
from mlsys.constants import MILLION
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class AlexNetBreakthrough:
|
||
"""
|
||
Namespace for AlexNet breakthrough statistics.
|
||
Scenario: ImageNet 2012 competition results.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
model = Models.Vision.ALEXNET
|
||
alexnet_top5_error = 15.3
|
||
second_place_error = 26.2
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
relative_improvement = (second_place_error - alexnet_top5_error) / second_place_error * 100
|
||
params_m = model.parameters.m_as('Mparam')
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(relative_improvement >= 40, f"AlexNet improvement should be ~42%, got {relative_improvement:.1f}%")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
alexnet_relative_improvement_str = fmt(relative_improvement, precision=0)
|
||
alexnet_params_m_str = fmt(params_m, precision=0)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
alexnet_relative_improvement_str = AlexNetBreakthrough.alexnet_relative_improvement_str
|
||
alexnet_params_m_str = AlexNetBreakthrough.alexnet_params_m_str
|
||
```
|
||
|
||
### Deep Learning Era: The Infrastructure Bottleneck {#sec-introduction-deep-learning-era-infrastructure-bottleneck-2f02}
|
||
|
||
\index{Deep Learning}
|
||
\index{Convolutional Neural Networks (CNNs)}
|
||
\index{ImageNet}
|
||
Deep learning (2012 to present) removed the human feature engineering requirement. Neural networks learn representations directly from raw data (pixels, audio waveforms), enabling "end-to-end" learning. This shift was unlocked not by new algorithms (CNNs existed in the 1980s) but by **Systems Co-design**\index{Systems Co-design}. The 2012 AlexNet breakthrough\index{AlexNet} [@alexnet2012] occurred because algorithmic structure (parallel matrix operations) matched hardware capabilities (GPUs). With 60 million parameters distributed across two GTX 580 GPUs, AlexNet achieved 15.3% top-5 error, a `{python} alexnet_relative_improvement_str`% relative improvement over the next-best entry that year, not through algorithmic novelty but through hardware-algorithm alignment. @fig-alexnet makes this co-design visible: the architecture's two parallel processing streams exist not for any algorithmic reason but because a single GTX 580 GPU lacked sufficient memory, making the network's very structure a product of its hardware constraints.
|
||
|
||
::: {#fig-alexnet fig-env="figure" fig-pos="htb" fig-cap="**AlexNet Architecture.** The network that launched the deep learning revolution at ImageNet 2012. Two parallel GPU streams process $224\times224$ input images through convolutional layers (green blocks) that extract spatial features at decreasing resolutions, converging through three fully connected layers to 1,000 output classes. With `{python} alexnet_params_m_str` million parameters trained across two GTX 580 GPUs, AlexNet achieved 15.3% top-5 error, a 42% relative improvement over the second-place entry." fig-alt="3D diagram of AlexNet with two parallel GPU streams. Green blocks show convolutional layers decreasing from 224x224 input. Red kernels overlay green blocks. Right side shows three dense layers converging to 1000 outputs."}
|
||
```{.tikz}
|
||
\begin{tikzpicture}[line join=round,font=\usefont{T1}{phv}{m}{n}\small]
|
||
\clip (-11.2,-2) rectangle (15.5,5.45);
|
||
%\draw[red](-11.2,-1.7) rectangle (15.5,5.45);
|
||
\tikzset{%
|
||
LineD/.style={line width=0.7pt,black!50,dashed,dash pattern=on 3pt off 2pt},
|
||
LineG/.style={line width=0.75pt,GreenLine},
|
||
LineR/.style={line width=0.75pt,RedLine},
|
||
LineA/.style={line width=0.75pt,BrownLine,-latex,text=black}
|
||
}
|
||
\newcommand\FillCube[4]{
|
||
\def\depth{#2}
|
||
\def\width{#3}
|
||
\def\height{#4}
|
||
\def\nc{#1}
|
||
% Lower front left corner
|
||
\coordinate (A\nc) at (0, 0);
|
||
% Donji prednji desni
|
||
\coordinate (B\nc) at (\width, 0);
|
||
% Upper front right
|
||
\coordinate (C\nc) at (\width, \height);
|
||
% Upper front left
|
||
\coordinate (D\nc) at (0, \height);
|
||
% Pomak u "dubinu"
|
||
\coordinate (shift) at (-0.7*\depth, \depth);
|
||
% Last points (moved)
|
||
\coordinate (E\nc) at ($(A\nc) + (shift)$);
|
||
\coordinate (F\nc) at ($(B\nc) + (shift)$);
|
||
\coordinate (G\nc) at ($(C\nc) + (shift)$);
|
||
\coordinate (H\nc) at ($(D\nc) + (shift)$);
|
||
% Front side
|
||
\draw[GreenLine,fill=green!08,line width=0.5pt] (A\nc) -- (B\nc) -- (C\nc) --(D\nc) -- cycle;
|
||
% Top side
|
||
\draw[GreenLine,fill=green!20,line width=0.5pt] (D\nc) -- (H\nc) -- (G\nc) -- (C\nc);
|
||
% Left
|
||
\draw[GreenLine,fill=green!15] (A\nc) -- (E\nc) -- (H\nc)--(D\nc)--cycle;
|
||
\draw[] (E\nc) -- (H\nc);
|
||
\draw[GreenLine,line width=0.75pt](A\nc)--(B\nc)--(C\nc)--(D\nc)--(A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--(H\nc);
|
||
}
|
||
%%%
|
||
\newcommand\SmallCube[4]{
|
||
\def\nc{#1}
|
||
\def\depth{#2}
|
||
\def\width{#3}
|
||
\def\height{#4}
|
||
\coordinate (A\nc) at (0, 0);
|
||
\coordinate (B\nc) at (\width, 0);
|
||
\coordinate (C\nc) at (\width, \height);
|
||
\coordinate (D\nc) at (0, \height);
|
||
\coordinate (shift) at (-0.7*\depth, \depth);
|
||
\coordinate (E\nc) at ($(A\nc) + (shift)$);
|
||
\coordinate (F\nc) at ($(B\nc) + (shift)$);
|
||
\coordinate (G\nc) at ($(C\nc) + (shift)$);
|
||
\coordinate (H\nc) at ($(D\nc) + (shift)$);
|
||
\draw[RedLine,fill=red!08,line width=0.5pt,fill opacity=0.7] (A\nc) -- (B\nc) -- (C\nc) -- (D\nc) -- cycle;
|
||
\draw[RedLine,fill=red!20,line width=0.5pt,fill opacity=0.7] (D\nc) -- (H\nc) -- (G\nc) -- (C\nc);
|
||
\draw[RedLine,fill=red!15,fill opacity=0.7] (A\nc) -- (E\nc) -- (H\nc)--(D\nc)--cycle;
|
||
\draw[] (E\nc) -- (H\nc);
|
||
}
|
||
%%%%%%%%%%%%%%%%%%%%%
|
||
%%4 column
|
||
%%%%%%%%%%%%%%%%%%%%
|
||
\begin{scope}
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{4VD}{0.8}{3}{2}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.10,0.4)},line width=0.5pt]
|
||
\SmallCube{4MD}{0.4}{3}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{4VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{192} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{13} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{13} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%Above
|
||
\begin{scope}[shift={(0,3.5)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{4VG}{0.8}{3}{2}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.18,0.55)}]
|
||
\SmallCube{4MG}{0.4}{3}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
\def\nc{4VG}
|
||
\draw[LineG](A\nc)--node[below,text=black]{192} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%%%%
|
||
%%5 column
|
||
%%%%
|
||
%%small cube
|
||
\begin{scope}[shift={(4.15,0)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{5VD}{0.8}{3}{2}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.10,1.25)}]
|
||
\SmallCube{5MD}{0.4}{3}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{5VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{192} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{13} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{13} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%Above
|
||
\begin{scope}[shift={(4.15,3.5)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{5VG}{0.8}{3}{2}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.08,0.28)}]
|
||
\SmallCube{5MG}{0.4}{3}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{5VG}
|
||
\draw[LineG](A\nc)--node[below,text=black]{192} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
%%3 column
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
\begin{scope}[shift={(-3.75,-0.5)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{3VD}{1.5}{2.33}{3}
|
||
\end{scope}
|
||
%%small cube-down
|
||
\begin{scope}[shift={(-0.10,0.45)}]
|
||
\SmallCube{3MDI}{0.4}{2.33}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\end{scope}
|
||
%%small cube - up
|
||
\begin{scope}[shift={(-0.12,2.23)}]
|
||
\SmallCube{3MDII}{0.4}{2.33}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{3VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{128} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1,pos=0.4]{27} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{27} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%Above
|
||
\begin{scope}[shift={(-3.75,3.5)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{3VG}{1.5}{2.33}{3}
|
||
\end{scope}
|
||
%%small cube-down
|
||
\begin{scope}[shift={(-0.42,0.75)}]
|
||
\SmallCube{3MGI}{0.4}{2.33}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{3VG}
|
||
\draw[GreenLine,line width=0.75pt](A\nc)--node[below,text=black]{128} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{} (H\nc);
|
||
\end{scope}
|
||
%%small cube-up
|
||
\begin{scope}[shift={(-0.06,0.18)}]
|
||
\SmallCube{3MGII}{0.4}{2.33}{0.6}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{3}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{3}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{3VG}
|
||
\draw[LineG](A\nc)--node[below,text=black]{128} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
%%2 column
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
\begin{scope}[shift={(-6.8,-1)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{2VD}{2}{1.3}{3.8}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.2,2.5)}]
|
||
\SmallCube{2MD}{0.4}{1.3}{1}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{5}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{5}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{2VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{48} (B\nc)--
|
||
(C\nc)--(D\nc)--node[pos=0.6,right,text=black,text opacity=1]{55} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[pos=0.26,right,text=black,text opacity=1]{55} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%Above
|
||
\begin{scope}[shift={(-6.8,3.5)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{2VG}{2}{1.3}{3.8}
|
||
\end{scope}
|
||
%%small cube
|
||
\begin{scope}[shift={(-0.1,0.5)}]
|
||
\SmallCube{2MG}{0.4}{1.3}{1}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--node[left,text=black]{5}
|
||
(C\nc)--(D\nc)-- (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left,text=black]{5}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{2VG}
|
||
\draw[LineG](A\nc)--node[above,text=black]{48} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)--node[right,text=black,text opacity=1]{} (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
%%1 column
|
||
%%%%%%%%%%%%%%%%%%%%%%%
|
||
\begin{scope}[shift={(-9.0,-1.2)}]
|
||
%big cube
|
||
\begin{scope}
|
||
\FillCube{1VD}{2}{0.2}{4.55}
|
||
\end{scope}
|
||
%%small cube=down
|
||
\begin{scope}[shift={(-0.25,0.5)}]
|
||
\SmallCube{1MDI}{0.8}{0.15}{1.7}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--
|
||
(C\nc)--(D\nc)-- node[left=-2pt,text=black,pos=0.4]{11}(A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left=3pt,text=black,pos=0.9]{11}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{1VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{3} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--node[below left,text=black]{224}(E\nc)--
|
||
node[left,text=black,text opacity=1]{224}(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
\end{scope}
|
||
%%small cube=up
|
||
\begin{scope}[shift={(-0.75,3.4)}]
|
||
\SmallCube{1MDII}{0.8}{0.15}{1.7}
|
||
%%
|
||
\draw[LineR](A\nc)-- (B\nc)--
|
||
(C\nc)--(D\nc)-- node[left=-2pt,text=black,pos=0.4]{11}(A\nc)
|
||
(A\nc)--(E\nc)--(H\nc)--(G\nc)--node[left=3pt,text=black,pos=0.9]{11}(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
%
|
||
\def\nc{1VD}
|
||
\draw[LineG](A\nc)--node[below,text=black]{3} (B\nc)--
|
||
(C\nc)--(D\nc)--node[right,text=black,text opacity=1]{} (A\nc)
|
||
(A\nc)--node[below left,text=black]{224}(E\nc)--
|
||
node[left,text=black,text opacity=1]{224}(H\nc)--(G\nc)--(C\nc)
|
||
(D\nc)-- (H\nc);
|
||
\end{scope}
|
||
\end{scope}
|
||
%%%%
|
||
\begin{scope}[shift={(8.15,0)}]
|
||
\begin{scope}
|
||
\FillCube{6VD}{0.8}{2.0}{2}
|
||
\path(A6VD)--node[below]{128}(B6VD);
|
||
\path(A6VD)--node[right]{13}(D6VD);
|
||
\path(D6VD)--node[right]{13}(H6VD);
|
||
\end{scope}
|
||
%up
|
||
\begin{scope}[shift={(0,3.5)}]
|
||
\FillCube{6VG}{0.8}{2.0}{2}
|
||
\path(A6VG)--node[below]{128}(B6VG);
|
||
\end{scope}
|
||
\end{scope}
|
||
|
||
\newcommand\Boxx[3]{\node[draw,LineG,fill=green!10,rectangle,minimum width=7mm,minimum height=#2](#1){};
|
||
\node[below=2pt of #1]{#3};
|
||
}
|
||
\begin{scope}[shift={(11.7,1.0)}]
|
||
\Boxx{B1D}{35mm}{2048}
|
||
\end{scope}
|
||
\begin{scope}[shift={(11.7,5.25)}]
|
||
\Boxx{B1G}{35mm}{2048}
|
||
\end{scope}
|
||
\begin{scope}[shift={(13.5,1.0)}]
|
||
\Boxx{B2D}{35mm}{2048}
|
||
\end{scope}
|
||
\begin{scope}[shift={(13.5,5.25)}]
|
||
\Boxx{B2G}{35mm}{2048}
|
||
\end{scope}
|
||
\begin{scope}[shift={(15.0,1.0)}]
|
||
\Boxx{B3}{19mm}{1000}
|
||
\end{scope}
|
||
%%%
|
||
\node[right=3pt of B1VD,align=center]{Stride\ of 4};
|
||
\node[right=3pt of B2VD,align=center]{Max\ pooling};
|
||
\node[right=3pt of B3VD,align=center]{Max\ pooling};
|
||
\node[below=3pt of B6VD,align=center]{Max\ pooling};
|
||
%
|
||
\coordinate(1C2)at($(A2VD)!0.4!(D2VD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 1MDI)--(1C2);
|
||
}
|
||
\coordinate(2C2)at($(E2VG)!0.2!(H2VG)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 1MDII)--(2C2);
|
||
}
|
||
%3
|
||
\coordinate(1C3)at($(A3VD)!0.55!(H3VD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 2MD)--(1C3);
|
||
}
|
||
\coordinate(2C3)at($(A3MGI)!0.35!(D3MGI)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 2MG)--(2C3);
|
||
}
|
||
%4
|
||
\coordinate(1C4)at($(A4VG)!0.15!(D4VG)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 3MGI)--(1C4);
|
||
}
|
||
\coordinate(2C4)at($(G4MD)!0.15!(H4MD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 3MGII)--(2C4);
|
||
}
|
||
\coordinate(3C4)at($(A4MG)!0.5!(C4MG)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 3MDII)--(3C4);
|
||
}
|
||
\coordinate(3C4)at($(A4VD)!0.12!(D4VD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 3MDI)--(3C4);
|
||
}
|
||
%5
|
||
\coordinate(1C5)at($(A5MG)!0.82!(H5MG)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 4MG)--(1C5);
|
||
}
|
||
\coordinate(2C5)at($(A5VD)!0.52!(C5VD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 4MD)--(2C5);
|
||
}
|
||
%6
|
||
\coordinate(1C6)at($(A6VG)!0.52!(C6VG)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 5MG)--(1C6);
|
||
}
|
||
\coordinate(1C6)at($(D6VD)!0.3!(B6VD)$);
|
||
\foreach\i in{B,C,G}{
|
||
\draw[LineD](\i 5MD)--(1C6);
|
||
}
|
||
%
|
||
\draw[LineA]($(B6VD)!0.52!(C6VD)$)coordinate(X1)--
|
||
node[below]{dense}(X1-|B1D.north west);
|
||
\draw[LineA](B1D)--node[below]{dense}(B2D);
|
||
\draw[LineA](B2D)--(B3);
|
||
%
|
||
\draw[LineA]($(B6VG)!0.52!(C6VG)$)coordinate(X1)--(X1-|B1G.north west);
|
||
\draw[LineA]($(B6VG)!0.52!(C6VG)$)--(B1D);
|
||
\draw[LineA]($(B6VD)!0.52!(C6VD)$)--(B1G);
|
||
\draw[LineA](B1D)--(B2G);
|
||
\draw[LineA](B1G)--(B2D);
|
||
\draw[LineA](B2G)--node[right]{dense}(B3);
|
||
\draw[LineA]($(B1G.north east)!0.7!(B1G.south east)$)--($(B2G.north west)!0.7!(B2G.south west)$);
|
||
\end{tikzpicture}
|
||
```
|
||
:::
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: gpt3-scale-stats
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ GPT-3 TRAINING SCALE STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Compute Bottleneck discussion (also reused in "Model Challenges")
|
||
# │
|
||
# │ Goal: Quantify the scale of modern foundation model training.
|
||
# │ Show: The infrastructure challenge defined by 175B parameters and zettaFLOP scale.
|
||
# │ How: Retrieve GPT-3 specifications and calculate training ZFLOPS.
|
||
# │
|
||
# │ Imports: mlsys.constants (GPT3_PARAMS, GPT3_TRAINING_OPS)
|
||
# │ Exports: gpt3_params_billion_str, gpt3_params_b_str, gpt3_training_zflops_str,
|
||
# │ gpt3_gpus_str, data_scale_gb_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Hardware, Models
|
||
from mlsys.constants import Bparam, ZFLOPs, byte, GB
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class GPT3Scale:
|
||
"""
|
||
Namespace for GPT-3 scale statistics.
|
||
Scenario: Quantifying the compute and data requirements for GPT-3.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
model = Models.GPT3
|
||
gpus_training = 1024
|
||
tokens_b = 500
|
||
avg_token_bytes = 1.4
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
# Size in Bparam
|
||
params_b = model.parameters.m_as(Bparam)
|
||
# Compute in ZFLOPs
|
||
training_zflops = model.training_ops.m_as(ZFLOPs)
|
||
# Data scale in GB
|
||
data_gb = (tokens_b * BILLION * avg_token_bytes * byte).m_as(GB)
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(params_b == 175, f"GPT-3 should be 175B params, got {params_b}")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
gpt3_params_b_str = fmt(params_b, precision=0, commas=False)
|
||
gpt3_params_billion_str = f"{gpt3_params_b_str} billion"
|
||
gpt3_training_zflops_str = fmt(round(training_zflops), precision=0, commas=False)
|
||
gpt3_gpus_str = fmt(gpus_training, precision=0, commas=True)
|
||
data_scale_gb_str = fmt(data_gb, precision=0, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
gpt3_params_b_str = GPT3Scale.gpt3_params_b_str
|
||
gpt3_params_billion_str = GPT3Scale.gpt3_params_billion_str
|
||
gpt3_training_zflops_str = GPT3Scale.gpt3_training_zflops_str
|
||
gpt3_gpus_str = GPT3Scale.gpt3_gpus_str
|
||
data_scale_gb_str = GPT3Scale.data_scale_gb_str
|
||
```
|
||
|
||
Deep learning effectively traded the **Feature Engineering Bottleneck** for a new **Compute Bottleneck**\index{Compute Bottleneck}. Models like GPT-3 [@brown2020language] (`{python} gpt3_params_billion_str` parameters) illustrate the scale of this new challenge. Training required `{python} gpt3_training_zflops_str` zettaFLOPs of compute, consumed approximately 500 billion tokens from filtered web text, books, and Wikipedia, and demanded `{python} gpt3_gpus_str` GPUs running for weeks. (One zettaFLOP equals $10^{21}$ floating-point operations; the training corpus comprised roughly `{python} data_scale_gb_str` GB of text.) The primary engineering challenge shifted from "*how* do we describe a cat's ear?" to "*how* do we coordinate `{python} gpt3_gpus_str` GPUs without failure?"
|
||
|
||
With these four paradigm shifts traced, @tbl-ai-evolution-strengths summarizes the defining bottleneck and strength of each era.
|
||
|
||
| **Aspect** | **Symbolic AI** | **Expert Systems** | **Statistical Learning** | **Deep Learning** |
|
||
|:------------------|:------------------------------|:-----------------------------------------|:-----------------------------------------------|:-----------------------------------------------|
|
||
| **Key Strength** | Logical reasoning | Domain expertise | Versatility | Pattern recognition |
|
||
| **Bottleneck** | **Brittleness** (Rules break) | **Knowledge Entry** (Experts are scarce) | **Feature Engineering** (Manual preprocessing) | **Compute & Data Scale** (Infrastructure cost) |
|
||
| **Data Handling** | Minimal data needed | Domain knowledge-based | Moderate data required | Massive data processing |
|
||
|
||
: **AI Paradigm Evolution**: Each era is defined by the systems bottleneck that constrained it. Deep learning (far right) overcame the Feature Engineering bottleneck but introduced new infrastructure challenges, necessitating modern ML systems engineering. {#tbl-ai-evolution-strengths}
|
||
|
||
The progression through four paradigms reveals a consistent pattern: each era's breakthrough came not from cleverer algorithms but from removing a systems bottleneck that prevented existing algorithms from using more data and computation. Symbolic AI had the algorithms for logic but lacked the data; expert systems had domain knowledge but could not scale it; statistical learning had the data but required human feature engineering; deep learning automated feature learning but demanded infrastructure that did not yet exist. The recurring theme is that *systems innovations*, not algorithmic innovations, enabled each transition. The pattern raises a practical dilemma: given limited resources, organizations must decide whether to invest in better algorithms, larger datasets, or higher-throughput hardware. One of AI's leading researchers examined the historical record systematically and reached a conclusion that challenges our deepest intuitions about how intelligence should be built.
|
||
|
||
## Bitter Lesson {#sec-introduction-bitter-lesson-79c7}
|
||
|
||
Richard Sutton's[^fn-sutton-bitter] 2019 essay "The Bitter Lesson"\index{Bitter Lesson, The} formalizes the historical pattern we just traced [@sutton2019bitter]. Looking back at seven decades of research, Sutton observed that general methods which use increasing computation consistently outperform approaches that encode human expertise. He writes: "The biggest lesson that can be read from 70 years of AI research is that general methods that leverage computation are ultimately the most effective, and by a large margin."
|
||
|
||
[^fn-sutton-bitter]: **Richard Sutton**: A reinforcement learning pioneer whose 2019 essay crystallized the pattern traced in the preceding sections: from symbolic AI through expert systems to deep learning, general methods leveraging computation consistently outperformed hand-engineered expertise. The lesson is "bitter" because it implies that domain-specific logic is a depreciating asset, while the durable advantage belongs to systems engineering that can absorb the billion-fold increase in raw compute since the 1970s. \index{Sutton, Richard!Bitter Lesson}
|
||
|
||
The shift from expert systems to statistical learning to deep learning has dramatically improved performance on representative tasks, with each transition enabled by increased computational scale rather than cleverer encoding of human knowledge.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: gpt4-training-scale
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ GPT-4 TRAINING SCALE
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: @tbl-ai-evolution-performance and its caption (Bitter Lesson section)
|
||
# │
|
||
# │ Goal: Quantify the massive infrastructure requirements of GPT-4.
|
||
# │ Show: The transition from minimal compute to millions of GPU-days.
|
||
# │ How: Retrieve GPT-4 training stats from the mlsys Digital Twins.
|
||
# │
|
||
# │ Imports: mlsys.constants (GPT4_TRAINING_GPU_DAYS)
|
||
# │ Exports: gpt4_gpu_m_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Models
|
||
from mlsys.constants import MILLION
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class GPT4Scale:
|
||
"""
|
||
Namespace for GPT-4 training scale.
|
||
Scenario: Quantifying the infrastructure requirement for GPT-4.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
model = Models.GPT4
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
gpu_days_m = model.training_gpu_days / MILLION
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(gpu_days_m >= 1.0, f"GPT-4 scale should be >=1M GPU days, got {gpu_days_m:.1f}M")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
gpt4_gpu_m_str = fmt(gpu_days_m, precision=1)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
gpt4_gpu_m_str = GPT4Scale.gpt4_gpu_m_str
|
||
```
|
||
|
||
@tbl-ai-evolution-performance provides quantitative validation of this principle. Examine the rightmost column carefully: as computational resources grew from "minimal" to "millions of GPU-days," performance improved from amateur-level to superhuman.
|
||
|
||
| **Era** | **Approach** | **Representative Task** | **Performance** | **Computational Resources** |
|
||
|:---------------------------------|:-------------------------------|:------------------------|--------------------:|:----------------------------------------------------------|
|
||
| **Expert Systems (1980s)** | Hand-crafted rules | Chess (Elo rating) | ~2000 Elo (amateur) | Minimal (rule evaluation) |
|
||
| **Statistical ML (1990s-2000s)** | Feature engineering + learning | ImageNet top-5 accuracy | 50–60% | Hours on single CPU |
|
||
| **Deep Learning (2012)** | End-to-end neural networks | ImageNet top-5 accuracy | 84.6% (AlexNet) | 6 days on 2 GPUs |
|
||
| **Modern Deep Learning (2020+)** | Large-scale transformers | ImageNet top-5 accuracy | 90.0%+ (ViT) | Hours on distributed systems |
|
||
| **Modern Deep Learning (2023)** | Foundation models | MMLU benchmark | 86.4% (GPT-4) | Estimated `{python} gpt4_gpu_m_str` million A100 GPU-days |
|
||
|
||
: **AI Performance Evolution Across Paradigms**: Each paradigm transition correlates with increased computational scale rather than algorithmic sophistication. Performance improved from amateur-level expert systems (2000 Elo) to superhuman foundation models (86.4% MMLU), while computational requirements grew from single CPUs to 2.5 million A100 GPU-days. Training time initially increased (hours to days) but later decreased as distributed systems enabled parallelization. {#tbl-ai-evolution-performance}
|
||
|
||
MMLU (Multitask Multiple-choice Language Understanding) is a standard benchmark for broad knowledge across many subjects; we formalize benchmarking in @sec-benchmarking.
|
||
|
||
The table reveals two additional insights. Training time initially increased (hours to days) but then decreased (back to hours) as distributed systems enabled parallelization. The most dramatic improvements occurred at paradigm transitions (expert systems to statistical learning, statistical learning to deep learning) when new approaches unlocked the ability to use more computation effectively. The pattern validates Sutton's observation: progress comes from finding ways to use more compute, not from encoding more human knowledge.
|
||
|
||
\index{Deep Blue}
|
||
The principle finds further validation across AI breakthroughs. In chess, IBM's Deep Blue defeated world champion Garry Kasparov[^fn-deepblue-compute] in 1997 [@campbell2002deep] not by encoding chess strategies, but through brute-force search evaluating millions of positions per second.
|
||
|
||
[^fn-deepblue-compute]: **Deep Blue**: IBM's chess system defeated World Champion Garry Kasparov in 1997 not through strategic understanding but through raw computational power: 200 million positions per second on 480 custom chess processors. Deep Blue was the first public demonstration that purpose-built silicon ($R_{peak}$) could substitute for human expertise, foreshadowing the domain-specific accelerator strategy that defines modern ML hardware. \index{Deep Blue!computational scale}
|
||
|
||
\index{AlphaGo}
|
||
In Go, DeepMind's AlphaGo[^fn-alphago-scale] [@silver2016mastering] achieved superhuman performance by learning from self-play rather than studying centuries of human Go wisdom.
|
||
|
||
[^fn-alphago-scale]: **AlphaGo**: Rather than learning from human games, AlphaGo generated its own training data through millions of self-play games, trading reliance on a limited expert corpus for the ability to explore the problem space at massive computational scale. The successor system, AlphaGo Zero, used this principle exclusively: it surpassed the original after just three days on 4 TPUs, winning 100 games to 0, but required roughly 5,000 TPU-hours of self-play compute, making infrastructure budget rather than human expertise the binding constraint. \index{AlphaGo!computational scale}
|
||
|
||
The lesson is "bitter" because our intuition misleads us\index{Bitter Lesson, The}. We naturally assume that encoding human expertise should be the path to artificial intelligence. Yet repeatedly, systems that use computation to learn from data outperform systems that rely on human knowledge given sufficient scale. The pattern has held across symbolic AI, statistical learning, and deep learning eras.
|
||
|
||
Modern language models like GPT-4 and image generation systems like DALL-E illustrate this principle directly. Their capabilities emerge not from linguistic or artistic theories encoded by humans but from training general-purpose neural networks on vast amounts of data using substantial computational resources. Estimates for models at GPT-3's scale suggest thousands of megawatt-hours of energy[^fn-gpt3-training-energy] [@patterson2021carbon], and serving these models to millions of users demands data centers consuming power comparable to small cities.
|
||
|
||
[^fn-gpt3-training-energy]: **GPT-3 Training Energy**: Patterson et al. estimated GPT-3's single training run consumed approximately 1,287 MWh and emitted 552 tonnes of CO2-equivalent, roughly the annual electricity of 120 average US households. The energy cost is dominated not by arithmetic but by data movement through the memory hierarchy, making the $D_{vol}/BW$ term of the Iron Law, not the compute term, the primary driver of the power bill at this scale. \index{GPT-3!training energy}
|
||
|
||
The implication is that realizing the Bitter Lesson's promise requires expertise in data engineering, hardware optimization, and systems coordination[^fn-memory-bandwidth-bottleneck] that goes far beyond algorithmic innovation. We explore these hardware constraints quantitatively in @sec-hardware-acceleration, where students will have the prerequisite background to analyze memory bandwidth limitations and their implications for system design.
|
||
|
||
[^fn-memory-bandwidth-bottleneck]: **Memory Bandwidth**: The rate at which a model's parameters move from memory to the processor. The "thousands of megawatt-hours" of energy consumed by GPT-scale models are dominated not by computation but by the physically expensive process of fetching billions of weights through the memory hierarchy. Moving data from off-chip memory can consume over 500x more energy than the actual arithmetic, making this bandwidth, not processor speed, the direct cause of the data center's massive power draw. \index{Memory Bandwidth!bottleneck}
|
||
|
||
Sutton's bitter lesson explains the motivation for ML systems engineering. If AI progress depends on our ability to scale computation effectively, then understanding *how* to build, deploy, and maintain these computational systems is essential for AI practitioners. Yet this understanding demands more than familiarity with any single technical domain. Computer Science advances ML algorithms, and Electrical Engineering develops specialized AI hardware, but neither discipline alone provides the engineering principles needed to deploy, optimize, and sustain ML systems at scale. The convergence of data management, algorithmic design, and infrastructure optimization into a single engineering challenge has given rise to a new discipline, one we define formally later in this chapter and develop across the entire book.
|
||
|
||
The Bitter Lesson tells us *why* scale matters. The natural next question is what kind of systems make that scale practical. We turn to a precise characterization of machine learning systems themselves.
|
||
|
||
## Defining ML Systems {#sec-introduction-defining-ml-systems-d4af}
|
||
|
||
Rather than beginning with an abstract definition, consider a system most people interact with daily: email spam filtering.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: email-scale
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ EMAIL SCALE STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: "Email Spam Filtering" concrete example
|
||
# │
|
||
# │ Goal: Ground the abstract definition of ML systems in a familiar application.
|
||
# │ Show: That even "simple" ML tasks like spam filtering operate at massive scale.
|
||
# │ How: Calculate annual email volume from Gmail's daily traffic.
|
||
# │
|
||
# │ Imports: mlsys.constants (GMAIL_EMAILS_PER_DAY)
|
||
# │ Exports: gmail_emails_t_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.constants import GMAIL_EMAILS_PER_DAY, TRILLION
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class EmailScale:
|
||
"""Gmail annual email volume for spam-filtering scale context."""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
daily_emails = GMAIL_EMAILS_PER_DAY
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
gmail_emails_t_value = daily_emails * 365 / TRILLION
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(gmail_emails_t_value >= 1, f"Gmail annual volume ({gmail_emails_t_value:.0f}T) unexpectedly low.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
gmail_emails_t_str = fmt(gmail_emails_t_value, precision=0)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
gmail_emails_t_str = EmailScale.gmail_emails_t_str
|
||
```
|
||
|
||
Consider the spam filter protecting your inbox. Every day, it processes millions of emails, deciding in milliseconds which messages deserve your attention and which should be quarantined. Gmail alone processes approximately `{python} gmail_emails_t_str` trillion emails annually, with spam comprising roughly 50% of all email traffic [@statista2024email]. Production spam filters typically target accuracy above 99.9% while processing each email in under 50 ms to avoid noticeable delays.
|
||
|
||
This deceptively simple task reveals *what* distinguishes machine learning systems from traditional software. The challenge begins with data: the filter trains on millions of labeled examples, constantly adapting as spammers evolve their tactics. Traditional software would require programmers to encode rules for every spam pattern manually, but the ML approach learns patterns automatically from data, adapting to new spam techniques without programmer intervention.
|
||
|
||
The challenge extends to algorithms: the system must generalize from training examples to recognize spam it has never seen before, balancing precision against recall to avoid false positives that hide legitimate emails while catching actual spam. This probabilistic decision-making differs fundamentally from deterministic software logic.
|
||
|
||
The challenge reaches into infrastructure as well: servers must process billions of emails daily, storing models that encode learned patterns, updating those models as spam evolves, and serving predictions with sub-100 ms latency across horizontally scaled data centers.
|
||
|
||
These three interconnected concerns, obtaining and managing training data at scale, implementing algorithms that learn and generalize effectively, and building infrastructure that supports both training and real-time prediction, appear in every machine learning system. No traditional software system exhibits all three simultaneously. With this concrete grounding, we can state precisely what a machine learning system is:
|
||
|
||
::: {.callout-definition title="Machine Learning Systems"}
|
||
|
||
***Machine Learning Systems***\index{Machine Learning Systems!definition} are software architectures where **Behavior** is learned from data rather than specified by explicit code. They extend the traditional software stack (OS, Network, DB) with a **Data Stack** (Ingestion, Training, Serving).
|
||
|
||
1. **Significance (Quantitative):** They enable the automation of tasks where the **Instruction Count** for a manual implementation would be infinite or the **Logic** too complex for human specification.
|
||
2. **Distinction (Durable):** Unlike traditional software, which manages **Deterministic State**, ML Systems manage **Probabilistic Uncertainty** and **Distribution Shift**.
|
||
3. **Common Pitfall:** A frequent misconception is that ML Systems are "just models." In reality, the model is a small fraction of the total system; the "System" encompasses the entire feedback loop of data collection, verification, and deployment.
|
||
|
||
:::
|
||
|
||
Recall the **Data · Algorithm · Machine (D·A·M) taxonomy** introduced at the start of this chapter. We now formalize its diagnostic application\index{D·A·M taxonomy}: when performance stalls or behavior degrades, ask *"Which axis is the bottleneck?"*
|
||
|
||
::: {.callout-definition title="The D·A·M Taxonomy"}
|
||
|
||
The ***D·A·M Taxonomy***\index{D·A·M taxonomy!definition} is the structural framework for classifying any machine learning system by its primary components: **Data**, **Algorithm**, and **Machine**.
|
||
|
||
1. **Significance (Quantitative):** It identifies the **Binding Constraint** on a system's performance. Within the **Iron Law**, $D_{vol}$ maps to Data, the mathematical operations ($O$) map to the Algorithm, and the hardware capabilities ($BW, R_{peak}$) map to the Machine.
|
||
2. **Distinction (Durable):** Unlike **Traditional Software Architecture**, which separates code from data, the D·A·M Taxonomy treats the **Algorithm and Data** as a single, interdependent entity that is fundamentally constrained by the **Machine**.
|
||
3. **Common Pitfall:** A frequent misconception is that these three axes are independent. In reality, they are an **Inseparable Triad**: changing the Algorithm often mandates a different Machine or a new Data distribution.
|
||
|
||
:::
|
||
|
||
Think of the three components as **Data** (the fuel), **Algorithm** (the blueprint), and **Machine** (the engine). Without any one component, the others remain theoretical. We call this conceptual relationship the **AI Triad**, the recognition that these three elements are fundamentally interdependent.
|
||
|
||
To visualize these interdependencies, consider the triangle in @fig-ai-triad: the bidirectional arrows between Data, Algorithm, and Machine emphasize that no axis can be optimized in isolation. Each element shapes the possibilities of the others. The algorithm dictates both the computational demands for training and inference and the volume and structure of data required for effective learning. The data's scale and complexity influence what machines are needed for storage and processing while determining which algorithms are feasible. The machine's capabilities establish practical limits on both model scale and data processing capacity, creating a boundary within which the other axes must operate.
|
||
|
||
::: {#fig-ai-triad fig-env="figure" fig-pos="htb" fig-cap="**The D·A·M Taxonomy**: Interdependent relationship between Data, Algorithm, and Machine. Each node (dataset, model, and infrastructure) constrains the capabilities of the others. ML systems engineering is the discipline of balancing these three axes; optimizing one in isolation often shifts the system bottleneck to another rather than eliminating it." fig-alt="Triangle diagram with three circles at vertices labeled Model, Data, and Machine. Double-headed purple arrows connect all three nodes, showing bidirectional dependencies. Icons inside circles depict neural network, database cylinders, and cloud."}
|
||
```{.tikz}
|
||
\scalebox{0.8}{
|
||
\begin{tikzpicture}[line join=round,font=\usefont{T1}{phv}{m}{n}\small]
|
||
\tikzset{
|
||
Line/.style={line width=0.35pt,black!50,text=black},
|
||
ALineA/.style={violet!80!black!50,line width=3pt,shorten <=2pt,shorten >=2pt,
|
||
{Triangle[width=1.1*6pt,length=0.8*6pt]}-{Triangle[width=1.1*6pt,length=0.8*6pt]}},
|
||
LineD/.style={line width=0.75pt,black!50,text=black,dashed,dash pattern=on 5pt off 3pt},
|
||
Circle/.style={inner xsep=2pt,
|
||
circle,
|
||
draw=BrownLine,
|
||
line width=0.75pt,
|
||
fill=BrownL!40,
|
||
minimum size=16mm
|
||
},
|
||
circles/.pic={
|
||
\pgfkeys{/channel/.cd, #1}
|
||
\node[circle,draw=\channelcolor,line width=\Linewidth,fill=\channelcolor!10,
|
||
minimum size=2.5mm](\picname){};
|
||
}
|
||
}
|
||
\tikzset {
|
||
pics/cloud/.style = {
|
||
code = {\colorlet{red}{RedLine}
|
||
\begin{scope}[local bounding box=CLO,scale=0.5, every node/.append style={transform shape}]
|
||
\draw[red,fill=white,line width=0.9pt](0.67,1.21)to[out=55,in=90,distance=13](1.5,0.96)
|
||
to[out=360,in=30,distance=9](1.68,0.42);
|
||
\draw[red,fill=white,line width=0.9pt](0,0)to[out=170,in=180,distance=11](0.1,0.61)
|
||
to[out=90,in=105,distance=17](1.07,0.71)
|
||
to[out=20,in=75,distance=7](1.48,0.36)
|
||
to[out=350,in=0,distance=7](1.48,0)--(0,0);
|
||
\draw[red,fill=white,line width=0.9pt](0.27,0.71)to[bend left=25](0.49,0.96);
|
||
|
||
\end{scope}
|
||
}
|
||
}
|
||
}
|
||
%streaming
|
||
\tikzset{%
|
||
LineST/.style={-{Circle[\channelcolor,fill=RedLine,length=4pt]},draw=\channelcolor,line width=\Linewidth,rounded corners},
|
||
ellipseST/.style={fill=\channelcolor,ellipse,minimum width = 2.5mm, inner sep=2pt, minimum height =1.5mm},
|
||
BoxST/.style={line width=\Linewidth,fill=white,draw=\channelcolor,rectangle,minimum width=56,
|
||
minimum height=16,rounded corners=1.2pt},
|
||
pics/streaming/.style = {
|
||
code = {\pgfkeys{/channel/.cd, #1}
|
||
\begin{scope}[local bounding box=STREAMING,scale=\scalefac, every node/.append style={transform shape}]
|
||
\node[BoxST,minimum width=44,minimum height=48](\picname-RE1){};
|
||
\foreach \i/\j in{1/north,2/center,3/south}{
|
||
\node[BoxST](\picname-GR\i)at(\picname-RE1.\j){};
|
||
\node[ellipseST]at($(\picname-GR\i.west)!0.2!(\picname-GR\i.east)$){};
|
||
\node[ellipseST]at($(\picname-GR\i.west)!0.4!(\picname-GR\i.east)$){};
|
||
}
|
||
\draw[LineST](\picname-GR3)--++(2,0)coordinate(\picname-C4);
|
||
\draw[LineST](\picname-GR3.320)--++(0,-0.7)--++(0.8,0)coordinate(\picname-C5);
|
||
\draw[LineST](\picname-GR3.220)--++(0,-0.7)--++(-0.8,0)coordinate(\picname-C6);
|
||
\draw[LineST](\picname-GR3)--++(-2,0)coordinate(\picname-C7);
|
||
\end{scope}
|
||
}
|
||
}
|
||
}
|
||
%data
|
||
\tikzset{mycylinder/.style={cylinder, shape border rotate=90, aspect=1.3, draw, fill=white,
|
||
minimum width=25mm,minimum height=11mm,line width=\Linewidth,node distance=-0.15},
|
||
pics/data/.style = {
|
||
code = {\pgfkeys{/channel/.cd, #1}
|
||
\begin{scope}[local bounding box=STREAMING,scale=\scalefac, every node/.append style={transform shape}]
|
||
\node[mycylinder,fill=\channelcolor!50] (A) {};
|
||
\node[mycylinder, above=of A,fill=\channelcolor!30] (B) {};
|
||
\node[mycylinder, above=of B,fill=\channelcolor!10] (C) {};
|
||
\end{scope}
|
||
}
|
||
}
|
||
}
|
||
\pgfkeys{
|
||
/channel/.cd,
|
||
channelcolor/.store in=\channelcolor,
|
||
drawchannelcolor/.store in=\drawchannelcolor,
|
||
scalefac/.store in=\scalefac,
|
||
Linewidth/.store in=\Linewidth,
|
||
picname/.store in=\picname,
|
||
channelcolor=BrownLine,
|
||
drawchannelcolor=BrownLine,
|
||
scalefac=1,
|
||
Linewidth=0.5pt,
|
||
picname=C
|
||
}
|
||
\node[Circle](MO){};
|
||
\node[Circle,below right=1 and 2.5 of MO,draw=GreenLine,fill=GreenL!40,](IN){};
|
||
\node[Circle,below left=1 and 2.5 of MO,draw=OrangeLine,fill=OrangeL!40,](DA){};
|
||
\draw[ALineA](MO)--(IN);
|
||
\draw[ALineA](MO)--(DA);
|
||
\draw[ALineA](DA)--(IN);
|
||
\node[below=2pt of MO]{Algorithm};
|
||
\node[below=2pt of IN]{Machine};
|
||
\node[below=2pt of DA]{Data};
|
||
%%
|
||
\begin{scope}[local bounding box=CIRCLE1,shift={($(MO)+(0.04,-0.24)$)},
|
||
scale=0.55, every node/.append style={transform shape}]
|
||
%1 column
|
||
\foreach \j in {1,2,3} {
|
||
\pgfmathsetmacro{\y}{(1.5-\j)*0.43 + 0.7}
|
||
\pic at (-0.8,\y) {circles={channelcolor=RedLine,picname=1CD\j}};
|
||
}
|
||
%2 column
|
||
\foreach \i in {1,...,4} {
|
||
\pgfmathsetmacro{\y}{(2-\i)*0.43+0.7}
|
||
\pic at (0,\y) {circles={channelcolor=RedLine, picname=2CD\i}};
|
||
}
|
||
%3 column
|
||
\foreach \j in {1,2} {
|
||
\pgfmathsetmacro{\y}{(1-\j)*0.43 + 0.7}
|
||
\pic at (0.8,\y) {circles={channelcolor=RedLine,picname=3CD\j}};
|
||
}
|
||
\foreach \i in {1,2,3}{
|
||
\foreach \j in {1,2,3,4}{
|
||
\draw[Line](1CD\i)--(2CD\j);
|
||
}}
|
||
\foreach \i in {1,2,3,4}{
|
||
\foreach \j in {1,2}{
|
||
\draw[Line](2CD\i)--(3CD\j);
|
||
}}
|
||
\end{scope}
|
||
%
|
||
\pic[shift={(-0.4,-0.08)}] at (IN) {cloud};
|
||
%
|
||
\pic[shift={(-0.05,-0.13)}] at (IN){streaming={scalefac=0.25,picname=2,channelcolor=RedLine, Linewidth=0.65pt}};
|
||
%
|
||
\pic[shift={(0,-0.3)}] at (DA){data={scalefac=0.3,picname=1,channelcolor=green!70!black, Linewidth=0.4pt}};
|
||
\end{tikzpicture}}
|
||
```
|
||
:::
|
||
|
||
ML systems engineering is the discipline of keeping all three axes in balance. @tbl-dam-taxonomy formalizes each axis's role.
|
||
|
||
| **Axis** | **Definition** | **Role in System** |
|
||
|:--------------|:-------------------------------------|:-----------------------------------------------------|
|
||
| **Data** | Information that guides behavior | *The Fuel*: Defines what the system learns |
|
||
| **Algorithm** | Mathematical structures that learn | *The Blueprint*: Defines how patterns are captured |
|
||
| **Machine** | Hardware and software infrastructure | *The Engine*: Defines computation speed and location |
|
||
|
||
: **The D·A·M Taxonomy**\index{D·A·M taxonomy!components}: Every ML system comprises these three interdependent axes, and optimizing one in isolation typically shifts the bottleneck to another rather than eliminating it. The diagnostic question, *"Which axis is the bottleneck?"*, recurs throughout this text as the first step in any systems analysis. {#tbl-dam-taxonomy}
|
||
|
||
The D·A·M taxonomy provides the diagnostic lens, but to build systems, we must organize these axes into a reproducible hierarchy. We formalize this throughout the book as the **Engineering Crux**: a four-layer stack that transforms raw physical constraints into functional user applications.
|
||
|
||
### The Engineering Crux: A Hierarchy of Architecture {#sec-introduction-engineering-crux}
|
||
|
||
\index{Engineering Crux!hierarchy}
|
||
Every machine learning system analyzed in this text is constructed from four hierarchical layers. This **Engineering Crux** transforms raw physical constraints into functional user applications, ensuring that a decision made at the silicon level is traceable to its impact on the final mission.
|
||
|
||
1. **Hardware (The Silicon)**: The physical foundation (The Engine). This layer defines the raw capabilities: $R_{peak}$, $BW$, and memory capacity. We use real-world hardware "Twins" like the **NVIDIA H100** and **ESP32-S3**.
|
||
2. **Systems (The Platforms)**: The integrated deployment unit (The Car). This layer defines the "Envelope" in which hardware operates: power budget, thermal limits, and node-level interconnects. Examples include the **Training Cluster Node** or the **Sub-Watt Sensor Node**.
|
||
3. **Workloads (The Models)**: The algorithmic demand (The Route). This layer defines the mathematical workload: operation count ($O$), parameter volume ($D_{vol}$), and data layout. We use **Lighthouse Workloads** like **GPT-4** and **Wake Vision**.
|
||
4. **Missions (The Scenarios)**: The application context (The Destination). This is the top of the stack, where a system is deployed to solve a specific problem. A **Mission**—such as the **Smart Doorbell**—introduces high-level requirements (e.g., "1-year battery life") that dictate the configuration of every layer below.
|
||
|
||
This hierarchy ensures that when we build a lab or a case study, we are not starting from scratch. We are "inheriting" the constraints of a System Archetype and applying a Lighthouse Model to a specific mission. For instance, the **Smart Doorbell** scenario (@sec-introduction-deployment-case-studies-636f) inherits the **TinyML Archetype**, uses the **Wake Vision** model, and operates on **ESP32** hardware. This structured approach allows us to reason about the "Physics of ML" across any application domain.
|
||
|
||
The D·A·M taxonomy serves as a diagnostic lens throughout this text.
|
||
Scale in ML systems is the relentless pursuit of the *moving bottleneck*. Alleviating a constraint along one axis often shifts the limitation to another. Upgrading to faster GPUs (Machine) might reveal that storage cannot feed data fast enough (Data). Collecting a massive dataset (Data) might reveal that the model lacks capacity to learn from it (Algorithm). Switching to a larger model (Algorithm) might exceed available memory (Machine). Understanding these dynamics is central to ML systems engineering. Part III formalizes this diagnostic approach with the D·A·M$\times$ Bottleneck matrix (@sec-benchmarking). For the complete diagnostic guide, intersection landscape (@fig-dam-venn), and troubleshooting matrix, consult the **D·A·M Taxonomy** appendix (@sec-dam-taxonomy).
|
||
|
||
\index{Samples per Dollar}
|
||
These three components interact through a single economic constraint that systems engineers must optimize: *samples per dollar*.
|
||
|
||
::: {.callout-perspective title="Samples per Dollar"}
|
||
**The Systems View**: While researchers optimize for *accuracy*, systems engineers optimize for **Samples per Dollar**. Let **Model Size** be the parameter count, **Dataset Size** the number of training samples, and **Hardware Efficiency** the compute throughput per dollar (FLOPs/$, where FLOPS are floating-point operations per second—formalized in the Iron Law below). This metric unifies the three axes of the D·A·M taxonomy into a single constraint equation, shown in @eq-cost-scaling:
|
||
|
||
$$ \text{Cost} \propto \frac{\text{Model Size} \times \text{Dataset Size}}{\text{Hardware Efficiency}} $$ {#eq-cost-scaling}
|
||
|
||
* **Data** (Information): Improving data quality (cleaning, filtering) increases the "learning value" of each sample, effectively reducing the numerator.
|
||
* **Algorithm** (Logic): More efficient architectures (like Transformers vs RNNs) reduce the compute per sample, lowering the numerator.
|
||
* **Machine** (Physics): Specialized hardware (GPUs/TPUs) increases the denominator, allowing more compute per dollar.
|
||
|
||
Systems engineering is the art of balancing this equation. As a rough illustration: a 10% gain in hardware efficiency allows for a 10% larger dataset, which might yield a 1% gain in accuracy. The engineer's job is to determine if that trade-off is economically viable.
|
||
:::
|
||
|
||
The D·A·M taxonomy tells us what an ML system is made of. Understanding the *components* of a system is not the same as understanding *how those components interact under stress*. Traditional software systems share the same basic ingredients (data, logic, infrastructure) yet fail in completely different ways. The distinctive failure mode of ML systems, silent degradation rather than explicit crashes, is what makes them genuinely new from an engineering standpoint.
|
||
|
||
## ML vs. Traditional Software {#sec-introduction-ml-vs-traditional-software-e19a}
|
||
|
||
\index{Inference}
|
||
The D·A·M taxonomy reveals *what* ML systems comprise: data that guides behavior, algorithms that extract patterns, and machines that enable learning and inference[^fn-inference-deployment]. The critical distinction between ML systems engineering and traditional software engineering lies not in these components themselves but in *how the resulting systems fail*.
|
||
|
||
[^fn-inference-deployment]: **Inference**: From Latin *inferre* ("to bring in" or "to conclude"). In ML engineering, inference refers to the deployment phase where a trained model applies learned patterns to novel inputs. The systems distinction matters: training is throughput-optimized (maximize samples/second), while inference is latency-optimized (minimize milliseconds/prediction), and these opposing objectives demand fundamentally different hardware configurations and software stacks (see @sec-model-serving). \index{Inference!deployment}
|
||
|
||
Traditional software exhibits explicit failure modes. When code breaks, applications crash, error messages propagate, and monitoring systems trigger alerts. This immediate feedback enables rapid diagnosis and remediation: the system operates correctly or fails observably. Machine learning systems operate under a different paradigm\index{Silent Degradation}. They can continue functioning while their performance degrades silently, without triggering conventional error detection mechanisms. The algorithms continue executing and the machines maintain prediction serving, yet the learned behavior becomes progressively less accurate or contextually relevant.
|
||
|
||
An autonomous vehicle's perception system illustrates this distinction concretely. Traditional automotive software exhibits binary operational states: the engine control unit either manages fuel injection correctly or triggers diagnostic warnings. The failure mode remains observable through standard monitoring. An ML-based perception system presents a different challenge. The system's accuracy in detecting pedestrians might decline from 95% to 85% over several months due to seasonal changes, as different lighting conditions, clothing patterns, or weather phenomena underrepresented in training data affect model performance. The vehicle continues operating, successfully detecting most pedestrians, yet the degraded performance creates safety risks that become apparent only through systematic monitoring of edge cases and comprehensive evaluation. Conventional error logging and alerting mechanisms remain silent while the system becomes measurably less safe.
|
||
|
||
The magnitude of this degradation matters in safety-critical contexts. For autonomous vehicles, even 95% accuracy may be inadequate: safety-critical systems typically require 99.9% or higher reliability. The 10-percentage-point degradation from 95% to 85% is especially concerning because failures concentrate in edge cases where detection was already marginal, precisely the scenarios where human safety is most at risk.
|
||
|
||
This silent degradation\index{Silent Degradation} manifests across all three D·A·M axes. The data distribution shifts as the world changes: user behavior evolves, seasonal patterns emerge, and new edge cases appear. Meanwhile, the algorithms continue making predictions based on outdated learned patterns, unaware that their training distribution no longer matches operational reality. The machines faithfully serve these increasingly inaccurate predictions at scale, amplifying the problem across every user and every query.
|
||
|
||
Because this failure mode is silent, we cannot rely on crash logs to detect it. We must rely on math. When failures do not announce themselves, we need quantitative signals that connect measurable distribution shift to expected performance loss. Just as Patterson and Hennessy's Iron Law [@patterson2021hardware] decomposed CPU performance into fundamental components, we can decompose ML system degradation into constituent factors. The **Degradation Equation**\index{Degradation Equation} in @eq-degradation captures how model performance evolves over time:
|
||
|
||
$$ \text{Accuracy}(t) \approx \text{Accuracy}_0 - \lambda \cdot D(P_t \| P_0) $$ {#eq-degradation}
|
||
|
||
where:
|
||
|
||
* $\text{Accuracy}_0$: Initial accuracy at deployment
|
||
* $D(P_t \| P_0)$: Statistical divergence between current data distribution $P_t$ and training distribution $P_0$
|
||
* $\lambda$: Model sensitivity to distribution shift (architecture-dependent)
|
||
|
||
This first-order linearization captures the dominant trend: accuracy erodes roughly in proportion to how far the current data distribution has drifted from the training distribution. The model breaks down for large shifts (where the relationship becomes nonlinear) and the specific divergence measure $D$ is left deliberately general (common choices include KL divergence, total variation distance, or Wasserstein distance, each with different sensitivity profiles). Despite these simplifications, the equation reveals three engineering levers for managing degradation:
|
||
|
||
1. **Improve initial accuracy** ($\text{Accuracy}_0$): Better training, more data, superior architectures. This shifts the curve but not its slope.
|
||
|
||
2. **Reduce distribution sensitivity** ($\lambda$): Robust training techniques, domain adaptation, broader training distributions. These flatten the degradation curve.
|
||
|
||
3. **Monitor and respond to drift** ($D$)\index{Data Drift}: Continuous measurement of distribution divergence enables proactive retraining before accuracy falls below acceptable thresholds.
|
||
|
||
The practical implication: *knowing when to retrain is as important as knowing how to train*.\index{Retraining} A system that retrains when $D(P_t \| P_0) > \tau$ for some threshold $\tau$ maintains accuracy within bounds. A system without drift monitoring operates blind to its own degradation. We develop the monitoring infrastructure and alerting strategies that implement this principle in @sec-ml-operations.
|
||
|
||
This framework distinguishes ML systems engineering from traditional software engineering at the deepest level. Traditional systems have no equivalent equation because they do not drift: a function that computed correctly yesterday computes correctly today. ML systems require continuous investment in monitoring infrastructure that traditional software never needed, and the Degradation Equation quantifies why. It is the engineering response to the Verification Gap identified earlier (@eq-verification-gap): since we cannot test exhaustively, we must monitor continuously.
|
||
|
||
A recommendation system illustrates: it might decline from 85% to below 40% accuracy over six months as user preferences evolve and training data becomes stale, precisely the degradation the equation predicts. This degradation often stems from training-serving skew\index{Training-Serving Skew}, where features computed differently between training and serving pipelines cause model performance to degrade despite unchanged code (@sec-ml-operations develops the detection pipelines and mitigation strategies that catch such skew before it reaches users). This is a machine issue that manifests as algorithmic failure.
|
||
|
||
This difference in failure modes demands new engineering practices. Traditional software development focuses on eliminating bugs and ensuring deterministic behavior, but ML systems engineering must additionally address probabilistic behaviors, evolving data distributions, and performance degradation that occurs without code changes. Monitoring systems must track not just infrastructure health but also model performance, data quality, and prediction distributions. Deployment practices must enable continuous model updates as data distributions shift. In short, the entire system lifecycle, from data collection through model training to inference serving, must be designed with silent degradation in mind.
|
||
|
||
The Degradation Equation reveals *what goes wrong* with ML systems: silent reliability decay absent from traditional software. Knowing that a system will degrade is not the same as knowing *why* it degrades or *where* to intervene. For that, we need to decompose performance itself into its physical constituents. The Bitter Lesson established that computational scale drives AI progress; the question now becomes how to reason quantitatively about the data movement, computation, and overhead that constitute that scale.
|
||
|
||
## Iron Law of ML Systems {#sec-introduction-iron-law-ml-systems-c32a}
|
||
|
||
\index{Iron Law of ML Systems!definition}
|
||
To reason about ML systems as engineers, we need more than qualitative descriptions. We need a quantitative framework that connects every layer of the stack. Just as classical mechanics is governed by Newton's laws and processor performance is governed by the Iron Law of Processor Performance, machine learning system performance is governed by the **Iron Law of ML Systems**\index{Iron Law of ML Systems!equation}, formalized in @eq-intro-iron-law:
|
||
|
||
$$\text{Time}_{\text{total}} = \underbrace{ \frac{\text{Data} (D_{vol})}{\text{Bandwidth} (BW)} }_{\text{The Data Term}} + \underbrace{ \frac{\text{Ops} (O)}{\text{Peak} (R_{peak}) \times \text{Efficiency} (\eta)} }_{\text{The Compute Term}} + \underbrace{ \text{Overhead} (L_{lat}) }_{\text{The Latency Term}}$$ {#eq-intro-iron-law}
|
||
|
||
*This equation is the mathematical spine of this book.* It decomposes the total time required for any ML task, whether training a model for weeks or serving an inference in milliseconds, into three terms that correspond directly to the physical constraints of the Dual Mandate introduced earlier:
|
||
|
||
1. **The Data Term ($D_{vol}/BW$)**: The physical cost of moving bits. $D_{vol}$ is the volume of data moved (bytes), and $BW$ is the memory or network bandwidth (bytes/sec). Whether loading terabytes from cloud storage or fetching weights from high-bandwidth memory, performance is often limited by I/O physics. We address this in **Part I: Foundations**.
|
||
2. **The Compute Term ($O / (R_{peak} \cdot \eta)$)**: The cost of arithmetic. $O$ is the number of floating-point operations. $R_{peak}$ is the hardware's theoretical peak throughput (FLOPS). $\eta$ (eta) is the utilization factor ($0 \le \eta \le 1$), representing software efficiency. We address this in **Part II: Build** and **Part III: Optimize**.
|
||
3. **The Overhead Term ($L_{lat}$)**: The irreducible "tax" of system orchestration, networking, and serialization. This fixed latency dominates in real-time deployment. We address this in **Part IV: Deploy**.
|
||
|
||
::: {.callout-perspective title="The Iron Law Analogy"}
|
||
We call this the "Iron Law" by analogy to Patterson & Hennessy's Iron Law of Processor Performance [@patterson2021hardware]. However, there are important differences. P&H's law is a **multiplicative decomposition** (a tautology factoring CPU time), whereas our equation is an **additive first-order model** that approximates performance under simplifying assumptions.
|
||
|
||
The additive form assumes sequential execution; in practice, systems can **overlap** these terms, transforming the sum into a max as @eq-intro-iron-law-pipelined shows:
|
||
|
||
$$T_{pipelined} = \max\left(\frac{D_{vol}}{BW}, \frac{O}{R_{peak} \cdot \eta}\right) + L_{lat}$$ {#eq-intro-iron-law-pipelined}
|
||
|
||
\index{Amdahl's Law!diagnostic analogy}
|
||
We retain "Iron Law" because, like **Amdahl's Law**, its value lies in **diagnostic power**: identifying which physical constraint dominates before optimizing. The Iron Law is useful precisely because it simplifies the complexity of the full stack into three manageable terms. @sec-dam-taxonomy presents the refined treatment, including pipelining and overlap techniques that transform the additive model into the max-based formulation used in practice.
|
||
:::
|
||
|
||
As George Box famously said, "All models are wrong, but some are useful."[^fn-box-ironlaw]
|
||
|
||
[^fn-box-ironlaw]: **"All models are wrong, but some are useful"**: Statistician George Box's aphorism applies directly to the Iron Law: the additive decomposition ignores pipelining, memory hierarchy effects, and communication overhead. Yet this deliberate simplification is precisely what makes it diagnostic. Engineers who try to model every interaction before profiling never ship; engineers who identify which of three terms dominates ship systems that work. \index{Box, George!aphorism}
|
||
|
||
@sec-machine-foundations develops the deeper mathematical treatment, including the Roofline Model that visualizes how data movement and computation trade off on specific hardware.
|
||
|
||
Throughout this book, every optimization technique we study, from pruning to kernel fusion, is a method for manipulating one of these variables. The following notebook demonstrates this by *training GPT-3* as a worked example of the Iron Law in practice.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: gpt3-training
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ GPT-3 TRAINING (IRON LAW EXAMPLE)
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Callout "Training GPT-3" — worked notebook example
|
||
# │
|
||
# │ Goal: Apply the Iron Law to estimate large-scale training time.
|
||
# │ Show: That improving efficiency from 45% to 60% saves significant compute days.
|
||
# │ How: Calculate training duration using the dTime formula for GPT-3.
|
||
# │
|
||
# │ Imports: mlsys.constants (GPT3_TRAINING_OPS, A100_FLOPS_FP16_TENSOR,
|
||
# │ TFLOPs, second, day),
|
||
# │ mlsys.formulas (dTime), mlsys.formatting (fmt, md_math)
|
||
# │ Exports: num_gpus_str, efficiency_eta_pct_str, target_eta_pct_str,
|
||
# │ days_initial_str, days_optimized_str, days_saved_str,
|
||
# │ peak_tflops_str, time_formula_math, time_value_math,
|
||
# │ time_days_math, ops_math
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Models, Hardware
|
||
from mlsys.constants import (
|
||
GPT3_TRAINING_OPS, A100_FLOPS_FP16_TENSOR, TFLOPs, second, day,
|
||
TRILLION, SEC_PER_DAY, flop
|
||
)
|
||
from mlsys.formulas import dTime
|
||
from mlsys.formatting import fmt, md_math
|
||
|
||
# --- Inputs (cluster configuration) ---
|
||
num_gpus_value = 1024
|
||
efficiency_eta_value = 0.45
|
||
target_eta_value = 0.60
|
||
|
||
# --- Process (Iron Law training time calculation) ---
|
||
training_time_value = dTime(
|
||
total_ops=GPT3_TRAINING_OPS,
|
||
num_devices=num_gpus_value,
|
||
peak_flops_per_device=A100_FLOPS_FP16_TENSOR,
|
||
efficiency_eta=efficiency_eta_value,
|
||
)
|
||
optimized_time_value = dTime(
|
||
total_ops=GPT3_TRAINING_OPS,
|
||
num_devices=num_gpus_value,
|
||
peak_flops_per_device=A100_FLOPS_FP16_TENSOR,
|
||
efficiency_eta=target_eta_value,
|
||
)
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class GPT3Training:
|
||
"""
|
||
Namespace for the 'Training GPT-3' Napkin Math callout.
|
||
Isolates variables (gpus, eta) so they don't leak into other scenarios.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
m_gpt3 = Models.GPT3
|
||
h_a100 = Hardware.Cloud.A100
|
||
|
||
ops = m_gpt3.training_ops.m_as(flop)
|
||
num_gpus = 1024
|
||
peak_tflops = h_a100.peak_flops.m_as(TFLOPs / second)
|
||
eta_base = 0.45
|
||
eta_opt = 0.60
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
# We use a static method or lambda for internal logic to avoid 'self' clutter
|
||
@staticmethod
|
||
def calc_days(ops, n, peak_tflops, eta):
|
||
# peak_tflops is in TFLOPS (1e12)
|
||
flops_per_sec = n * (peak_tflops * TRILLION) * eta
|
||
seconds = ops / flops_per_sec
|
||
return seconds / SEC_PER_DAY
|
||
|
||
# Compute values (these execute during class definition)
|
||
days_base = calc_days(ops, num_gpus, peak_tflops, eta_base)
|
||
days_opt = calc_days(ops, num_gpus, peak_tflops, eta_opt)
|
||
days_saved = days_base - days_opt
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(days_base > 20, f"Text implies >20 days, got {days_base:.1f}")
|
||
check(days_saved > 5, f"Text claims significant savings, got {days_saved:.1f}")
|
||
check(days_opt < days_base, "Optimization failed to reduce time")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
# Text strings
|
||
num_gpus_str = fmt(num_gpus, precision=0, commas=False)
|
||
eta_base_pct_str = fmt(eta_base * 100, precision=0, commas=False)
|
||
eta_opt_pct_str = fmt(eta_opt * 100, precision=0, commas=False)
|
||
days_initial_str = fmt(days_base, precision=0, commas=False)
|
||
days_optimized_str = fmt(days_opt, precision=0, commas=False)
|
||
days_saved_str = fmt(days_saved, precision=0, commas=False)
|
||
|
||
# LaTeX math components
|
||
_ops_mag = f"{ops:.2e}"
|
||
ops_coeff_str = _ops_mag.split("e+")[0]
|
||
ops_exp_value = int(_ops_mag.split("e+")[1])
|
||
peak_tflops_str = fmt(peak_tflops, precision=0, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
# Expose only what the markdown needs. This is the "Public API" of the cell.
|
||
num_gpus_str = GPT3Training.num_gpus_str
|
||
efficiency_eta_pct_str = GPT3Training.eta_base_pct_str
|
||
target_eta_pct_str = GPT3Training.eta_opt_pct_str
|
||
days_initial_str = GPT3Training.days_initial_str
|
||
days_optimized_str = GPT3Training.days_optimized_str
|
||
days_saved_str = GPT3Training.days_saved_str
|
||
|
||
# Math vars needed for the equation rendering
|
||
ops_coeff_str = GPT3Training.ops_coeff_str
|
||
ops_exp_value = GPT3Training.ops_exp_value
|
||
num_gpus_value = GPT3Training.num_gpus
|
||
peak_tflops_str = GPT3Training.peak_tflops_str
|
||
efficiency_eta_value = GPT3Training.eta_base
|
||
|
||
# Equation assembly (using the exported values)
|
||
time_formula_math = md_math(r"\text{Time} \approx \frac{O}{N \cdot R_{peak} \cdot \eta}")
|
||
time_value_math = md_math(
|
||
rf"\approx \frac{{{ops_coeff_str} \times 10^{{{ops_exp_value}}}}}{{{num_gpus_value} \times ({peak_tflops_str} \times 10^{{12}}) \times {efficiency_eta_value}}}"
|
||
)
|
||
time_days_math = md_math(rf"\approx {days_initial_str}\ \text{{days}}")
|
||
ops_math = md_math(rf"\approx {ops_coeff_str} \times 10^{{{ops_exp_value}}}")
|
||
```
|
||
|
||
::: {.callout-notebook title="Training GPT-3"}
|
||
**Problem**: Estimate the training time for a GPT-3 class model on a cluster of A100 GPUs.
|
||
|
||
**1. The Variables**:
|
||
|
||
* **Ops (O)**: `{python} ops_math` FLOPs (from paper).
|
||
* **Peak (Rpeak)**: `{python} peak_tflops_str` TFLOPS (A100 FP16 tensor core peak).
|
||
* **Efficiency (η)**: ≈ `{python} efficiency_eta_pct_str`% (typical for large-scale distributed training).
|
||
* **Scale (N)**: `{python} num_gpus_str` GPUs.
|
||
|
||
**2. The Calculation**:
|
||
|
||
- `{python} time_formula_math`
|
||
- `{python} time_value_math`
|
||
- `{python} time_days_math`
|
||
|
||
**3. The Result**:
|
||
**`{python} days_initial_str` days**.
|
||
|
||
**The Systems Insight**: If we improve software efficiency (η) from `{python} efficiency_eta_pct_str`% to `{python} target_eta_pct_str`% through kernel fusion and better scheduling, training time drops to **`{python} days_optimized_str` days**, saving nearly `{python} days_saved_str` days of expensive compute time.
|
||
:::
|
||
|
||
The equation is dimensionally consistent: each term resolves to seconds. One cannot add FLOPs to Bytes any more than you can add meters to kilograms; the **Iron Law** adds Time to Time to Time. @sec-machine-foundations-dimensional-analysis-76b3 provides a formal dimensional analysis verifying this consistency and demonstrates how unit tracking prevents common modeling errors.
|
||
|
||
The **Iron Law** governs *time*, but time is not the only constraint. For mobile devices, edge systems, and large-scale training clusters, *energy* often matters more than raw speed.
|
||
|
||
Just as time is governed by physics, so is energy. We must add a fourth term to our mental model: **The Energy Tax.**\index{Energy Tax!data movement cost} In many modern systems (mobile, edge, and large-scale training), energy, not time, is the hard constraint. Let $D_{vol}$ be the total data volume moved (bytes), $E_{\text{move}}$ the energy per byte moved, $O$ the total operation count, and $E_{\text{compute}}$ the energy per operation. @eq-energy-cost formalizes this relationship:
|
||
|
||
$$ \text{Energy}_{\text{total}} \approx \underbrace{ D_{vol} \times E_{\text{move}} }_{\text{Dominant Term}} + \underbrace{ O \times E_{\text{compute}} }_{\text{Secondary Term}} $$ {#eq-energy-cost}
|
||
|
||
Crucially, $E_{\text{move}} \gg E_{\text{compute}}$. Moving a byte of data from memory often costs 100$\times$ more energy than performing a floating-point operation on it. The physical reason is that data movement requires charging and discharging wires over macroscopic distances, while arithmetic is performed locally within a processing unit's circuits. Therefore, **minimizing data movement ($D_{vol}$)** is the primary lever for both speed *and* energy efficiency.
|
||
|
||
The relationship between time, energy, and data movement forms the central analytical tool of this book.
|
||
|
||
::: {.callout-checkpoint title="The Iron Law" collapse="false"}
|
||
The **Iron Law** ($T \approx \frac{D_{vol}}{BW} + \frac{O}{R_{peak} \cdot \eta} + L_{lat}$) is the analytical backbone of this book. Before proceeding, verify you can manipulate its terms:
|
||
|
||
- [ ] **Data Term ($D_{vol}/BW$)**: Bound by memory bandwidth. Dominates in Transformers and Large Language Models where we move massive weights for every token.
|
||
- [ ] **Compute Term ($O/R_{peak}$)**: Bound by processor speed. Dominates in ConvNets (ResNet) where we reuse weights many times.
|
||
- [ ] **Latency Term ($L_{lat}$)**: Bound by physics and software overhead. Dominates in Inference and small-batch regimes.
|
||
|
||
*Self-Test: If you double the processor speed ($R_{peak}$), which term does it improve?*
|
||
:::
|
||
|
||
### The Economic Invariant: Return on Compute (RoC) {#sec-introduction-roc-invariant}
|
||
|
||
This decomposition of time is not merely a physical constraint; it is an economic one. In the Hennessy & Patterson tradition of quantitative reasoning, we define the **Return on Compute (RoC)**\index{Return on Compute (RoC)!economic invariant} as the marginal accuracy gain per dollar of infrastructure investment.
|
||
|
||
$$ \text{RoC} = \frac{\Delta \text{Accuracy}}{\Delta \text{Compute Cost}} $$ {#eq-roc-invariant}
|
||
|
||
This invariant forces the engineer to ask: if a 1% gain in accuracy requires a 10$\times$ increase in $O$ (Total Operations), is the **Silicon Contract** still economically viable? Every optimization in the following chapters targets either the numerator (extracting more signal from the same data) or the denominator (reducing the cost of executing the math). If the RoC is negative or negligible, the system is over-engineered, regardless of its technical sophistication. This economic lens transforms "accuracy" from a research target into an engineering budget.
|
||
|
||
If scale is the ultimate lever for performance, it is also the ultimate consumer of resources. The Bitter Lesson teaches that scale works, but the **Iron Law** teaches us *how* to afford it. This tension between scaling and sustainability shapes the engineering principles that follow.
|
||
|
||
The **Iron Law** provides more than a diagnostic framework. It organizes the entire discipline. Each term in the equation corresponds to a core engineering imperative. The Data Term demands that we *build* robust data pipelines and infrastructure (@sec-data-engineering). The Compute Term requires that we *optimize* algorithms and hardware utilization for efficiency (Part III). The Overhead Term necessitates that we *deploy* and *operate* systems reliably in production (@sec-model-serving, @sec-ml-operations). These three imperatives structure this textbook: Parts I and II address building, Part III addresses optimization, and Part IV addresses deployment and operations.
|
||
|
||
Abstract equations become concrete through concrete workloads. This textbook employs five recurring **Lighthouse Models** as diagnostic tools for the Iron Law. These canonical workloads serve as the Cast of Characters, our Systems Detectives, reappearing in every chapter to interrogate the Iron Law (the Silicon Contract between algorithm and machine).
|
||
|
||
Each archetype represents a distinct extreme of the Iron Law. For instance, **ResNet-50** allows us to investigate the **Compute Term** in its purest form, while **GPT-2/Llama** acts as our primary probe for **Memory Bandwidth** bottlenecks. By following these same workloads from data engineering through to edge deployment, each chapter demonstrates how a single architectural choice propagates physical and economic constraints across the entire system.
|
||
|
||
@tbl-lighthouse-examples summarizes why each archetype serves as a diagnostic tool for specific system bottlenecks.
|
||
|
||
| **Lighthouse Model** | **System Bottleneck** | **What It Reveals** | **Key Engineering Questions** |
|
||
|:---------------------|:----------------------|:----------------------------|:-----------------------------------------------|
|
||
| **ResNet-50** | Compute throughput | GPU utilization, batching | Is my hardware doing math or waiting for data? |
|
||
| **GPT-2 / Llama** | Memory bandwidth | KV caching, weight loading | How fast can I move model weights to compute? |
|
||
| **DLRM** | Memory capacity | Embedding tables, scale-out | How do I fit terabyte-scale models in memory? |
|
||
| **MobileNetV2** | Latency and power | Efficient operator design | Can I meet real-time constraints on battery? |
|
||
| **Keyword Spotting** | Power envelope | Extreme quantization | Can I run always-on inference on milliwatts? |
|
||
|
||
: **Lighthouse Models as Systems Detectives**: Each workload isolates a distinct bottleneck, enabling systematic investigation of how system constraints affect different architectural patterns. Quantitative specifications and architectural details appear in @sec-network-architectures. {#tbl-lighthouse-examples}
|
||
|
||
The Iron Law makes these differences precise. ResNet-50 applies the same small weight filters across millions of spatial positions, reusing each weight thousands of times; its $O/(R_{peak} \cdot \eta)$ term dominates because the processor must sustain enormous arithmetic throughput while the data footprint remains modest. GPT-2, by contrast, loads billions of unique weight parameters for every token it generates, and each weight is used only once before the next must be fetched; its $D_{vol}/BW$ term dominates because memory bandwidth, not arithmetic, is the binding constraint. The same equation, applied to two different workloads, yields opposite diagnoses and therefore opposite optimization strategies: doubling $R_{peak}$ accelerates ResNet-50 but barely affects GPT-2, while doubling $BW$ has the reverse effect.
|
||
|
||
Each archetype manifests different constraints along the D·A·M axes, ensuring that the principles developed throughout this text are tested against the diversity of real-world systems engineering challenges. Later in this chapter, we complement these technical workloads with three deployment case studies, Waymo, FarmBeats, and AlphaFold, that illustrate how the same core challenges manifest in production systems under radically different constraints.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: imagenet-footnote
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ IMAGENET SCALE STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: ImageNet footnote [^fn-imagenet-scale] (D·A·M taxonomy discussion)
|
||
# │
|
||
# │ Goal: Quantify the scale of the ImageNet dataset.
|
||
# │ Show: That even foundational benchmarks required significant systems engineering.
|
||
# │ How: Calculate millions of training images from the mlsys Digital Twins.
|
||
# │
|
||
# │ Imports: mlsys.constants (IMAGENET_IMAGES)
|
||
# │ Exports: imagenet_images_m_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.constants import IMAGENET_IMAGES, MILLION
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class ImageNetStats:
|
||
"""
|
||
Namespace for ImageNet Scale Statistics.
|
||
Scenario: Quantifying dataset scale (1.2M images) for the ImageNet footnote.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
images_raw = IMAGENET_IMAGES
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
images_million = images_raw.m_as('count') / MILLION
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(images_million >= 1.0, f"ImageNet scale ({images_million}M) is too small.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
images_m_str = fmt(images_million, precision=1)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
imagenet_images_m_str = ImageNetStats.images_m_str
|
||
```
|
||
|
||
To see these Lighthouse Models' diagnostic power in action, consider the breakthrough moment that launched the deep learning era. The D·A·M taxonomy's interdependencies become concrete in the 2012 AlexNet victory [@alexnet2012], which reduced ImageNet[^fn-imagenet-scale] [@deng2009imagenet] top-5 error from 26.2% to 15.3% not through algorithmic novelty alone but because convolutional neural networks' parallel matrix operations aligned perfectly with GPU hardware capabilities.
|
||
|
||
[^fn-imagenet-scale]: **ImageNet**: The 2009 dataset that proved data scale was the missing ingredient in computer vision. Fei-Fei Li marshaled 49,000 Mechanical Turk workers to label 14.2 million images across 21,841 categories, a data engineering operation that dwarfed the algorithmic novelty of everything it subsequently enabled, including AlexNet's 2012 breakthrough (see @sec-data-engineering). \index{ImageNet!scale}
|
||
|
||
This interdependence means that optimizing one component often shifts pressure to another. AlexNet's co-design success came at a cost affordable in 2012 (two consumer GPUs for a week), but modern models demand resources nine orders of magnitude larger. If the **Iron Law** governs *how fast* a system runs, we still need a framework for reasoning about *how efficiently* it uses those resources.
|
||
|
||
## Efficiency Framework {#sec-introduction-efficiency-framework-8dd4}
|
||
|
||
The Bitter Lesson establishes that scale drives AI progress, but it also creates a paradox: if advancing AI requires ever-larger datasets and compute budgets, participation narrows to only the most resource-rich organizations. Even those organizations face physical limits in data center power constraints, memory bandwidth bottlenecks, and the diminishing returns of adding more parameters.
|
||
|
||
Training GPT-4-class models reportedly consumed over two million A100 GPU-days, representing millions of dollars in compute costs and substantial environmental impact. Many research institutions and companies cannot afford to compete through brute-force scaling. The reality motivates a complementary approach: rather than focusing solely on applying more compute, the field must also address how efficiently existing compute is used.
|
||
|
||
The answer defines the efficiency framework. Three complementary dimensions map directly to our **D·A·M taxonomy** (@tbl-dam-taxonomy), and they matured in a revealing historical sequence. **Algorithmic Efficiency**, the earliest frontier, reduces computational requirements through better model design and training procedures. Techniques such as model compression\index{Model Compression} (pruning, quantization, knowledge distillation), efficient architectures like MobileNet, and neural architecture search all deliver more capability per FLOP. As algorithms demanded ever more computation, **Compute Efficiency** became the second critical dimension. It maximizes hardware utilization by aligning algorithmic logic with machine physics, encompassing the evolution from general-purpose CPUs to specialized accelerators (GPUs, TPUs) and the hardware-software co-design principles that translate theoretical TFLOPS into real-world speedups. Most recently, **Data Selection** emerged as the third dimension, extracting more learning signal from limited examples and thereby reducing the data numerator of the Iron Law. Techniques including transfer learning\index{Transfer Learning}, active learning\index{Active Learning}, and curriculum design ensure every sample provides maximum learning value. Together, these three dimensions provide the engineering tools to overcome the Data, Algorithm, and Machine walls that pure scaling alone cannot address.
|
||
|
||
These three dimensions did not emerge simultaneously; as @fig-evolution-efficiency reveals, each progressed through distinct eras at different rates. Algorithmic efficiency led the way, compute efficiency followed as demand grew, and data-centric methods matured most recently. While history progressed from algorithmic breakthroughs to hardware acceleration to data-centric methods, Part III of this book reverses that sequence: we begin with data selection, then model compression, then hardware acceleration. This pedagogical order reflects how practitioners actually build systems: quality data is prerequisite to effective model optimization, and understanding the model is prerequisite to mapping it efficiently onto hardware.
|
||
|
||
::: {#fig-evolution-efficiency fig-env="figure" fig-pos="htb" fig-cap="**Historical Efficiency Trends.** A three-track timeline from 1980 to 2023 shows parallel progress in Algorithmic Efficiency (blue), Compute Efficiency (yellow), and Data Selection (green). Each track progresses through distinct eras: algorithms advance from early methods through deep learning to modern efficiency techniques; compute evolves from general-purpose CPUs through accelerated hardware to sustainable computing; data practices shift from scarcity through big data to data-centric AI." fig-alt="Timeline with three horizontal tracks from 1980 to 2023. Blue track shows Algorithmic Efficiency progressing through Deep Learning Era to Modern Efficiency. Yellow shows Compute Efficiency from General-Purpose through Accelerated to Sustainable Computing. Green shows Data Selection from Scarcity through Big Data to Data-Centric AI."}
|
||
|
||
```{.tikz}
|
||
\begin{tikzpicture}[font=\small\usefont{T1}{phv}{m}{n},node distance=2mm]
|
||
\tikzset{
|
||
Box/.style={inner xsep=1pt,
|
||
draw=none,
|
||
fill=#1,
|
||
anchor=west,
|
||
text width=27mm,align=center,
|
||
minimum width=27mm, minimum height=10mm
|
||
},
|
||
Box/.default=red
|
||
}
|
||
\definecolor{col1}{RGB}{128, 179, 255}
|
||
\definecolor{col2}{RGB}{255, 255, 128}
|
||
\definecolor{col3}{RGB}{204, 255, 204}
|
||
\definecolor{col4}{RGB}{230, 179, 255}
|
||
\definecolor{col5}{RGB}{255, 153, 204}
|
||
\definecolor{col6}{RGB}{245, 82, 102}
|
||
\definecolor{col7}{RGB}{255, 102, 102}
|
||
|
||
\node[Box={col1}](B1){Algorithmic\\ Efficiency};
|
||
\node[Box={col1},right=of B1](B2){Deep\\ Learning Era};
|
||
\node[Box={col1},right=of B2](B3){Modern\\ Efficiency};
|
||
\node[Box={col2},right=of B3](B4){General-Purpose\\ Computing};
|
||
\node[Box={col2},right=of B4](B5){Accelerated\\ Computing};
|
||
\node[Box={col2},right=of B5](B6){Sustainable Computing};
|
||
\node[Box={col3},right=of B6](B7){Data\\ Scarcity};
|
||
\node[Box={col3},right=of B7](B8){Big\\ Data Era};
|
||
\node[Box={col3},right=of B8](B9){ Data-Centric AI};
|
||
%%%%
|
||
\node[Box={col1},above=of B2,minimum width=87mm,
|
||
text width=85mm](GB1){Algorithmic Efficiency};
|
||
\node[Box={col2},above=of B5,minimum width=87mm,
|
||
text width=85mm](GB5){Compute Efficiency};
|
||
\node[Box={col3},above=of B8,minimum width=87mm,
|
||
text width=85mm](GB8){Data Selection};
|
||
%%
|
||
\foreach \x in{1,2,...,9}
|
||
\draw[dashed,thick,-latex](B\x)--++(270:5.5);
|
||
|
||
\path[red]([yshift=-8mm]B1.south west)coordinate(P)-|coordinate(K)(B9.south east);
|
||
\draw[line width=2pt,-latex](P)--(K)--++(0:3mm);
|
||
|
||
\node[Box={col1!50},below=2 of B1](BB1){1980};
|
||
\node[Box={col1!50},below=2 of B2](BB2){2010};
|
||
\node[Box={col1!50},below=2 of B3](BB3){2023};
|
||
\node[Box={col2!70},below=2 of B4](BB4){1980};
|
||
\node[Box={col2!70},below=2 of B5](BB5){2010};
|
||
\node[Box={col2!70},below=2 of B6](BB6){2023};
|
||
\node[Box={col3!70},below=2 of B7](BB7){1980};
|
||
\node[Box={col3!50},below=2 of B8](BB8){2010};
|
||
\node[Box={col3!50},below=2 of B9](BB9){2023};
|
||
%%%%%
|
||
\node[Box={col4!50},below= of BB1](BBB1){2010};
|
||
\node[Box={col4!50},below= of BB2](BBB2){2022};
|
||
\node[Box={col4!50},below= of BB3](BBB3){Future};
|
||
%
|
||
\node[Box={col5!50},below= of BB4](BBB4){2010};
|
||
\node[Box={col5!50},below= of BB5](BBB5){2022};
|
||
\node[Box={col5!50},below= of BB6](BBB6){Future};
|
||
%
|
||
\node[Box={col7!50},below= of BB7](BBB7){2010};
|
||
\node[Box={col7!50},below= of BB8](BBB8){2022};
|
||
\node[Box={col7!50},below= of BB9](BBB9){Future};
|
||
\end{tikzpicture}
|
||
```
|
||
:::
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: efficiency-gains
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ EFFICIENCY GAINS STATISTICS
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: "Quantitative Evidence" section, @fig-algo-efficiency, Moore's Law
|
||
# │ footnote
|
||
# │
|
||
# │ Goal: Contrast AI compute growth with hardware scaling limits.
|
||
# │ Show: That AI demand doubles 7× faster than Moore's Law silicon scaling.
|
||
# │ How: Calculate the growth gap ratio between algorithmic demand and hardware supply.
|
||
# │
|
||
# │ Imports: (none - pure calculation)
|
||
# │ Exports: algo_efficiency_max_str, moores_speedup_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class EfficiencyGains:
|
||
"""
|
||
Namespace for Algorithmic Efficiency and Moore's Law comparison.
|
||
Scenario: AI compute demand doubling (3.4mo) vs Silicon doubling (24mo).
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
algo_efficiency_max = 44.5 # EfficientNet vs AlexNet (Hernandez & Brown 2020)
|
||
moores_doubling_months = 24 # Silicon scaling
|
||
ai_compute_doubling_months = 3.4 # Training compute scaling (Amodei 2018)
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
# How much faster is AI demand growing than Silicon supply?
|
||
growth_gap_ratio = moores_doubling_months / ai_compute_doubling_months
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(growth_gap_ratio >= 5, f"AI growth ({ai_compute_doubling_months}mo) is not significantly faster than Moore's Law ({moores_doubling_months}mo). Gap: {growth_gap_ratio:.1f}x")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
algo_efficiency_max_str = fmt(algo_efficiency_max, precision=1)
|
||
moores_speedup_str = fmt(growth_gap_ratio, precision=1)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
algo_efficiency_max_str = EfficiencyGains.algo_efficiency_max_str
|
||
moores_speedup_str = EfficiencyGains.moores_speedup_str
|
||
```
|
||
|
||
The magnitude of efficiency improvements is measurable. Between 2012 and 2019, computational resources needed to train a neural network to achieve AlexNet-level performance on ImageNet classification decreased by approximately `{python} algo_efficiency_max_str`$\times$ [@hernandez2020measuring]. This improvement, which halved every 16 months, outpaced hardware efficiency gains predicted by Moore's Law\index{Moore's Law!comparison to AI scaling}[^fn-moores-law-baseline], demonstrating that algorithmic innovation drives efficiency as much as hardware advances.
|
||
|
||
[^fn-moores-law-baseline]: **Moore’s Law**: Gordon Moore’s 1965 observation that transistor density doubles approximately every two years. Over the same 2012-2019 window where algorithmic efficiency improved 44$\times$, Moore’s Law delivered roughly 16$\times$ in transistor scaling, meaning algorithmic innovation closed more of the efficiency gap than hardware alone. Simultaneously, *demand* for training compute doubled every 3.4 months, outpacing both curves and forcing the shift to domain-specific accelerators. \index{Moore’s Law!vs. AI growth}
|
||
|
||
Simultaneously, the compute used in AI training increased by roughly five orders of magnitude from 2012 to 2018, doubling approximately every 3.4 months [@amodei2018aicompute]. This exponential growth far exceeds Moore's Law and explains *why* efficiency optimization is not optional. Without it, only the most resource-rich organizations could participate in AI development.
|
||
|
||
These measurements emerge from rigorous empirical methodology that tracked training compute across hundreds of published models; @sec-benchmarking develops the measurement frameworks that enable such systematic analysis of ML system performance. Closing the Systems Gap is the primary objective of this textbook, requiring integrated expertise across the software and hardware stack.
|
||
|
||
To appreciate the magnitude of these gains, consider the trajectory in @fig-algo-efficiency: starting from AlexNet as the baseline, each successive architecture (VGG, ResNet, MobileNet, EfficientNet) achieves comparable accuracy with dramatically fewer computational resources, culminating in a `{python} algo_efficiency_max_str`$\times$ improvement over just eight years.
|
||
|
||
::: {#fig-algo-efficiency fig-env="figure" fig-pos="htb" fig-cap="**Algorithmic Efficiency Trajectory.** Training efficiency factor relative to AlexNet (2012 baseline) for ImageNet classification. Each point represents a model architecture that achieves comparable accuracy with fewer computational resources. The trajectory from AlexNet (1×) through VGG, ResNet, MobileNet, and ShuffleNet to EfficientNet (44×) demonstrates that algorithmic innovation has delivered a 44-fold reduction in required compute over eight years, independent of hardware improvements." fig-alt="Scatter plot showing training efficiency factor from 2012 to 2020. Red dots mark models from AlexNet at 1× to EfficientNet at 44×. Dashed trend line curves upward. Labels identify VGG, ResNet, MobileNet, ShuffleNet versions at their positions."}
|
||
```{python}
|
||
#| echo: false
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ ALGORITHMIC EFFICIENCY TRAJECTORY (FIGURE)
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: @fig-algo-efficiency — Algorithmic Efficiency Trajectory
|
||
# │
|
||
# │ Goal: Visualize the trajectory of algorithmic efficiency.
|
||
# │ Show: The 44× compute reduction from AlexNet to EfficientNet.
|
||
# │ How: Plot historical ImageNet training compute data points.
|
||
# │
|
||
# │ Imports: sys, os, matplotlib.pyplot, numpy, mlsys.viz
|
||
# │ Exports: (figure output)
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
import sys
|
||
import os
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
# Ensure mlsys/viz is available in Quarto execution.
|
||
sys.path.insert(0, ".")
|
||
from mlsys import viz
|
||
|
||
fig, ax, COLORS, plt = viz.setup_plot()
|
||
|
||
# --- Data (model efficiency factors) ---
|
||
algo_efficiency_data = [
|
||
{"Model": "AlexNet", "Year": 2012.5, "Efficiency_Factor": 1.0},
|
||
{"Model": "VGG-11", "Year": 2014.7, "Efficiency_Factor": 0.8},
|
||
{"Model": "GoogLeNet", "Year": 2014.75, "Efficiency_Factor": 4.5},
|
||
{"Model": "ResNet-18", "Year": 2015.9, "Efficiency_Factor": 2.9},
|
||
{"Model": "DenseNet-121", "Year": 2016.7, "Efficiency_Factor": 3.3},
|
||
{"Model": "MobileNet-v1", "Year": 2017.3, "Efficiency_Factor": 11.2},
|
||
{"Model": "ShuffleNet-v1", "Year": 2017.5, "Efficiency_Factor": 20.8},
|
||
{"Model": "MobileNet-v2", "Year": 2018.1, "Efficiency_Factor": 13.3},
|
||
{"Model": "ShuffleNet-v2", "Year": 2018.5, "Efficiency_Factor": 24.9},
|
||
{"Model": "EfficientNet-B0", "Year": 2019.4, "Efficiency_Factor": 44.5},
|
||
]
|
||
|
||
# --- Plot ---
|
||
ax.scatter(
|
||
[d["Year"] for d in algo_efficiency_data],
|
||
[d["Efficiency_Factor"] for d in algo_efficiency_data],
|
||
color=COLORS["RedLine"],
|
||
s=80,
|
||
alpha=0.9,
|
||
edgecolors="white",
|
||
zorder=3,
|
||
)
|
||
|
||
offsets = {
|
||
"AlexNet": (0, -15),
|
||
"VGG-11": (0, -15),
|
||
"GoogLeNet": (0, 10),
|
||
"ResNet-18": (0, -15),
|
||
"DenseNet-121": (0, -15),
|
||
"MobileNet-v1": (-25, 5),
|
||
"ShuffleNet-v1": (-20, 15),
|
||
"MobileNet-v2": (25, -15),
|
||
"ShuffleNet-v2": (10, 15),
|
||
"EfficientNet-B0": (0, 10),
|
||
}
|
||
|
||
for row in algo_efficiency_data:
|
||
name = row["Model"]
|
||
offset = offsets.get(name, (0, 5))
|
||
ax.annotate(
|
||
name,
|
||
(row["Year"], row["Efficiency_Factor"]),
|
||
xytext=offset,
|
||
textcoords="offset points",
|
||
fontsize=8,
|
||
ha="center",
|
||
color=COLORS["primary"],
|
||
bbox=dict(facecolor="white", alpha=0.6, edgecolor="none", pad=0.5),
|
||
)
|
||
|
||
years = np.linspace(2012, 2020, 100)
|
||
trend = 1.0 * 1.72 ** (years - 2012.5)
|
||
ax.plot(years, trend, "--", color=COLORS["BlueLine"], label="44x Improvement", linewidth=2, zorder=1)
|
||
|
||
ax.set_ylim(0, 50)
|
||
ax.set_xlim(2012, 2020)
|
||
ax.set_xlabel("Year")
|
||
ax.set_ylabel("Efficiency Factor (Relative to AlexNet)")
|
||
ax.legend(loc="upper left", fontsize=9)
|
||
plt.show()
|
||
```
|
||
:::
|
||
|
||
Efficiency gains tell only half the story. @fig-ai-training-compute-growth reveals the countervailing trend: even as individual architectures become more efficient, the field's total appetite for compute has grown exponentially, making efficiency optimization not a luxury but a necessity for continued progress.
|
||
|
||
::: {#fig-ai-training-compute-growth fig-env="figure" fig-pos="htb" fig-cap="**The Era of Scale.** Training Compute (FLOPs) vs. Year (Log Scale). While early Deep Learning (blue) showed rapid growth, the Transformer Era (red) accelerated this trend significantly. From AlexNet (2012) to GPT-4 (2023), compute requirements increased by $10^7$ (10 million times), far outpacing Moore's Law. This exponential demand drives the specialized infrastructure described in this book." fig-alt="Scatter plot of Training Compute FLOPs vs Year. Blue dots (2012-2018) show deep learning models like ResNet. Red dots (2018-2024) show large scale models like GPT-4, rising much faster on the log scale."}
|
||
```{python}
|
||
#| echo: false
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ TRAINING COMPUTE GROWTH (FIGURE)
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: @fig-ai-training-compute-growth — The Era of Scale
|
||
# │
|
||
# │ Goal: Visualize the explosive growth of training compute.
|
||
# │ Show: The $10^7$ growth in FLOPs that outpaces Moore's Law.
|
||
# │ How: Plot training compute data points from the Deep Learning era.
|
||
# │
|
||
# │ Imports: sys, os, matplotlib.pyplot, numpy, mlsys.viz
|
||
# │ Exports: (figure output)
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
import sys
|
||
import os
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
# Ensure mlsys/viz is available in Quarto execution.
|
||
sys.path.insert(0, ".")
|
||
from mlsys import viz
|
||
|
||
fig, ax, COLORS, plt = viz.setup_plot()
|
||
|
||
# --- Data (training compute by model and era) ---
|
||
training_compute_data = [
|
||
{"Model": "AlexNet", "Year": 2012, "FLOPs": 1.2e18, "Era": "Deep Learning"},
|
||
{"Model": "VGG-16", "Year": 2014, "FLOPs": 3.0e19, "Era": "Deep Learning"},
|
||
{"Model": "ResNet-50", "Year": 2015, "FLOPs": 5.0e19, "Era": "Deep Learning"},
|
||
{"Model": "GoogleNet", "Year": 2014, "FLOPs": 2.0e19, "Era": "Deep Learning"},
|
||
{"Model": "AlphaGoZero", "Year": 2017, "FLOPs": 3.0e22, "Era": "Deep Learning"},
|
||
{"Model": "Transformer", "Year": 2017, "FLOPs": 5.0e20, "Era": "Large Scale"},
|
||
{"Model": "BERT-Large", "Year": 2018, "FLOPs": 3.0e21, "Era": "Large Scale"},
|
||
{"Model": "GPT-2-XL", "Year": 2019, "FLOPs": 2.0e22, "Era": "Large Scale"},
|
||
{"Model": "T5-11B", "Year": 2019, "FLOPs": 5.0e22, "Era": "Large Scale"},
|
||
{"Model": "GPT-3", "Year": 2020, "FLOPs": 3.1e23, "Era": "Large Scale"},
|
||
{"Model": "Gopher", "Year": 2021, "FLOPs": 6.0e23, "Era": "Large Scale"},
|
||
{"Model": "PaLM", "Year": 2022, "FLOPs": 2.5e24, "Era": "Large Scale"},
|
||
{"Model": "GPT-4", "Year": 2023, "FLOPs": 2.0e25, "Era": "Large Scale"},
|
||
{"Model": "Llama-3-70B", "Year": 2024, "FLOPs": 8.0e24, "Era": "Large Scale"},
|
||
{"Model": "Gemini-Ultra", "Year": 2024, "FLOPs": 5.0e25, "Era": "Large Scale"},
|
||
]
|
||
|
||
# --- Plot ---
|
||
for era in ["Deep Learning", "Large Scale"]:
|
||
group = [d for d in training_compute_data if d["Era"] == era]
|
||
color = COLORS["BlueLine"] if era == "Deep Learning" else COLORS["RedLine"]
|
||
ax.scatter(
|
||
[d["Year"] for d in group],
|
||
[d["FLOPs"] for d in group],
|
||
color=color,
|
||
s=80,
|
||
alpha=0.8,
|
||
edgecolors="white",
|
||
label=era,
|
||
)
|
||
|
||
for row in training_compute_data:
|
||
if row["Model"] in ["AlexNet", "AlphaGoZero", "GPT-3", "PaLM", "GPT-4"]:
|
||
xytext = (0, 5)
|
||
if row["Model"] == "PaLM":
|
||
xytext = (-5, 5)
|
||
if row["Model"] == "GPT-4":
|
||
xytext = (5, 5)
|
||
ax.annotate(
|
||
row["Model"],
|
||
(row["Year"], row["FLOPs"]),
|
||
xytext=xytext,
|
||
textcoords="offset points",
|
||
fontsize=8,
|
||
bbox=dict(facecolor="white", alpha=0.8, edgecolor="none", pad=0.5),
|
||
)
|
||
|
||
ax.set_yscale("log")
|
||
ax.set_xlabel("Year")
|
||
ax.set_ylabel("Training Compute (FLOPs)")
|
||
|
||
years = np.linspace(2012, 2024, 100)
|
||
trend = 1e18 * 10 ** (7 / 12 * (years - 2012))
|
||
ax.plot(years, trend, "--", color=COLORS["grid"], label="Trend (~6mo Doubling)")
|
||
|
||
ax.legend(loc="lower right", fontsize=8)
|
||
plt.show()
|
||
```
|
||
:::
|
||
|
||
Taken together, these two figures reveal a seeming contradiction that defines the economics of modern AI development: @fig-algo-efficiency shows efficiency improving 44$\times$ while @fig-ai-training-compute-growth shows compute demand growing by seven orders of magnitude. The resolution lies in understanding how efficiency and scale co-evolve.
|
||
|
||
::: {.callout-perspective title="The Efficiency Paradox"}
|
||
\index{Efficiency Paradox}This apparent contradiction defines the economics of ML systems engineering. Efficiency gains enabled larger experiments, which demanded more compute, which motivated further efficiency research. Consider: if EfficientNet needs 44$\times$ less compute than AlexNet to reach the same accuracy, organizations invest the savings not into cost reduction but into training *larger* models on *more* data, which is precisely how GPT-3 came to require orders of magnitude more compute than AlexNet despite enormous per-FLOP efficiency gains. This feedback loop, where efficiency enables scale and scale demands efficiency, defines the modern AI engineering landscape. Understanding this dynamic is essential for making informed decisions about where to invest optimization effort.
|
||
:::
|
||
|
||
The specific techniques for achieving these gains (pruning algorithms, quantization strategies, knowledge distillation, neural architecture search, hardware-aware optimization, and efficient training procedures) are developed systematically in @sec-model-compression (algorithmic techniques) and @sec-hardware-acceleration (hardware foundations). @sec-data-engineering addresses data selection through pipeline design and quality optimization.
|
||
|
||
Which efficiency dimensions to prioritize depends heavily on deployment context: cloud systems optimize for throughput while edge devices optimize for power. Notice what the last several sections have demanded of the engineer: the Iron Law requires reasoning about data movement and computation simultaneously, the Degradation Equation requires monitoring statistical drift in production, and the Efficiency Framework requires balancing algorithmic, compute, and data improvements against each other. No single existing discipline teaches all of these skills. Computer science addresses algorithms; electrical engineering addresses hardware. Neither addresses the integrated challenge of building ML systems that are simultaneously efficient, reliable, and scalable. This gap motivates a formal definition of the discipline that spans them.
|
||
|
||
## Defining AI Engineering {#sec-introduction-defining-ai-engineering-19ce}
|
||
|
||
The Iron Law decomposes performance into physical terms, the Degradation Equation quantifies silent decay, and the Efficiency Framework maps the three levers for managing scale. Together, they demand a discipline that integrates all three:
|
||
|
||
::: {.callout-definition title="AI Engineering"}
|
||
|
||
***AI Engineering***\index{AI Engineering!definition} is the discipline of building **Stochastic Systems** with **Deterministic Reliability**.
|
||
|
||
1. **Significance (Quantitative):** It achieves **Systems-Level Integration** of data, models, and infrastructure by treating them as interdependent artifacts. It bridges **Accuracy Optimization** (research) with **Latency, Cost, and Safety Optimization** (production).
|
||
2. **Distinction (Durable):** Unlike **Machine Learning Research**, which focuses on **Algorithmic Innovation**, AI Engineering focuses on the **Reliable Deployment** of those algorithms under physical constraints.
|
||
3. **Common Pitfall:** A frequent misconception is that AI Engineering is just "Software Engineering for ML." In reality, it inherits **Statistical Rigor** from Electrical Engineering to validate uncertain functions against uncertain inputs.
|
||
|
||
:::
|
||
|
||
The phrase "stochastic systems with deterministic reliability" captures a deep lineage. The emergence of AI Engineering as a distinct discipline mirrors how Computer Engineering emerged in the late 1960s and early 1970s.[^fn-computer-engineering-lineage] As computing systems grew more complex, neither Electrical Engineering nor Computer Science alone could address the integrated challenges of building reliable computers. Computer Engineering emerged as a discipline bridging both fields. Today, AI Engineering faces similar challenges at the intersection of algorithms, infrastructure, and operational practices.
|
||
|
||
[^fn-computer-engineering-lineage]: **Computer Engineering**: Formalized as an academic discipline when Case Western Reserve launched the first accredited program in 1971, recognizing that neither Electrical Engineering nor Computer Science alone could address building reliable computers from unreliable components. ML systems engineering recapitulates this convergence: the binding constraint is not algorithmic or hardware in isolation but the integration of both under latency, power, and data-quality budgets that neither discipline's curriculum addresses. \index{Computer Engineering!historical lineage}
|
||
|
||
AI Engineering encompasses the complete lifecycle of production intelligent systems. A breakthrough algorithm requires efficient data collection and processing, distributed computation across hundreds or thousands of machines, reliable service to users with strict latency requirements, and continuous monitoring and updating based on real-world performance. Throughout this text, we use "ML systems engineering" to describe the practice: the work of designing, deploying, and maintaining the machine learning systems that constitute modern AI.
|
||
|
||
Defining a discipline is one thing; practicing it is another. The definition tells us *what* AI Engineering is, but engineers need to know *how* it unfolds in practice. Traditional software follows a well-understood lifecycle: design, implement, test, deploy, maintain. ML systems follow a different pattern, one shaped by the data-dependent behavior and silent degradation modes we have identified. Understanding this lifecycle, and how deployment context reshapes it, is the bridge between abstract principles and daily engineering work.
|
||
|
||
## ML System Lifecycle {#sec-introduction-ml-system-lifecycle-849f}
|
||
|
||
A traditional software project follows a well-understood arc: design, implement, test, deploy, maintain. An ML project follows a different arc shaped by data-dependent behavior and silent degradation. The development process itself differs from traditional software engineering, the deployment context reshapes that process, and multiple engineering disciplines must coordinate across it. We begin with the development lifecycle and its distinctive feedback loops, then examine how deployment targets from cloud to microcontroller alter what the lifecycle demands, and finally map the engineering disciplines that sustain it in production.
|
||
|
||
### The ML Development Lifecycle {#sec-introduction-ml-development-lifecycle-4ea0}
|
||
|
||
ML systems differ from traditional software in their development and operational lifecycle\index{ML Development Lifecycle!vs. traditional software}. Traditional software follows predictable patterns where developers write explicit instructions that execute deterministically. These systems build on decades of established practices: version control maintains precise code histories, continuous integration pipelines automate testing, and static analysis tools measure quality. This mature infrastructure enables reliable software development following well-defined engineering principles.
|
||
|
||
Machine learning systems depart from this paradigm. While traditional systems execute explicit programming logic, ML systems derive their behavior from data patterns discovered through training. The shift from code to data as the primary behavior driver introduces complexities that existing software engineering practices cannot address. We address these challenges and specialized workflows in @sec-ml-workflow.
|
||
|
||
Unlike traditional software's linear progression from design through deployment, ML systems operate in continuous cycles\index{ML Development Lifecycle!continuous iteration}. Follow the feedback loops in @fig-ml_lifecycle_overview to see why: when monitoring detects performance degradation, the system does not simply receive a patch. It cycles back through data collection, preparation, training, and evaluation before redeployment, creating a never-ending loop that has no counterpart in traditional software engineering.
|
||
|
||
::: {#fig-ml_lifecycle_overview fig-env="figure" fig-pos="htb" fig-cap="**ML System Lifecycle.** A six-box flowchart depicting Data Collection, Preparation, Model Training, Evaluation, Deployment, and Monitoring. Two feedback loops distinguish this cycle from linear software development: evaluation returns to preparation when results are insufficient, and monitoring triggers new data collection when performance degrades." fig-alt="Flowchart showing cyclical ML lifecycle. Six boxes: Data Collection, Preparation, Model Training, Evaluation, Deployment, Monitoring. Two loops: evaluation returns to preparation; monitoring triggers collection."}
|
||
```{.tikz}
|
||
\begin{tikzpicture}[font=\small\usefont{T1}{phv}{m}{n}]
|
||
\tikzset{
|
||
Box/.style={inner xsep=2pt,
|
||
draw=GreenLine,
|
||
line width=0.75pt,
|
||
fill=GreenL,
|
||
anchor=west,
|
||
text width=20mm,align=flush center,
|
||
minimum width=20mm, minimum height=8mm
|
||
},
|
||
Line/.style={line width=1.0pt,black!50,text=black,-{Triangle[width=0.8*6pt,length=0.98*6pt]}},
|
||
Text/.style={inner sep=4pt,
|
||
draw=none, line width=0.75pt,
|
||
fill=TextColor!70,
|
||
font=\fontsize{8pt}{9}\selectfont\usefont{T1}{phv}{m}{n},
|
||
align=flush center,
|
||
minimum width=7mm, minimum height=5mm
|
||
},
|
||
}
|
||
|
||
\node[Box](B1){ Data\ Preparation};
|
||
\node[Box,node distance=15mm,right=of B1,fill=RedL,draw=RedLine](B2){Model\ Evaluation};
|
||
\node[Box,node distance=32mm,right=of B2,fill=VioletL,draw=VioletLine](B3){Model \ Deployment};
|
||
\node[Box,node distance=9mm,above=of $(B1)!0.5!(B2)$,
|
||
fill=BackColor!60!yellow!90,draw=BackLine](GB){Model\ Training};
|
||
\node[Box,node distance=9mm,below left=1.1 and 0 of B1.south west,
|
||
fill=BlueL,draw=BlueLine](DB1){Data\ Collection};
|
||
\node[Box,node distance=9mm,below right=1.1 and 0 of B3.south east,
|
||
fill=OrangeL,draw=OrangeLine](DB2){Model\ Monitoring};
|
||
\draw[Line](B2)--node[Text,pos=0.5]{Meets\ Requirements}(B3);
|
||
\draw[Line](B2)--++(270:1.2)-|node[Text,pos=0.25]{Needs\ Improvement}(B1);
|
||
\draw[Line](DB2)--node[Text,pos=0.25]{Performance\ Degrades}(DB1);
|
||
\draw[Line](DB1)|-(B1);
|
||
\draw[Line](B1)|-(GB);
|
||
\draw[Line](GB)-|(B2);
|
||
\draw[Line](B3)-|(DB2);
|
||
\end{tikzpicture}
|
||
```
|
||
:::
|
||
|
||
The data-dependent nature of ML systems creates dynamic lifecycles requiring continuous monitoring and adaptation. Unlike source code that changes only through developer modifications, data reflects real-world dynamics: distribution shifts can silently alter system behavior without any code changes. Traditional tools designed for deterministic code-based systems prove insufficient here. Version control excels at tracking discrete code changes but struggles with large, evolving datasets. Testing frameworks designed for deterministic outputs require adaptation for probabilistic predictions. We address data versioning and quality management in @sec-data-engineering and monitoring approaches that handle probabilistic behaviors in @sec-ml-operations.
|
||
|
||
In production, lifecycle stages create either virtuous or vicious cycles. Virtuous cycles emerge when high-quality data enables effective learning, robust infrastructure supports efficient processing, and well-engineered systems facilitate better data collection. Vicious cycles occur when poor data quality undermines learning, inadequate infrastructure hampers processing, and system limitations prevent data collection improvements, with each problem compounding the others.
|
||
|
||
::: {.callout-perspective #perspective-application-scenarios title="The Engineering Missions: Application Scenarios"}
|
||
|
||
The top of the hierarchy transforms abstract systems into concrete engineering missions. Throughout the book and its associated labs, we focus on these four **Application Scenarios**:
|
||
|
||
| **Mission** | **System Archetype** | **Lighthouse Model** | **Critical Constraint** |
|
||
|:---|:---|:---|:---|
|
||
| **Frontier Training** | Cloud Cluster | GPT-4 | `{python} scenario_frontier_limit` |
|
||
| **Autonomous Perception** | Edge Robotics | YOLOv8-nano | `{python} scenario_drive_limit` |
|
||
| **Mobile Assistant** | Smartphone | Llama-2-70B | Thermal Throttling / RAM |
|
||
| **Smart Doorbell** | TinyML (MCU) | Wake Vision | `{python} scenario_doorbell_limit` |
|
||
|
||
These missions act as the "End-to-End" verification for our engineering decisions. A 2$\times$ increase in memory bandwidth is an academic result until it is proven to extend the battery life of the **Smart Doorbell** or enable the safety-critical latency required for **Autonomous Perception**.
|
||
|
||
:::
|
||
|
||
### The Deployment Spectrum {#sec-introduction-deployment-spectrum-a38c}
|
||
|
||
The lifecycle stages apply universally to ML systems, but their specific implementation varies based on deployment environment. The deployment spectrum spans from megawatt-scale data centers to milliwatt-scale embedded devices, and each position on that spectrum reshapes how every lifecycle stage is realized in practice.
|
||
|
||
At one end of the spectrum, cloud-based ML systems\index{Cloud ML} run in massive data centers. These systems, including large language models and recommendation engines, process petabytes of data while serving millions of users simultaneously. They draw on virtually unlimited computing resources but manage enormous operational complexity and costs. @sec-ml-systems examines the architectural patterns for building such large-scale systems, while @sec-hardware-acceleration explores the hardware foundations that make this scale economically viable.
|
||
|
||
At the other end, TinyML systems\index{TinyML} run on microcontrollers[^fn-microcontrollers-tinyml] and embedded devices, performing ML tasks with severe memory, computing power, and energy consumption constraints. Smart home devices like Alexa or Google Assistant must recognize voice commands using less power than LED bulbs, while sensors must detect anomalies on battery power for months or years. The efficiency framework developed earlier in this chapter (@sec-introduction-efficiency-framework-8dd4) introduces the principles underlying constrained deployment, while @sec-model-compression provides the specific techniques (quantization, pruning, distillation) that make TinyML feasible.
|
||
|
||
[^fn-microcontrollers-tinyml]: **Microcontrollers**: Single-chip computers whose severe hardware limits—memory in kilobytes, power in milliwatts—are the direct origin of the constraints mentioned. These resource budgets dictate the feasibility of the use cases described; a keyword-spotting model for a smart device, for example, must operate on a power budget often below 1 milliwatt to enable months of battery-powered operation. \index{Microcontrollers!TinyML substrate}
|
||
|
||
\index{Latency!etymology}
|
||
The space between these poles contains a rich variety of ML systems adapted for specific contexts. Edge ML systems\index{Edge ML!latency and bandwidth} bring computation closer to data sources, reducing latency[^fn-latency-responsiveness] and bandwidth requirements while managing local computing resources. Mobile ML systems\index{Mobile ML!resource constraints} must balance sophisticated capabilities with severe constraints. Modern smartphones typically have 4–12 GB RAM, ARM processors operating at 1.5–3 GHz, and power budgets of 2–5 W that must be shared across all system functions. For example, running a state-of-the-art image classification model on a smartphone might consume 100–500 mW and complete inference in 10–100 ms, compared to cloud servers that can use 200+ W but deliver results in under 1 ms. Enterprise ML systems often operate within specific business constraints, focusing on particular tasks while integrating with existing infrastructure. Some organizations employ hybrid approaches, distributing ML capabilities across multiple tiers to balance various requirements.
|
||
|
||
[^fn-latency-responsiveness]: **Latency**: From Latin *latere* ("to lie hidden"), a fitting etymology because delay is invisible until it causes failure. Autonomous braking requires <10 ms end-to-end; at highway speeds, every additional millisecond adds roughly 3 cm of stopping distance. This makes $L_{lat}$ rather than throughput the binding constraint for edge deployment, and explains why latency-critical systems cannot offload inference to distant cloud servers regardless of their superior compute. \index{Latency!edge deployment}
|
||
|
||
Each position on this deployment spectrum creates distinct bottlenecks that determine which efficiency dimensions matter most, as summarized in @tbl-efficiency-priorities:
|
||
|
||
| **Environment** | **Primary Constraint** | **Efficiency Focus** |
|
||
|:--------------------|:--------------------------|:-----------------------------------------------|
|
||
| **Cloud training** | Cost, throughput | Distributed efficiency, hardware utilization |
|
||
| **Cloud inference** | Latency, cost per query | Batching, model serving optimization |
|
||
| **Edge devices** | Memory, power | Model compression, quantization |
|
||
| **Mobile** | Battery, thermal | Energy-efficient inference |
|
||
| **TinyML** | KB-scale memory, mW power | Extreme compression, specialized architectures |
|
||
|
||
: **Efficiency Priorities by Deployment Context**: Each deployment environment creates distinct bottlenecks, requiring tailored optimization strategies. Cloud systems optimize for throughput and cost; edge systems optimize for memory and power; TinyML systems require extreme efficiency across all dimensions. {#tbl-efficiency-priorities}
|
||
|
||
### How Deployment Shapes the Lifecycle {#sec-introduction-deployment-shapes-lifecycle-7667}
|
||
|
||
The deployment spectrum represents more than different hardware configurations. Each deployment environment reshapes every stage of the ML lifecycle, from initial data collection through continuous operation and evolution, creating an interplay of constraints that traditional software rarely encounters.
|
||
|
||
Consider how a single deployment decision cascades through the entire system. Latency-sensitive applications like autonomous vehicles or real-time fraud detection require edge or embedded architectures despite their resource constraints, while large language models naturally gravitate toward centralized cloud infrastructure. But this initial architectural choice determines far more than where computation happens. Cloud systems must optimize for cost efficiency at scale, balancing expensive GPU clusters, storage, and network bandwidth, which in turn shapes how often models are retrained, what historical data is retained, and how inference load is distributed. Edge and mobile systems face fixed resource limits that constrain model complexity and update frequency, forcing aggressive model compression[^fn-compression-tradeoff] and careful scheduling. The strictest constraints arise in embedded and TinyML environments, where every byte of memory and milliwatt of power matters.
|
||
|
||
[^fn-compression-tradeoff]: **Model Compression**: A required consequence of the architectural choice to deploy on the edge, directly trading a model's predictive accuracy to satisfy a device's fixed resource budget. This allows a model originally designed for a data center to run within the kilobyte-scale memory and milliwatt power envelope of an embedded system, often reducing its size by over 90%. \index{Model Compression!definition}
|
||
|
||
Operational complexity increases as systems become more distributed. Centralized cloud architectures benefit from mature deployment tools and managed services, while edge and hybrid systems must coordinate data collection across sensors with varying connectivity, track models deployed across thousands of devices, handle staged rollouts with rollback capabilities, and aggregate monitoring signals from geographically distributed endpoints (@sec-ml-operations). Data considerations introduce competing pressures: privacy requirements or data sovereignty regulations may push computation toward the edge through federated learning[^fn-federated-learning-locality] approaches, while the need for large-scale training data pulls toward centralized cloud aggregation. Even model updates behave differently across the spectrum: cloud architectures enable easy A/B testing[^fn-ab-testing-validation] and rapid iteration, while edge deployments require over-the-air updates[^fn-ota-updates-drift] with careful bandwidth management and rollback capabilities.
|
||
|
||
[^fn-federated-learning-locality]: **Federated Learning**: A distributed training paradigm where models learn from data residing on decentralized devices without centralizing the raw information. This approach transforms the constraint of data locality into a privacy feature, but introduces new systems challenges: gradient aggregation across unreliable networks, non-IID data distributions across devices, and communication $BW$ that often dominates total training time. \index{Federated Learning!definition}
|
||
|
||
[^fn-ab-testing-validation]: **A/B Testing**: A statistical validation method that serves different model versions to distinct user segments, isolating the causal impact of a model change on business metrics. The "ease" the triggering sentence describes is cloud-specific: centralized traffic routing enables instant version switching and rollback. A typical experiment requires serving over 100,000 users to reliably detect a 1% change in a target metric, making A/B testing impractical on edge deployments with limited user pools. \index{A/B Testing!validation role}
|
||
|
||
[^fn-ota-updates-drift]: **OTA (Over-the-Air) Updates**: The delivery mechanism for models on edge devices where, unlike cloud servers, updates are constrained by system hardware. The "careful bandwidth management" mentioned is a direct consequence of low-power, intermittent networks that cannot handle multi-gigabyte model packages. The "rollback capabilities" are also not free; they require enough on-device storage to hold both the current and the new model simultaneously, doubling the storage footprint. \index{OTA Updates!drift mitigation}
|
||
|
||
In practice, these trade-offs are rarely simple binary choices. Modern ML systems often adopt hybrid approaches that span the deployment spectrum. An autonomous vehicle performs real-time perception and control at the edge for latency reasons, uploads driving data to the cloud for model improvement, and periodically downloads updated models. A voice assistant runs wake-word detection on-device to preserve privacy and reduce latency but sends full speech to the cloud for complex natural language processing. The key insight is that a choice to deploy on embedded devices does not just constrain model size; it affects data collection strategies, training approaches, evaluation metrics, deployment mechanisms, and monitoring capabilities. These interconnected decisions demonstrate the D·A·M taxonomy in practice, where constraints along one axis create cascading effects throughout the system.
|
||
|
||
To make these abstract trade-offs concrete, we examine three production systems that represent the extremes of the deployment spectrum. Each system faces the same core challenges (data quality, model complexity, and machine scale), but the constraints of their deployment environments force radically different engineering solutions.
|
||
|
||
## Deployment Case Studies {#sec-introduction-deployment-case-studies-636f}
|
||
|
||
Three production systems illustrate how the same engineering principles produce radically different designs under different deployment constraints:
|
||
|
||
- **Waymo**\index{High-Stakes Hybrid Deployment!autonomous vehicles}[^fn-waymo-hybrid] is Alphabet's autonomous vehicle division, operating a fleet of self-driving taxis that must make safety-critical decisions in real-time. Waymo represents the *high-stakes hybrid* deployment pattern: on-vehicle perception models run at the edge with <10 ms latency requirements, while massive cloud infrastructure supports training on petabytes of driving data.
|
||
|
||
[^fn-waymo-hybrid]: **Waymo**: The hybrid deployment forces a synchronization challenge absent from pure cloud or pure edge systems: the on-vehicle model must be frozen during deployment for safety certification, while the cloud continuously trains improved versions on newly collected driving data. This creates a version gap where the deployed model may lag weeks behind the latest cloud-trained version, requiring rigorous regression testing before any OTA update can be pushed to a safety-critical fleet. \index{Waymo!hybrid deployment}
|
||
|
||
- **FarmBeats**\index{Resource-Constrained Edge Deployment!precision agriculture}[^fn-farmbeats-edge] is Microsoft Research's precision agriculture platform, deploying ML models to farms with limited connectivity. FarmBeats represents the *resource-constrained edge* deployment pattern: models under 500 KB run inference on low-power devices using TV white-space bandwidth measured in kilobits per second.
|
||
|
||
[^fn-farmbeats-edge]: **FarmBeats**: The system demonstrates that the binding constraint for edge ML is often network $BW$ rather than compute or model quality. With TV white-space links measured in kilobits per second, even a 500 KB model update takes minutes to deliver, making model freshness rather than model accuracy the dominant failure mode in connectivity-constrained deployments. \index{FarmBeats!connectivity constraint}
|
||
|
||
- **AlphaFold**\index{Compute-Intensive Cloud Deployment!scientific computing} [@jumper2021highly] is DeepMind's protein structure prediction system that solved a 50-year grand challenge in biology. AlphaFold represents the *compute-intensive cloud* deployment pattern: training required 128 TPUv3 cores for weeks, accessing the entire Protein Data Bank to predict structures for over 200 million proteins.
|
||
|
||
These systems complement the Lighthouse Models introduced earlier by illustrating how the same core challenges (data quality, model complexity, and infrastructure scale) manifest under radically different constraints. Rather than examining each system in isolation, we analyze them through the lens of the D·A·M taxonomy. The same data drift phenomenon that affects Waymo's perception models in changing weather also affects FarmBeats' crop disease detection across growing seasons, though the engineering responses differ based on machine constraints.
|
||
|
||
The interdependencies across the D·A·M axes create specific challenge categories that define the daily work of an ML systems engineer. By examining our deployment extremes, we can see these challenges in their most rigorous forms.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: waymo-data-rates
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ WAYMO DATA RATES
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Data Challenges paragraph (Waymo sensor data rates)
|
||
# │
|
||
# │ Goal: Provide concrete data volumes for autonomous vehicle sensors.
|
||
# │ Show: The TB/hour scale of real-world multimodal data ingestion.
|
||
# │ How: List low and high data rates from Waymo's public specifications.
|
||
# │
|
||
# │ Imports: mlsys.constants (WAYMO_DATA_PER_HOUR_LOW, WAYMO_DATA_PER_HOUR_HIGH,
|
||
# │ TB, hour), mlsys.formatting (fmt)
|
||
# │ Exports: waymo_data_low_str, waymo_data_high_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.constants import WAYMO_DATA_PER_HOUR_LOW, WAYMO_DATA_PER_HOUR_HIGH, TB, hour
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class WaymoStats:
|
||
"""
|
||
Namespace for Waymo Data Rates.
|
||
Scenario: Autonomous vehicles generating massive data volumes (TB/hr).
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
# From constants (Waymo 1-5 TB/hr citation)
|
||
rate_low_raw = WAYMO_DATA_PER_HOUR_LOW
|
||
rate_high_raw = WAYMO_DATA_PER_HOUR_HIGH
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
val_low = rate_low_raw.m_as(TB / hour)
|
||
val_high = rate_high_raw.m_as(TB / hour)
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(val_low >= 1, f"Waymo data rate ({val_low} TB/hr) is too low.")
|
||
check(val_high > val_low, "High rate must be > Low rate.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
low_str = fmt(val_low, precision=0, commas=False)
|
||
high_str = fmt(val_high, precision=0, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
waymo_data_low_str = WaymoStats.low_str
|
||
waymo_data_high_str = WaymoStats.high_str
|
||
```
|
||
|
||
Real-world data is often noisy and inconsistent, presenting the first category of challenges. Waymo's autonomous vehicles serve as roving data centers, processing between `{python} waymo_data_low_str` and `{python} waymo_data_high_str` terabytes of data per hour across their sensor suite, including LiDAR[^fn-lidar-pointcloud], radar[^fn-radar-robustness], and cameras. Engineers must solve for sensor interference, such as rain obscuring cameras, and temporal misalignment across asynchronous data streams. Scale compounds these quality issues: while FarmBeats operates under severe constraints (running inference on models under 500 KB transmitted over TV white-space bandwidth measured in kilobits per second), AlphaFold occupies the opposite extreme, requiring access to the entire Protein Data Bank containing over 180,000 experimentally determined structures to predict configurations for more than 200 million proteins.
|
||
|
||
Data drift\index{Data Drift!operational burden} creates an ongoing operational burden atop both quality and scale. The statistical properties of input data change over time, and models are only as reliable as their alignment with the current distribution. Waymo models trained on Phoenix's sun-drenched roads may fail in New York's snowstorms due to distribution shift[^fn-drift-entropy]; detecting these shifts requires continuous monitoring of input statistics before they manifest as system failures.
|
||
|
||
[^fn-lidar-pointcloud]: **LiDAR (Light Detection and Ranging)**: This sensor is a primary reason the vehicle is a "roving data center," as its pulsed lasers generate a dense 3D point cloud of the environment. The raw data stream from a single unit can exceed 100 MB/s, creating both the terabyte-scale volume challenge and the quality challenge mentioned, as the signal is easily degraded by sensor interference from rain or fog. \index{LiDAR!data volume}
|
||
|
||
[^fn-radar-robustness]: **Radar (Radio Detection and Ranging)**: Radar emits radio waves that are largely unaffected by the rain and fog that blind optical sensors like cameras. This physical property provides the critical robustness layer in a sensor fusion system like Waymo's, allowing it to compensate for the known failure mode of its cameras in bad weather. Automotive radar's all-weather reliability stems from its high-frequency operation (~77 GHz), which provides continuous object detection even when higher-resolution sensors are degraded. \index{Radar!sensor robustness}
|
||
|
||
[^fn-drift-entropy]: **Data Drift**: The gradual divergence between the training data distribution ($P_0$) and the real-world production distribution ($P_t$). Drift is the "entropy" of ML systems: accuracy decays silently over time as the environment shifts, and without continuous monitoring the degradation is invisible until it manifests as a system failure (see @sec-ml-operations). \index{Data Drift!definition}
|
||
|
||
Beyond data, model complexity and generalization form the second challenge category. Computational intensity\index{Computational Intensity!foundation model training} defines the upper bound of capability: foundation models at GPT-3 scale (@sec-introduction-deep-learning-era-infrastructure-bottleneck-2f02) demand zettaFLOPs of compute, and even smaller scientific models like AlphaFold required training on 128 TPUv3 cores for weeks. Systems engineers must optimize for "FLOPs per watt" to make these models economically and environmentally viable. Yet raw scale is not enough. The generalization gap\index{Generalization Gap!benchmark vs. production} remains the central algorithmic risk: a model might achieve 99% accuracy on benchmarks but only 75% in the real world. For Waymo's safety-critical autonomous driving systems, minimizing this gap is a life-or-death requirement, demanding techniques like transfer learning and adversarial testing to ensure robustness across the long tail of edge cases.
|
||
|
||
The third category encompasses the system-level challenges of getting models to work reliably in production. The "training-serving divide"\index{Training-Serving Divide} describes the gap between the flexible environment where models are born and the rigid environment where they operate. Latency-throughput\index{Latency!vs. throughput trade-off} trade-offs dictate architecture: Waymo's perception system requires <10 ms latency for safety, forcing computation to the *edge*, while AlphaFold prioritizes throughput, running for days in the *cloud* to explore vast protein configuration spaces. Hybrid coordination\index{Hybrid Coordination!tiered architectures} adds further complexity, as modern systems increasingly adopt tiered architectures. A voice assistant, for example, performs wake-word detection locally (TinyML) to preserve privacy and reduce latency, but offloads complex natural language processing to massive GPU clusters in the cloud.
|
||
|
||
Finally, as systems scale, their impact on society becomes a first-class engineering concern that cuts across all three D·A·M axes. Fairness and bias\index{Fairness}\index{Bias} must be managed proactively, since models can unintentionally learn societal biases present in their training data. Responsible engineering requires systematic auditing of performance across demographic subgroups to ensure equitable outcomes. Transparency and privacy\index{Transparency}\index{Privacy} requirements further constrain design: many deep networks function as "black boxes," yet in domains like healthcare or finance, stakeholders require interpretability. Systems must also be resilient against inference attacks[^fn-inference-attack-privacy] that attempt to extract sensitive training data from model predictions.
|
||
|
||
[^fn-inference-attack-privacy]: **Inference Attack**: A security threat where an adversary queries a model to deduce sensitive information about the training set. These attacks exploit the tendency of overparameterized models to memorize unique patterns in their training data, creating a direct trade-off between model capacity ($O$) and privacy risk that motivates defensive techniques such as differential privacy and output perturbation. \index{Inference Attack!privacy risk}
|
||
|
||
These four challenge categories, data, model, system, and ethical, do not exist in isolation. Data drift degrades model accuracy, which strains infrastructure, which amplifies ethical risks. Addressing them requires not ad hoc solutions but a structured engineering framework that assigns clear responsibility for each category while ensuring coordination across all of them.
|
||
|
||
## Five-Pillar Framework {#sec-introduction-fivepillar-framework-8118}
|
||
|
||
Industry surveys report that 60--85% of ML projects fail to reach production [@paleyes2022challenges], not because the algorithms are wrong but because no single team owns the full chain from data quality through model reliability to ethical governance. Silent performance degradation, data drift, model complexity, and ethical concerns each demand specialized engineering, yet they interact: a data quality failure degrades the model, which strains the serving infrastructure, which amplifies ethical risks. Traditional software engineering practices cannot address systems that degrade quietly rather than failing visibly. What is needed is a structured framework that assigns clear responsibility for each challenge category while ensuring coordination across all of them.
|
||
|
||
This work organizes ML systems engineering around five interconnected disciplines that directly address the challenge categories we have identified. @fig-pillars presents this organizational structure: five engineering pillars, each targeting a distinct challenge category, resting on a shared foundation that reflects the physical and economic constraints every pillar must respect. Together, they represent the core engineering capabilities required to bridge the gap between research prototypes and production systems capable of operating reliably at scale. While these pillars organize the *practice* of ML engineering, they are supported by the foundational technical imperatives of **Performance Optimization** and **Hardware Acceleration** (covered in Part III), which provide the efficiency required to make large-scale training and deployment economically and physically viable.
|
||
|
||
{#fig-pillars fig-pos="t" fig-alt="Five pillars diagram: Data Engineering, Training Systems, Deployment Infrastructure, Operations and Monitoring, Ethics and Governance. Pillars rest on foundation labeled Performance Optimization and Hardware Acceleration."}
|
||
|
||
### The Five Engineering Disciplines {#sec-introduction-five-engineering-disciplines-fa08}
|
||
|
||
The five-pillar framework organizes the practice of **ML Systems Engineering**, providing the operational structure for the broader AI Engineering discipline defined earlier.
|
||
|
||
Each pillar addresses specific challenge categories while recognizing their interdependencies. Alternative organizational frameworks exist: one could organize by system component (data, model, infrastructure) or by lifecycle phase (development, deployment, operation). We chose the five-pillar structure because it aligns with how engineering teams are typically organized in industry, with specialized roles for data engineering, training infrastructure, deployment, operations, and responsible AI practices. The Ethics pillar ensures that responsible engineering is treated as an explicit discipline rather than distributed implicitly across other areas, where it might be overlooked under deadline pressure.
|
||
|
||
Data Engineering (@sec-data-engineering) addresses the data-related challenges we identified: quality assurance, scale management, drift detection, and distribution shift. This pillar encompasses building robust data pipelines that ensure quality, handle massive scale, maintain privacy, and provide the infrastructure upon which all ML systems depend. For systems like Waymo, this means managing terabytes of sensor data per vehicle, validating data quality in real-time, detecting distribution shifts across different cities and weather conditions, and maintaining data lineage for debugging and compliance. The techniques covered include data versioning, quality monitoring, drift detection algorithms, and privacy-preserving data processing.
|
||
|
||
Training Systems (@sec-model-training) tackles the model-related challenges around complexity and scale. This pillar covers developing training systems that can manage large datasets and complex models while optimizing computational resource utilization across distributed environments. Modern foundation models require coordinating thousands of GPUs, implementing parallelization strategies, managing training failures and restarts, and balancing training costs against model quality. The chapter explores distributed training architectures, optimization algorithms, hyperparameter tuning at scale, and the frameworks that make large-scale training practical.
|
||
|
||
Deployment Infrastructure (@sec-benchmarking, @sec-ml-operations) addresses system-related challenges around the training-serving divide and operational complexity. This pillar encompasses measuring and optimizing inference performance across deployment tiers, from cloud to edge devices. @sec-benchmarking covers inference metrics, latency analysis, and the MLPerf scenarios that characterize different deployment contexts. @sec-ml-operations covers A/B testing, staged rollouts, and operational playbooks for production systems.
|
||
|
||
Operations and Monitoring\index{MLOps} (@sec-ml-operations, @sec-benchmarking) directly addresses the silent performance degradation patterns distinctive to ML systems. This pillar covers creating monitoring and maintenance systems that ensure continued performance, enable early issue detection, and support safe system updates in production. Unlike traditional software monitoring focused on infrastructure metrics, ML operations requires four-dimensional monitoring: infrastructure health, model performance, data quality, and business impact. The chapter explores metrics design, alerting strategies, incident response procedures, debugging techniques for production ML systems, and continuous evaluation approaches that catch degradation before it affects users.
|
||
|
||
Ethics and Governance\index{Responsible AI} (@sec-responsible-engineering) addresses the ethical and societal challenges around fairness, transparency, privacy, and safety. This pillar implements responsible engineering practices throughout the system lifecycle rather than treating them as an afterthought. This book introduces core methods and workflows, and the companion book extends these ideas to governance and deployment at scale.
|
||
|
||
### Connecting Components, Lifecycle, and Disciplines {#sec-introduction-connecting-components-lifecycle-disciplines-d372}
|
||
|
||
The five pillars do not operate in isolation; they emerge from the D·A·M taxonomy and lifecycle stages established earlier, with each pillar responsible for specific axes and their interactions across the system lifecycle. This structure reflects how AI evolved from algorithm-centric research to systems-centric engineering, shifting focus from making individual algorithms work to building systems that reliably deploy, operate, and maintain those algorithms at scale. The five pillars represent the engineering capabilities required for that transition.
|
||
|
||
These pillars also provide the organizational backbone for this textbook. Each part of the book develops the knowledge and skills needed for one or more pillars, following a progression that mirrors how engineers build systems in practice: foundations first, then model construction, then optimization, and finally production deployment.
|
||
|
||
## Book Organization {#sec-introduction-book-organization-0b64}
|
||
|
||
The five pillars map directly onto this textbook's four-part structure, which progresses from foundational concepts through model development to production deployment. The organizing principle is *context before theory*: we establish the landscape and vocabulary (Part I) before building models (Part II), optimize those models (Part III), and deploy them reliably (Part IV). @tbl-book-structure outlines this organization.
|
||
|
||
| **Part** | **Theme** | **Key Chapters** |
|
||
|:-------------------|:-------------------------------|:---------------------------------------------------------------------------------------------|
|
||
| **I: Foundations** | Context: ML systems landscape | @sec-introduction, @sec-ml-systems, @sec-ml-workflow, @sec-data-engineering |
|
||
| **II: Build** | Theory: Model fundamentals | @sec-neural-computation, @sec-network-architectures, @sec-ml-frameworks, @sec-model-training |
|
||
| **III: Optimize** | Efficiency: Performance tuning | @sec-data-selection, @sec-model-compression, @sec-hardware-acceleration, @sec-benchmarking |
|
||
| **IV: Deploy** | Production: Real-world systems | @sec-model-serving, @sec-ml-operations, @sec-responsible-engineering, @sec-conclusion |
|
||
|
||
: **Book Organization**: The four parts follow a pedagogical progression from context (Foundations) through theory (Build) to practice (Optimize, Deploy). Each part builds on the vocabulary and frameworks of its predecessors, so Part III's optimization techniques assume familiarity with Part II's model architectures, and Part IV's deployment practices assume mastery of Parts II and III. {#tbl-book-structure}
|
||
|
||
Part I establishes context by surveying the ML systems landscape. @sec-introduction develops the engineering revolution in AI and the frameworks that organize this discipline. @sec-ml-systems explores the deployment spectrum from Cloud to TinyML, examining how physical constraints (power envelopes, memory hierarchies, and latency budgets) govern each tier. @sec-ml-workflow presents the end-to-end process from problem formulation through deployment, providing the conceptual map that guides subsequent learning. @sec-data-engineering addresses data collection, processing, and management, establishing that data infrastructure precedes and enables model development.
|
||
|
||
Part II builds theoretical foundations and practical skills for model development. @sec-neural-computation provides algorithmic foundations, while @sec-network-architectures extends these to specific network designs. Both chapters reference the five **Lighthouse Models** introduced above (ResNet-50, GPT-2/Llama, MobileNet, DLRM, and Keyword Spotting) to anchor abstract concepts in concrete workloads. @sec-ml-frameworks examines the software infrastructure from TensorFlow and PyTorch to specialized tools. @sec-model-training develops training systems for complex models and large datasets.
|
||
|
||
Part III addresses optimization for production deployment. @sec-data-selection introduces techniques for reducing computational requirements while maintaining quality. @sec-model-compression covers compression techniques including quantization, pruning, and knowledge distillation. @sec-hardware-acceleration examines specialized hardware from GPUs to custom ASICs. @sec-benchmarking establishes methodologies for measuring and comparing system performance.
|
||
|
||
Part IV ensures optimized systems operate reliably in production. @sec-model-serving covers infrastructure for delivering predictions with low latency. @sec-ml-operations encompasses practices from monitoring and deployment to incident response. @sec-responsible-engineering addresses ethical considerations and governance. @sec-conclusion synthesizes the complete methodology and prepares the reader for the transition from single-node mastery to fleet-scale orchestration.
|
||
|
||
For detailed guidance on reading paths, learning outcomes, prerequisites, and how to maximize your experience with this textbook, refer to the [About](../../frontmatter/about/about.qmd) section.
|
||
|
||
Before moving forward, we examine the assumptions that trip up practitioners new to ML systems. The frameworks above provide the right mental models, but only if we also shed the wrong ones carried over from adjacent fields. Every discipline accumulates intuitions that work within its boundaries but fail when applied elsewhere. ML systems engineering is particularly vulnerable to such imported assumptions because it draws from software engineering, statistics, and hardware design simultaneously, each of which cultivates subtly different intuitions about how systems should behave.
|
||
|
||
## Fallacies and Pitfalls {#sec-introduction-fallacies-pitfalls-230d}
|
||
|
||
Assumptions that hold in traditional software, academic research, or pure mathematics fail when applied to systems whose behavior emerges from data. The following fallacies and pitfalls capture errors that waste engineering effort, delay deployments, and cause silent production failures.
|
||
|
||
**Fallacy:** *Better algorithms automatically produce better systems.*
|
||
|
||
Engineers assume algorithmic sophistication drives system performance, but this ignores the Iron Law (@sec-introduction-iron-law-ml-systems-c32a). A state-of-the-art Vision Transformer achieves 1–2% higher accuracy than ResNet-50 on ImageNet but requires 4$\times$ the FLOPs and 3$\times$ the memory bandwidth [@dosovitskiy2021image]. In production, a model that is 1% more accurate but violates latency requirements has effectively zero utility. Google's analysis found that only 5% of production ML code is the model itself; the remaining 95% is data pipelines, serving infrastructure, and monitoring [@sculley2015hidden]. A well-engineered system with a simpler model consistently outperforms a state-of-the-art architecture lacking robust infrastructure.
|
||
|
||
**Pitfall:** *Treating ML systems as traditional software that happens to include a model.*
|
||
|
||
Engineers apply traditional testing and deployment practices to ML systems, but these systems fail in qualitatively different ways (@sec-introduction-ml-vs-traditional-software-e19a). Traditional bugs produce stack traces within milliseconds; ML systems can silently degrade 10–15% over 3–6 months before anyone notices. A/B tests in conventional software show clear signals within 2–3 days; ML comparisons may require 4–6 weeks to detect 1–2% accuracy differences across subpopulations. Unit tests verify deterministic paths; ML systems require monitoring infrastructure to catch the 5–10% of predictions where models produce unreliable outputs. Teams deploying ML with only CI/CD pipelines risk silent failures affecting 20–30% of predictions before intervention.
|
||
|
||
**Fallacy:** *High accuracy on benchmark datasets indicates production readiness.*
|
||
|
||
Engineers assume benchmark performance predicts production accuracy, but distribution shift and operational differences cause substantial degradation in deployment. A sentiment analysis model achieving 94% accuracy on curated test data drops to 78–82% accuracy in production as users employ slang, emojis, and context absent from benchmarks. The deployment spectrum (@sec-introduction-deployment-spectrum-a38c) shows that cloud, edge, and mobile environments each introduce distinct constraints: network latency adds 50–200 ms overhead, mobile devices' limited numerical precision reduces accuracy by 2–5%, and edge devices lack the memory for multi-model strategies that boosted benchmark scores. Production systems require failure mode analysis across demographic subgroups where performance may vary by 10–15 percentage points, monitoring infrastructure to detect drift, and validation protocols that match actual operating conditions rather than idealized test sets.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: amdahls-pitfall
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ AMDAHL'S LAW PITFALL
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Pitfall "Optimizing individual components without considering
|
||
# │ system interactions"
|
||
# │
|
||
# │ Goal: Demonstrate Amdahl's Law in a realistic inference pipeline.
|
||
# │ Show: That a 3× component speedup yields only a marginal end-to-end gain.
|
||
# │ How: Calculate total latency before and after local optimization.
|
||
# │
|
||
# │ Imports: mlsys.formulas (calc_amdahls_speedup), mlsys.formatting (fmt)
|
||
# │ Exports: t_inference_str, t_inf_new_str, t_pre_str, t_post_str,
|
||
# │ total_ms, new_total_ms, improv_pct, naive_p
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.formulas import calc_amdahls_speedup
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# --- Inputs (hypothetical pipeline timings) ---
|
||
t_inference_value = 45 # ms
|
||
t_pre_value = 60 # ms
|
||
t_post_value = 25 # ms
|
||
s_inf_value = 3
|
||
|
||
# --- Process (Amdahl's Law calculation) ---
|
||
t_total_value = t_pre_value + t_inference_value + t_post_value
|
||
t_inf_new_value = t_inference_value / s_inf_value
|
||
t_total_new_value = t_pre_value + t_inf_new_value + t_post_value
|
||
|
||
p_inf_value = t_inference_value / t_total_value
|
||
overall_speedup_value = calc_amdahls_speedup(p_inf_value, s_inf_value)
|
||
improvement_pct_value = (1 - (1 / overall_speedup_value)) * 100
|
||
naive_pct_value = (1 - (1 / s_inf_value)) * 100
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class AmdahlsPitfall:
|
||
"""
|
||
Namespace for Amdahl's Law Pitfall example.
|
||
Scenario: Optimizing a 45 ms inference component in a 130 ms pipeline.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
t_inference = 45 # ms
|
||
t_pre = 60 # ms
|
||
t_post = 25 # ms
|
||
s_inf = 3 # Component Speedup (3x)
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
t_total = t_pre + t_inference + t_post
|
||
t_inf_new = t_inference / s_inf
|
||
t_total_new = t_pre + t_inf_new + t_post
|
||
|
||
p_inf = t_inference / t_total
|
||
overall_speedup = 1 / ((1 - p_inf) + (p_inf / s_inf))
|
||
|
||
improvement_pct = (1 - (1 / overall_speedup)) * 100
|
||
naive_pct = (1 - (1 / s_inf)) * 100
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(overall_speedup <= 1.5, f"System speedup ({overall_speedup:.2f}x) is too high for a 'Pitfall'.")
|
||
check((improvement_pct / naive_pct) <= 0.5, "The discrepancy between naive and actual improvement is too small.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
t_inference_str = fmt(t_inference, precision=0, commas=False)
|
||
t_inf_new_str = fmt(t_inf_new, precision=0, commas=False)
|
||
t_pre_str = fmt(t_pre, precision=0, commas=False)
|
||
t_post_str = fmt(t_post, precision=0, commas=False)
|
||
total_ms = fmt(t_total, precision=0, commas=False)
|
||
new_total_ms = fmt(t_total_new, precision=0, commas=False)
|
||
improv_pct = fmt(improvement_pct, precision=0, commas=False)
|
||
naive_p = fmt(naive_pct, precision=0, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
t_inference_str = AmdahlsPitfall.t_inference_str
|
||
t_inf_new_str = AmdahlsPitfall.t_inf_new_str
|
||
t_pre_str = AmdahlsPitfall.t_pre_str
|
||
t_post_str = AmdahlsPitfall.t_post_str
|
||
total_ms = AmdahlsPitfall.total_ms
|
||
new_total_ms = AmdahlsPitfall.new_total_ms
|
||
improv_pct = AmdahlsPitfall.improv_pct
|
||
naive_p = AmdahlsPitfall.naive_p
|
||
```
|
||
|
||
**Pitfall:** *Optimizing individual components without considering system interactions.*
|
||
|
||
Engineers optimize inference latency in isolation, but **Amdahl's Law** governs end-to-end performance. A team reduces model inference from `{python} t_inference_str` ms to `{python} t_inf_new_str` ms, expecting proportional improvement. Yet preprocessing consumes `{python} t_pre_str` ms and postprocessing adds `{python} t_post_str` ms, so total latency drops only from `{python} total_ms` ms to `{python} new_total_ms` ms: `{python} improv_pct`% improvement rather than the expected `{python} naive_p`%. The D·A·M taxonomy (@tbl-dam-taxonomy) shows that data, algorithms, and machines form interdependent systems where optimizing one component shifts bottlenecks rather than eliminating them. A model requiring 3$\times$ more preprocessing can increase total cost 40% while improving accuracy only 2%. Teams optimizing components independently often find 50–70% of their engineering effort fails to improve end-to-end metrics.
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: drift-fallacy
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ DRIFT FALLACY
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Fallacy "ML systems can be deployed once and left to run
|
||
# │ indefinitely"
|
||
# │
|
||
# │ Goal: Demonstrate the impact of data drift on model performance.
|
||
# │ Show: How a recommendation system loses accuracy over 6 months without retraining.
|
||
# │ How: Apply the Degradation Equation using linear drift points per month.
|
||
# │
|
||
# │ Imports: mlsys.formatting (fmt)
|
||
# │ Exports: acc_initial_str, acc_final_str, acc_drop_str, months_str
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class DriftFallacy:
|
||
"""
|
||
Namespace for Drift Fallacy example.
|
||
Scenario: A recommendation system degrading over 6 months.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Constants) ───────────────────────────────────────────────
|
||
acc_initial = 85.0 # %
|
||
drift_points_per_month = 0.8 # 0.8% accuracy loss per month (e.g. 85 -> 84.2)
|
||
months = 6
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
# Linear degradation model for short-term estimation
|
||
total_drop = drift_points_per_month * months
|
||
acc_final = acc_initial - total_drop
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(total_drop >= 3, f"Degradation ({total_drop:.1f}%) is too small to be a 'Fallacy'.")
|
||
check(acc_final >= 50, f"Model became random guessing ({acc_final:.1f}%), which is unrealistic for 6 months.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
acc_initial_str = fmt(acc_initial, precision=0, commas=False)
|
||
acc_final_str = fmt(acc_final, precision=0, commas=False)
|
||
acc_drop_str = fmt(total_drop, precision=1, commas=False) # Changed to 1 decimal for precision
|
||
months_str = fmt(months, precision=0, commas=False)
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
acc_initial_str = DriftFallacy.acc_initial_str
|
||
acc_final_str = DriftFallacy.acc_final_str
|
||
acc_drop_str = DriftFallacy.acc_drop_str
|
||
months_str = DriftFallacy.months_str
|
||
```
|
||
|
||
**Fallacy:** *ML systems can be deployed once and left to run indefinitely.*
|
||
|
||
Engineers assume deployed systems maintain performance indefinitely, but the Degradation Equation (@eq-degradation) quantifies why ML systems decay. A recommendation system deployed at `{python} acc_initial_str`% accuracy drops to `{python} acc_final_str`% within `{python} months_str` months as purchasing patterns shift, losing `{python} acc_drop_str` percentage points without any code changes. The ML development lifecycle (@sec-introduction-ml-development-lifecycle-4ea0) shows continuous monitoring and retraining as operational requirements. Fraud detection models degrade 5–10% per quarter as attackers adapt. NLP systems lose 2–3% accuracy annually from vocabulary drift. Without monitoring, systems appear healthy while 15–25% of predictions become unreliable. Organizations treating deployment as one-time typically discover failures through customer complaints 3–6 months after degradation begins.
|
||
|
||
**Pitfall:** *Assuming that ML expertise alone is sufficient for ML systems engineering.*
|
||
|
||
Organizations hire ML researchers expecting production-ready systems, but the five engineering disciplines (@sec-introduction-five-engineering-disciplines-fa08) require integrated expertise across algorithms, software, systems, and operations. Teams with strong ML skills but limited systems experience ship systems achieving only 10–20% of throughput targets because they lack API design and database optimization expertise. Conversely, software engineers without ML understanding build infrastructure that introduces preprocessing bugs causing 5–15% accuracy degradation undetected for months. Industry surveys report 60–85% of ML projects fail to reach production, primarily due to systems engineering gaps rather than algorithmic limitations [@paleyes2022challenges]. Effective teams integrate ML researchers, software engineers, and operations specialists rather than expecting one role to master all skills.
|
||
|
||
## Summary {#sec-introduction-summary-385d}
|
||
|
||
This introduction has established the conceptual foundation for everything that follows. The chapter began by examining the relationship between artificial intelligence as vision and machine learning as methodology, then defined machine learning systems as the artifacts that engineers build: integrated computing systems comprising data, algorithms, and machines, formalized as the AI Triad and its diagnostic counterpart, the D·A·M taxonomy. Two quantitative principles provide the conceptual backbone for reasoning about these systems. The **Iron Law of ML Systems** decomposes performance into data movement, computation, and overhead, revealing that the slowest component limits the system. The **Degradation Equation** captures how model performance evolves as data distributions shift, a phenomenon unique to ML systems that traditional software engineering never confronts.
|
||
|
||
Through the Bitter Lesson and AI's historical evolution, the chapter demonstrated why systems engineering has become central to AI progress and how learning-based approaches came to dominate the field. The Efficiency Framework revealed that algorithmic, compute, and data-selection gains must outpace exponentially growing compute demands, with deployment contexts from cloud to TinyML determining which dimension to prioritize. The chapter then traced the ML development lifecycle and illustrated deployment extremes through case studies, showing why continuous iteration and context-aware design are mandatory rather than optional. This context enabled a formal definition of AI Engineering as a distinct discipline, following the pattern of Computer Engineering's emergence and establishing it as the field dedicated to building reliable, efficient, and scalable machine learning systems across all computational platforms. The five Lighthouse Models introduced here (ResNet-50, GPT-2/Llama, MobileNetV2, DLRM, and Keyword Spotting, detailed in @sec-network-architectures) serve as recurring touchstones throughout subsequent chapters, grounding abstract principles in the concrete engineering challenges of real workloads.
|
||
|
||
::: {.callout-takeaways title="Constraints Drive Architecture"}
|
||
|
||
* **The D·A·M taxonomy governs all ML systems**: Data, Algorithm, and Machine are interdependent axes. Optimizing one axis in isolation shifts bottlenecks rather than eliminating them. When performance stalls, analysis should examine all three axes of the D·A·M taxonomy.
|
||
* **ML systems fail silently, and the Degradation Equation quantifies why**: Unlike traditional software that crashes on errors, ML systems degrade as data distributions drift. Use the Degradation Equation to estimate expected accuracy loss over time and to set retraining triggers based on measurable drift.
|
||
* **The Iron Law decomposes performance into three terms**: Data movement, computation, and overhead each resolve to time; the slowest term dominates. Reducing inference from `{python} t_inference_str` ms to `{python} t_inf_new_str` ms yields only `{python} improv_pct`% end-to-end improvement when preprocessing (`{python} t_pre_str` ms) and postprocessing (`{python} t_post_str` ms) dominate total latency.
|
||
* **The Bitter Lesson applies**: Scale and compute outperform hand-crafted features. Systems that use general methods with more computation consistently outperform specialized approaches long-term.
|
||
* **Efficiency and scale co-evolve across the deployment spectrum**: Algorithmic improvements delivered a 44$\times$ compute reduction, yet total AI compute grew by seven orders of magnitude. Deployment context (from cloud to TinyML) determines which efficiency dimension (algorithmic, compute, or data) to prioritize.
|
||
* **Five pillars require integration**: ML systems engineering encompasses Data Engineering, Training Systems, Deployment Infrastructure, Operations and Monitoring, and Ethics and Governance. Teams lacking expertise in any pillar face 60–70% project failure rates.
|
||
* **Lifecycle and deployment context reshape every design decision**: ML systems require continuous iteration and monitoring, and the dominant bottlenecks shift across cloud, edge, mobile, and TinyML deployments.
|
||
|
||
:::
|
||
|
||
The principles and frameworks established in this introduction provide the conceptual vocabulary for everything that follows. They also answer the question posed at the outset: building machine learning systems demands different engineering principles because these systems derive their behavior from data rather than code, degrade silently rather than fail explicitly, and require co-design across algorithms, software, and hardware at every stage. This is the mandate of **AI Engineering**: to tame this stochastic behavior with deterministic reliability. The D·A·M taxonomy offers a systematic lens for analyzing any ML system challenge, while the five Lighthouse Models ground these abstract concepts in concrete engineering problems encountered throughout a practitioner's career.
|
||
|
||
This book makes a stronger claim: ML systems engineering is not merely a collection of best practices but a *distinct engineering discipline* with its own governing laws. The Iron Law decomposes every inference into data movement, computation, and overhead, three terms rooted in silicon physics that no software optimization can repeal. The Conservation of Complexity guarantees that cost is relocated, never eliminated. The Statistical Drift Invariant ensures that every deployed model decays at a rate determined by the distance between its training distribution and the live world. The Memory Wall sets bandwidth ceilings that faster arithmetic cannot overcome. These are not guidelines; they are physical constraints as permanent as Ohm's law or the speed of light. The twelve invariants developed across the chapters that follow constitute the field's first principled vocabulary, a shared analytical language for reasoning about ML systems from physics rather than from intuition.
|
||
|
||
::: {.callout-chapter-connection title="From Vision to Architecture"}
|
||
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ SYSTEM ARCHETYPES (THE HIERARCHY)
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Section "Where should an ML model run?"
|
||
# │
|
||
# │ Goal: Quantify the 9-order-of-magnitude span of the ML Systems landscape.
|
||
# │ Show: RAM, Compute, and Power constraints for the 4 deployment paradigms.
|
||
# │ How: Reference the Systems Archetypes (Cloud, Edge, Mobile, Tiny).
|
||
# │
|
||
# │ Imports: mlsys.Systems, mlsys.formatting
|
||
# │ Exports: cloud_*, edge_*, mobile_*, tiny_* formatted strings
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Systems, Archetypes
|
||
from mlsys.constants import GB, MB, KiB, watt, milliwatt, TFLOPs, second, flop
|
||
from mlsys.formatting import fmt, check
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class DeploymentSystems:
|
||
"""
|
||
Namespace for the 4 Deployment Archetypes.
|
||
"""
|
||
|
||
# ┌── 1. LOAD (Archetypes) ───────────────────────────────────────────────
|
||
s_cloud = Systems.Cloud # H100
|
||
s_edge = Systems.Edge # Jetson
|
||
s_mobile = Systems.Mobile # Smartphone
|
||
s_tiny = Systems.Tiny # ESP32
|
||
|
||
# ┌── 2. EXECUTE (The Compute) ─────────────────────────────────────────
|
||
# We compare the scaling factors (Cloud vs Tiny)
|
||
mem_scaling = (s_cloud.ram / s_tiny.ram).to('count').magnitude
|
||
compute_scaling = (s_cloud.peak_flops / s_tiny.peak_flops).to('count').magnitude
|
||
power_scaling = (s_cloud.power_budget / s_tiny.power_budget).to('count').magnitude
|
||
|
||
# ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
|
||
check(mem_scaling > 1e5, "Cloud memory should be >100,000x TinyML memory.")
|
||
check(compute_scaling > 1e6, "Cloud compute should be >1,000,000x TinyML compute.")
|
||
|
||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||
cloud_mem_str = fmt(s_cloud.ram.m_as(GB), precision=0)
|
||
cloud_compute_str = fmt(s_cloud.peak_flops.m_as(TFLOPs/second), precision=0)
|
||
cloud_power_str = fmt(s_cloud.power_budget.m_as(watt), precision=0)
|
||
|
||
edge_mem_str = fmt(s_edge.ram.m_as(GB), precision=0)
|
||
edge_compute_str = fmt(s_edge.peak_flops.m_as(TFLOPs/second), precision=1)
|
||
edge_power_str = fmt(s_edge.power_budget.m_as(watt), precision=0)
|
||
|
||
mobile_mem_str = fmt(s_mobile.ram.m_as(GB), precision=0)
|
||
mobile_compute_str = fmt(s_mobile.peak_flops.m_as(TFLOPs/second), precision=1)
|
||
mobile_power_str = fmt(s_mobile.power_budget.m_as(watt), precision=0)
|
||
|
||
tiny_mem_str = fmt(s_tiny.ram.m_as(KiB), precision=0)
|
||
tiny_compute_str = fmt(s_tiny.peak_flops.m_as(TFLOPs/second), precision=4)
|
||
tiny_power_str = fmt(s_tiny.power_budget.m_as(milliwatt), precision=0)
|
||
|
||
mem_span_str = f"{mem_scaling:.0e}".replace("e+0", "10^").replace("e+", "10^")
|
||
compute_span_str = f"{compute_scaling:.0e}".replace("e+0", "10^").replace("e+", "10^")
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
cloud_mem_str = DeploymentSystems.cloud_mem_str
|
||
cloud_compute_str = DeploymentSystems.cloud_compute_str
|
||
cloud_power_str = DeploymentSystems.cloud_power_str
|
||
edge_mem_str = DeploymentSystems.edge_mem_str
|
||
edge_compute_str = DeploymentSystems.edge_compute_str
|
||
edge_power_str = DeploymentSystems.edge_power_str
|
||
mobile_mem_str = DeploymentSystems.mobile_mem_str
|
||
mobile_compute_str = DeploymentSystems.mobile_compute_str
|
||
mobile_power_str = DeploymentSystems.mobile_power_str
|
||
tiny_mem_str = DeploymentSystems.tiny_mem_str
|
||
tiny_compute_str = DeploymentSystems.tiny_compute_str
|
||
tiny_power_str = DeploymentSystems.tiny_power_str
|
||
mem_span_str = DeploymentSystems.mem_span_str
|
||
compute_span_str = DeploymentSystems.compute_span_str
|
||
```
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: application-scenarios
|
||
# ┌─────────────────────────────────────────────────────────────────────────────
|
||
# │ APPLICATION SCENARIOS (THE MISSIONS)
|
||
# ├─────────────────────────────────────────────────────────────────────────────
|
||
# │ Context: Section "The Engineering Crux"
|
||
# │
|
||
# │ Goal: Connect Models, Systems, and Missions into concrete scenarios.
|
||
# │ Show: How Lighthouse Models map to specific User Missions.
|
||
# │ How: Reference the Application Archetypes (Frontier, AutoDrive, Doorbell).
|
||
# │
|
||
# │ Imports: mlsys.Applications, mlsys.formatting
|
||
# │ Exports: scenario_* strings for mission and constraints.
|
||
# └─────────────────────────────────────────────────────────────────────────────
|
||
from mlsys import Applications
|
||
from mlsys.formatting import fmt
|
||
|
||
# ┌── LEGO ───────────────────────────────────────────────
|
||
class ScenarioRegistry:
|
||
"""
|
||
Namespace for Application Scenarios.
|
||
"""
|
||
app_frontier = Applications.Frontier
|
||
app_drive = Applications.AutoDrive
|
||
app_doorbell = Applications.Doorbell
|
||
|
||
# ┌── EXPORTS (Bridge to Text) ─────────────────────────────────────────────────
|
||
scenario_frontier_mission = ScenarioRegistry.app_frontier.mission_goal
|
||
scenario_frontier_limit = ScenarioRegistry.app_frontier.critical_constraint
|
||
|
||
scenario_drive_mission = ScenarioRegistry.app_drive.mission_goal
|
||
scenario_drive_limit = ScenarioRegistry.app_drive.critical_constraint
|
||
|
||
scenario_doorbell_mission = ScenarioRegistry.app_doorbell.mission_goal
|
||
scenario_doorbell_limit = ScenarioRegistry.app_doorbell.critical_constraint
|
||
```
|
||
|
||
::: {.callout-perspective #perspective-deployment-archetypes title="The ML Systems Landscape: Four Archetypes"}
|
||
|
||
The machine learning systems landscape spans nine orders of magnitude in computational power and memory capacity. We categorize this continuum into four **System Archetypes** that define the constraints for every subsequent chapter:
|
||
|
||
| **Archetype** | **Example System** | **RAM / Memory** | **Peak Compute** | **Power Budget** |
|
||
|:---|:---|:---|:---|:---|
|
||
| **Cloud** | H100 Cluster | `{python} cloud_mem_str` GB | `{python} cloud_compute_str` TFLOPS | `{python} cloud_power_str` W |
|
||
| **Edge** | Jetson Robotics | `{python} edge_mem_str` GB | `{python} edge_compute_str` TFLOPS | `{python} edge_power_str` W |
|
||
| **Mobile** | Smartphone | `{python} mobile_mem_str` GB | `{python} mobile_compute_str` TFLOPS | `{python} mobile_power_str` W |
|
||
| **TinyML** | ESP32-S3 | `{python} tiny_mem_str` KiB | `{python} tiny_compute_str` TFLOPS | `{python} tiny_power_str` mW |
|
||
|
||
**The Scaling Gap**: The gap between the Cloud and TinyML archetypes is roughly `{python} mem_span_str` in memory and `{python} compute_span_str` in compute power. This divergence is precisely why we cannot simply "shrink" a cloud model to run at the edge; each tier requires a fundamental redesign of the D·A·M axes.
|
||
|
||
:::
|
||
|
||
Where should an ML model actually run? The answer is not "wherever is most convenient." Physical laws dictate what is possible.
|
||
The speed of light makes distant cloud servers useless for emergency braking. Thermodynamics prevents datacenter-class models from running in your pocket. Memory physics creates bandwidth ceilings that faster chips cannot overcome. @sec-ml-systems introduces the four deployment paradigms (Cloud, Edge, Mobile, and TinyML) that span nine orders of magnitude in power and memory, explaining why each exists and how to choose among them.
|
||
|
||
Welcome to AI Engineering.
|
||
|
||
:::
|
||
|
||
::: { .quiz-end }
|
||
:::
|
||
|
||
```{python}
|
||
#| echo: false
|
||
#| label: chapter-end
|
||
from mlsys.registry import end_chapter
|
||
end_chapter("vol1:introduction")
|
||
```
|