mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-16 14:42:29 -05:00
477 lines
18 KiB
Plaintext
477 lines
18 KiB
Plaintext
---
|
|
title: "Full-Stack Audit: LLaMA-70B Training"
|
|
subtitle: "One model, six domains, twelve walls --- a complete systems analysis in 60 seconds."
|
|
description: "Compose 6+ solvers across all six taxonomy domains to produce a holistic training analysis. Discover that the binding constraint is compute, but checkpoint overhead is the hidden cost."
|
|
categories: ["capstone", "advanced"]
|
|
---
|
|
|
|
## The Question
|
|
|
|
What does a **complete** systems analysis look like? No single solver captures the full
|
|
picture. Training a 70B-parameter model on 512 H100 GPUs involves compute walls, memory
|
|
walls, communication overhead, checkpoint I/O, energy costs, and carbon emissions ---
|
|
simultaneously. This tutorial traces all six taxonomy domains and exercises 12 of the 22
|
|
systems walls through a single workload.
|
|
|
|
::: {.callout-note}
|
|
## Prerequisites
|
|
Complete [Tutorial 0: Hello, Roofline](00_hello_roofline.qmd),
|
|
[Tutorial 1: The Memory Wall](01_memory_wall.qmd),
|
|
[Tutorial 6: Scaling to 1000 GPUs](06_scaling_1000_gpus.qmd), and
|
|
[Tutorial 9: Sensitivity Analysis](09_sensitivity.qmd). You should understand
|
|
roofline analysis, distributed training, and binding constraint identification.
|
|
:::
|
|
|
|
::: {.callout-note}
|
|
## What You Will Learn
|
|
|
|
- **Compose** six solver families across all taxonomy domains into a holistic analysis
|
|
- **Identify** which of the 22 systems walls bind for a real training workload
|
|
- **Quantify** the hidden costs: checkpoint overhead, carbon, water, and TCO
|
|
- **Produce** a summary table mapping domain -> solver -> binding wall
|
|
:::
|
|
|
|
::: {.callout-tip}
|
|
## Solver Quick Reference
|
|
|
|
This capstone uses solvers from all six domains. If you arrived via an accelerated
|
|
learning path, here is what each solver does:
|
|
|
|
| Solver | Domain | What It Computes |
|
|
|:-------|:-------|:-----------------|
|
|
| `SingleNodeModel` | Node | Roofline bottleneck, latency, throughput |
|
|
| `DataModel` | Data | Whether the data pipeline can sustain GPU demand |
|
|
| `ScalingModel` | Algorithm | Compute-optimal training budget (Chinchilla) |
|
|
| `DistributedModel` | Fleet | Communication overhead and scaling efficiency |
|
|
| `ReliabilityModel` | Fleet | Cluster MTBF and optimal checkpoint intervals |
|
|
| `EconomicsModel` | Ops | CapEx, OpEx, and total cost of ownership (TCO) |
|
|
| `SustainabilityModel` | Ops | Energy, carbon footprint, and water usage |
|
|
| `SensitivitySolver` | Analysis | Partial derivatives identifying the binding constraint |
|
|
| `SynthesisSolver` | Analysis | Minimum hardware specs from a latency target |
|
|
:::
|
|
|
|
::: {.callout-tip}
|
|
## Background: The Six Taxonomy Domains
|
|
|
|
The MLSys wall taxonomy organizes 22 systems walls into six domains:
|
|
|
|
| Domain | Walls | What It Covers |
|
|
|:-------|:------|:---------------|
|
|
| Node | 1--3 | Compute, memory capacity, memory bandwidth |
|
|
| Data | 8--10 | Storage throughput, data pipeline stalls |
|
|
| Algorithm | 11--13 | Scaling laws, compute-optimal training |
|
|
| Fleet | 14--16 | Communication, synchronization, reliability |
|
|
| Ops | 17--20 | TCO, energy, carbon, water, safety |
|
|
| Analysis | 21--22 | Sensitivity, inverse synthesis |
|
|
|
|
No single solver spans all six. The insight emerges from **composition**.
|
|
:::
|
|
|
|
---
|
|
|
|
## 1. Setup: Build the Fleet
|
|
|
|
We construct a 512-GPU training cluster: 64 DGX H100 nodes, 8 GPUs per node,
|
|
NVLink intra-node, InfiniBand NDR inter-node, powered by Quebec's hydroelectric grid.
|
|
|
|
```{python}
|
|
#| echo: false
|
|
#| output: false
|
|
import mlsysim # installed via `pip install mlsysim` (see workflow)
|
|
import mlsysim
|
|
```
|
|
|
|
```python
|
|
import mlsysim
|
|
from mlsysim.systems.types import Fleet, Node, NetworkFabric
|
|
from mlsysim.core.units import Q_
|
|
```
|
|
|
|
```{python}
|
|
from mlsysim.systems.types import Fleet, Node, NetworkFabric
|
|
from mlsysim.infrastructure.registry import Grids
|
|
from mlsysim.core.units import Q_
|
|
|
|
model = mlsysim.Models.Language.Llama3_70B
|
|
h100 = mlsysim.Hardware.Cloud.H100
|
|
ib_ndr = mlsysim.Systems.Fabrics.InfiniBand_NDR
|
|
|
|
# Build the DGX H100 node: 8 GPUs connected by NVLink 4.0
|
|
node = Node(
|
|
name="DGX H100",
|
|
accelerator=h100,
|
|
accelerators_per_node=8,
|
|
intra_node_bw=h100.nvlink.bandwidth
|
|
)
|
|
|
|
# Build the cluster fabric: InfiniBand NDR (400 Gbps)
|
|
fabric = NetworkFabric(
|
|
name="InfiniBand NDR",
|
|
topology="fat-tree",
|
|
bandwidth=ib_ndr.bandwidth
|
|
)
|
|
|
|
# Build the fleet: 64 nodes = 512 GPUs, Quebec grid
|
|
fleet = Fleet(
|
|
name="Training Cluster",
|
|
node=node,
|
|
count=64,
|
|
fabric=fabric,
|
|
region=Grids.Quebec
|
|
)
|
|
|
|
from mlsysim.show import table, info, banner
|
|
|
|
info("Fleet Configuration",
|
|
Model=f"{model.name} ({model.parameters.to('Bparam'):.1f~})",
|
|
Fleet=f"{fleet.count} nodes x {node.accelerators_per_node} GPUs = {fleet.total_accelerators} GPUs",
|
|
Intra_node=f"NVLink 4.0 ({h100.nvlink.bandwidth.to('GB/s'):.0f~})",
|
|
Inter_node=f"IB NDR ({ib_ndr.bandwidth.to('Gbps'):.0f~})",
|
|
Region=Grids.Quebec.name)
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Node (Walls 1--3): Single-GPU Roofline
|
|
|
|
First, classify the per-GPU forward-backward pass. Is each GPU compute-bound or
|
|
memory-bound during training?
|
|
|
|
```{python}
|
|
from mlsysim.solvers import SingleNodeModel
|
|
|
|
node_solver = SingleNodeModel()
|
|
node_result = node_solver.solve(
|
|
model=model, hardware=h100,
|
|
batch_size=4, precision="fp16"
|
|
)
|
|
|
|
banner("Domain: Node (Walls 1-3)")
|
|
info(Bottleneck=node_result.bottleneck,
|
|
Per_GPU_latency=node_result.latency.to('ms'),
|
|
Throughput=f"{node_result.throughput:.0f} samples/s")
|
|
```
|
|
|
|
Training at batch size 4 per GPU puts us in the compute-bound regime --- unlike inference,
|
|
training has high arithmetic intensity due to the backward pass. Wall 1 (Compute) is the
|
|
binding constraint at the node level.
|
|
|
|
Compute-bound is good news --- it means the GPU is doing useful work, not waiting for data.
|
|
But can the data pipeline actually keep up with 512 GPUs demanding training samples?
|
|
|
|
---
|
|
|
|
## 3. Data (Walls 8--10): Can the Pipeline Keep Up?
|
|
|
|
The roofline tells us each GPU can consume data at a certain rate. But can the storage and
|
|
preprocessing pipeline actually deliver data that fast? If not, the GPUs stall --- and
|
|
"compute-bound" becomes a meaningless label.
|
|
|
|
```{python}
|
|
from mlsysim.solvers import DataModel
|
|
|
|
# Estimate data demand per step: 4 samples/GPU * 512 GPUs * 2048 tokens * 2 bytes ≈ 8 MB/step
|
|
# At ~1 step/sec, this is ~8 MB/s — tokenized text is compact
|
|
data_demand = Q_("8 MB/s")
|
|
|
|
data_solver = DataModel()
|
|
data_result = data_solver.solve(
|
|
workload_data_rate=data_demand,
|
|
hardware=h100
|
|
)
|
|
|
|
banner("Domain: Data (Walls 8-10)")
|
|
info(Data_demand=data_result.demand_bw,
|
|
Data_supply=data_result.supply_bw,
|
|
Utilization=f"{data_result.utilization:.1%}",
|
|
Stalled=data_result.is_stalled,
|
|
Bottleneck=data_result.bottleneck)
|
|
```
|
|
|
|
For text-based training, the data pipeline is rarely the bottleneck --- tokenized text
|
|
is compact. But for image or video training, this wall can dominate.
|
|
|
|
The data pipeline can keep up. The GPUs are compute-bound and well-fed. But are we
|
|
spending our compute budget wisely? A 30-day run on 512 GPUs is an enormous investment
|
|
--- the scaling laws tell us whether we are allocating it optimally.
|
|
|
|
---
|
|
|
|
## 4. Algorithm (Walls 11--13): Compute-Optimal Budget
|
|
|
|
Is our training budget compute-optimal? The Chinchilla scaling law says
|
|
D = 20P (tokens = 20x parameters) for optimal allocation.
|
|
|
|
```{python}
|
|
from mlsysim.solvers import ScalingModel
|
|
|
|
# MFU (Model FLOP Utilization): the fraction of peak hardware FLOP/s that goes
|
|
# to useful model computation (excluding communication, idle time, overhead).
|
|
# MFU = 0.4 means 40% of theoretical peak -- typical for large-scale LLM training.
|
|
# Published values: 0.30-0.45 (Llama-2/3), up to 0.50 (highly optimized runs).
|
|
# Compute budget: 512 GPUs * 989 TFLOPs * 30 days * 86400s * 0.4 MFU
|
|
gpu_flops = h100.compute.peak_flops.to("flop/s").magnitude
|
|
total_flops = 512 * gpu_flops * 30 * 86400 * 0.4
|
|
compute_budget = Q_(total_flops, "flop")
|
|
|
|
scaling_solver = ScalingModel()
|
|
scaling_result = scaling_solver.solve(
|
|
compute_budget=compute_budget,
|
|
target_model_size=model.parameters
|
|
)
|
|
|
|
banner("Domain: Algorithm (Walls 11-13)")
|
|
info(Compute_budget=compute_budget.to('EFLOP'),
|
|
Optimal_tokens=f"{scaling_result.optimal_tokens.magnitude:.2e}",
|
|
Tokens_per_parameter=f"{scaling_result.tokens_per_parameter:.1f}",
|
|
Chinchilla_ratio=f"{'OVER' if scaling_result.tokens_per_parameter > 20 else 'UNDER'}-trained")
|
|
```
|
|
|
|
If the tokens-per-parameter ratio is significantly above or below 20, the training
|
|
budget is not optimally allocated. Over-training wastes compute; under-training wastes
|
|
model capacity.
|
|
|
|
So far, everything looks manageable: compute-bound GPUs, adequate data pipeline,
|
|
reasonable training budget. If we throw 512 GPUs at this, we should scale linearly,
|
|
right? The fleet-level analysis reveals what single-node reasoning misses.
|
|
|
|
---
|
|
|
|
## 5. Fleet (Walls 14--16): Communication and Reliability
|
|
|
|
The distributed solver models AllReduce overhead and pipeline bubbles.
|
|
The reliability solver computes cluster MTBF and optimal checkpoint intervals.
|
|
|
|
```{python}
|
|
from mlsysim.solvers import DistributedModel, ReliabilityModel
|
|
|
|
# 3D parallelism: TP=8 (within node), PP=1, DP=64
|
|
dist_solver = DistributedModel()
|
|
dist_result = dist_solver.solve(
|
|
model=model, fleet=fleet,
|
|
batch_size=2048, precision="fp16",
|
|
tp_size=8, pp_size=1,
|
|
overlap_comm=True, seq_len=2048
|
|
)
|
|
|
|
banner("Domain: Fleet (Walls 14-16)")
|
|
info(Scaling_efficiency=f"{dist_result.scaling_efficiency:.2%}",
|
|
Step_latency=dist_result.step_latency_total.to('ms'),
|
|
DP_comm_latency=dist_result.dp_communication_latency.to('ms'),
|
|
TP_comm_latency=dist_result.tp_communication_latency.to('ms'),
|
|
Bubble_fraction=f"{dist_result.bubble_fraction:.2%}")
|
|
```
|
|
|
|
```{python}
|
|
# Reliability: 30-day training job
|
|
rel_solver = ReliabilityModel()
|
|
rel_result = rel_solver.solve(
|
|
fleet=fleet,
|
|
job_duration_hours=30*24,
|
|
checkpoint_time_s=120
|
|
)
|
|
|
|
info(Fleet_MTBF=rel_result.fleet_mtbf.to('hour'),
|
|
Failure_probability=f"{rel_result.failure_probability:.2%}",
|
|
Expected_failures=f"{rel_result.expected_failures:.1f}",
|
|
Optimal_ckpt_interval=rel_result.optimal_checkpoint_interval.to('minute'))
|
|
```
|
|
|
|
At 512 GPUs, the cluster MTBF shrinks significantly. Checkpoint overhead becomes a
|
|
non-trivial fraction of wall-clock time --- this is the "hidden cost" that single-node
|
|
analysis misses entirely.
|
|
|
|
The reliability analysis tells us HOW OFTEN the cluster fails. But failures cost money ---
|
|
and so does the energy to keep 512 GPUs running for 30 days. The operational domain
|
|
quantifies these costs.
|
|
|
|
---
|
|
|
|
## 6. Ops (Walls 17--20): TCO, Energy, Carbon, Water
|
|
|
|
The economics solver combines CapEx, OpEx, and sustainability into a single financial model.
|
|
|
|
```{python}
|
|
from mlsysim.solvers import EconomicsModel, SustainabilityModel
|
|
|
|
# 30-day training run
|
|
econ_solver = EconomicsModel()
|
|
econ_result = econ_solver.solve(
|
|
fleet=fleet,
|
|
duration_days=30,
|
|
grid=Grids.Quebec,
|
|
mfu=0.4
|
|
)
|
|
|
|
banner("Domain: Ops (Walls 17-20)")
|
|
info(CapEx=f"${econ_result.capex_usd:,.0f}",
|
|
OpEx_energy=f"${econ_result.opex_energy_usd:,.0f}",
|
|
OpEx_maintenance=f"${econ_result.opex_maintenance_usd:,.0f}",
|
|
Total_TCO=f"${econ_result.tco_usd:,.0f}")
|
|
```
|
|
|
|
```{python}
|
|
sust_solver = SustainabilityModel()
|
|
sust_result = sust_solver.solve(
|
|
fleet=fleet,
|
|
duration_days=30,
|
|
datacenter=Grids.Quebec,
|
|
mfu=0.4
|
|
)
|
|
|
|
info(IT_Energy=sust_result.it_energy_kwh.to('MWh'),
|
|
Total_Energy_PUE=sust_result.total_energy_kwh.to('MWh'),
|
|
Carbon_footprint=f"{sust_result.carbon_footprint_kg:.0f} kg CO2",
|
|
Water_usage=f"{sust_result.water_usage_liters:.0f} liters",
|
|
PUE=sust_result.pue,
|
|
Region=sust_result.region_name)
|
|
```
|
|
|
|
Quebec's hydroelectric grid makes this one of the lowest-carbon training locations in the
|
|
world. The same run in Poland (coal-heavy grid) would produce dramatically more CO2 ---
|
|
infrastructure geography is a first-class engineering variable.
|
|
|
|
---
|
|
|
|
## 7. Analysis (Walls 21--22): Sensitivity and Synthesis
|
|
|
|
Finally, confirm the binding constraint and derive minimum hardware for a 14-day completion target.
|
|
|
|
```{python}
|
|
from mlsysim.solvers import SensitivitySolver, SynthesisSolver
|
|
|
|
# Sensitivity: confirm compute is the binding constraint for training
|
|
sens_solver = SensitivitySolver()
|
|
sens_result = sens_solver.solve(
|
|
model=model, hardware=h100, precision="fp16"
|
|
)
|
|
|
|
banner("Domain: Analysis (Walls 21-22)")
|
|
info(Binding_constraint=sens_result.binding_constraint)
|
|
|
|
sens_rows = [[param, f"{val:+.4f}"] for param, val in sens_result.sensitivities.items()]
|
|
table(["Parameter", "Sensitivity"], sens_rows)
|
|
```
|
|
|
|
```{python}
|
|
# Synthesis: what per-GPU step latency is needed to finish in 14 days?
|
|
# Total training FLOPs / (N_GPUs * MFU * peak_FLOPS) = wall_clock_seconds
|
|
target_days = 14
|
|
target_seconds = target_days * 86400
|
|
# Per-GPU step target: total_steps * step_latency = target_seconds
|
|
# Approximate: we need each step to complete within a target latency
|
|
synth_solver = SynthesisSolver()
|
|
synth_result = synth_solver.solve(
|
|
model=model,
|
|
target_latency=Q_("200 ms"), # per-GPU training step target
|
|
precision="fp16"
|
|
)
|
|
|
|
info("Synthesis (200ms per-GPU training step target)",
|
|
Required_BW=synth_result.required_bw.to('TB/s'),
|
|
Required_FLOPS=synth_result.required_flops.to('TFLOPs/s'),
|
|
Required_memory=synth_result.required_memory.to('GB'))
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Summary Table: The Complete Picture
|
|
|
|
We have now traced a single workload through all six domains. Each solver answered one
|
|
question in isolation. But the systems engineer's job is synthesis: seeing the complete
|
|
picture at once. The table below is that picture --- and its most important property is
|
|
that no single row captures the full story.
|
|
|
|
```{python}
|
|
mtbf_hours = rel_result.fleet_mtbf.to('hour').magnitude
|
|
summary_rows = [
|
|
["Node", "SingleNodeModel", f"Bottleneck: {node_result.bottleneck}", "Wall 1: Compute"],
|
|
["Data", "DataModel", f"Util: {data_result.utilization:.0%}", "Not binding"],
|
|
["Algorithm", "ScalingModel", f"Tok/param: {scaling_result.tokens_per_parameter:.0f}","Wall 11"],
|
|
["Fleet", "DistributedModel", f"Efficiency: {dist_result.scaling_efficiency:.0%}", "Wall 14: Comm"],
|
|
["Fleet", "ReliabilityModel", f"MTBF: {mtbf_hours:.0f}h", "Wall 19: Ckpt"],
|
|
["Ops", "EconomicsModel", f"TCO: ${econ_result.tco_usd:,.0f}", "Wall 17: Cost"],
|
|
["Ops", "SustainabilityModel", f"CO2: {sust_result.carbon_footprint_kg:.0f} kg", "Wall 18: Energy"],
|
|
["Analysis", "SensitivitySolver", f"Binding: {sens_result.binding_constraint}", "Wall 21"],
|
|
]
|
|
|
|
table(["Domain", "Solver", "Key Metric", "Binding Wall"], summary_rows, "<<>>")
|
|
```
|
|
|
|
::: {.callout-important}
|
|
## Key Insight
|
|
|
|
**No single solver captures the full picture --- the systems view emerges from composition.**
|
|
This end-to-end trace exercises 12 of 22 walls through a single model. The per-GPU binding
|
|
constraint is compute (Wall 1), but the **hidden costs** only appear at fleet scale:
|
|
checkpoint overhead (Wall 19) consumes wall-clock time proportional to the MTBF-driven
|
|
checkpoint frequency, and infrastructure geography (Quebec vs. Poland) can change the
|
|
carbon footprint by 40x (as [Tutorial 7](07_geography.qmd) demonstrated). A complete
|
|
systems analysis is not one solver run --- it is the composition of all six domains.
|
|
:::
|
|
|
|
---
|
|
|
|
## Your Turn
|
|
|
|
::: {.callout-caution}
|
|
## Exercises
|
|
|
|
**Exercise 1: Predict before you compute.**
|
|
What if you train in Poland instead of Quebec? Before running code, predict how the
|
|
TCO and carbon footprint will change. (Hint: Poland's grid is coal-heavy with ~800 g
|
|
CO2/kWh vs. Quebec's ~20 g CO2/kWh, and Poland has a higher PUE.) Then re-run the
|
|
economics and sustainability solvers with `Grids.Poland` and compare. How close was
|
|
your prediction?
|
|
|
|
**Exercise 2: Double the cluster.**
|
|
Scale the fleet to 1024 GPUs (128 nodes). Re-run the distributed solver and reliability
|
|
solver. Does scaling efficiency hold? How does the MTBF change? At what cluster size does
|
|
the checkpoint overhead exceed 5% of wall-clock time?
|
|
|
|
**Exercise 3: Minimum viable cluster.**
|
|
What is the minimum cluster size to complete Llama-3 70B training in 14 days? Use the
|
|
scaling result to determine the required total FLOPS, then work backward to find the
|
|
number of H100 GPUs needed at 40% MFU. Verify with the distributed solver that the
|
|
communication overhead is acceptable at that scale.
|
|
|
|
**Exercise 4: Propose a design change.**
|
|
Using the full-stack analysis, identify the single highest-leverage change — hardware
|
|
upgrade, parallelism strategy, region change, or precision change — that would reduce
|
|
TCO by at least 20%. Re-run the relevant solvers with your proposed change and compute
|
|
the new TCO. *Write one paragraph justifying why this change has the largest impact,
|
|
referencing at least two domains from the summary table.*
|
|
|
|
**Self-check:** If the fleet MTBF is 4 hours and each checkpoint takes 2 minutes, what
|
|
fraction of wall-clock time is spent checkpointing? (Use the Young-Daly formula:
|
|
optimal interval = sqrt(2 * delta * MTBF).)
|
|
:::
|
|
|
|
---
|
|
|
|
## Key Takeaways
|
|
|
|
::: {.callout-tip}
|
|
## Summary
|
|
|
|
- **Composition is the method**: no single solver spans all six taxonomy domains; the
|
|
systems view emerges only from composing 6+ solvers
|
|
- **Compute binds at the node level**, but checkpoint overhead and communication are the
|
|
hidden costs at fleet scale
|
|
- **Infrastructure geography matters**: Quebec vs. Poland can change carbon footprint by
|
|
40x and TCO by 20--30%
|
|
- **The summary table** is the deliverable: one row per domain, solver, key metric, and
|
|
binding wall
|
|
- **12 of 22 walls** are exercised through a single model-fleet pair --- this is what a
|
|
complete analysis looks like
|
|
:::
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
- **[Sensitivity Analysis](09_sensitivity.qmd)** --- Dive deeper into the Analysis domain solvers
|
|
- **[GPU vs. Wafer-Scale](10_gpu_vs_wafer.qmd)** --- See how architecture shifts the binding wall
|
|
- **[Geography of AI](07_geography.qmd)** --- Explore how datacenter location changes sustainability
|
|
- **[The \$9 Million GPU](08_nine_million_dollar.qmd)** --- Deep dive into TCO modeling
|