Skip to content

Commit 3ee246a

Browse files
committed
feat(labs): integrate mlsysim engine and pedagogical iron law nuggets into Top 5 core labs
1 parent 8f5bf9a commit 3ee246a

5 files changed

Lines changed: 425 additions & 296 deletions

File tree

labs/vol1/lab_02_ml_systems.py

Lines changed: 103 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -40,35 +40,41 @@
4040

4141

4242
@app.cell
43-
def _():
43+
async def _():
4444
import marimo as mo
4545
import sys
4646
from pathlib import Path
4747
import plotly.graph_objects as go
4848
import numpy as np
4949
import math
5050

51-
_root = Path(__file__).resolve().parents[2]
52-
if str(_root) not in sys.path:
53-
sys.path.insert(0, str(_root))
51+
# WASM bootstrap: install mlsysim from hosted wheel when running in browser
52+
if sys.platform == "emscripten":
53+
import micropip
54+
await micropip.install("https://mlsysbook.ai/labs/wheels/mlsysim-0.1.0-py3-none-any.whl")
55+
elif "mlsysim" not in sys.modules:
56+
_root = Path(__file__).resolve().parents[2]
57+
if str(_root) not in sys.path:
58+
sys.path.insert(0, str(_root))
5459

55-
from labs.core.state import DesignLedger
56-
from labs.core.style import COLORS, LAB_CSS, apply_plotly_theme
60+
from mlsysim.labs.state import DesignLedger
61+
from mlsysim.labs.style import COLORS, LAB_CSS, apply_plotly_theme
62+
import mlsysim
5763

58-
# ── Hardware constants (NVIDIA published specs + chapter equations) ──
64+
# ── Hardware constants (extracted from mlsysim for reference) ──
5965
# H100 SXM5: https://www.nvidia.com/en-us/data-center/h100/
60-
H100_BW_GBS = 3350 # GB/s HBM3e memory bandwidth
61-
H100_TFLOPS_FP16 = 989 # TFLOPS FP16 dense tensor core — NVIDIA H100 SXM5 spec
62-
H100_RAM_GB = 80 # GB HBM capacity
66+
H100_BW_GBS = mlsysim.Hardware.Cloud.H100.memory.bandwidth.m_as("GB/s")
67+
H100_TFLOPS_FP16 = mlsysim.Hardware.Cloud.H100.compute.peak_flops.m_as("TFLOPs/s")
68+
H100_RAM_GB = mlsysim.Hardware.Cloud.H100.memory.capacity.m_as("GB")
6369

6470
# A100 SXM4: prior-generation reference for CTO upgrade scenario
65-
A100_BW_GBS = 2000 # GB/s HBM2e (actual: 1935, rounded up for scenario)
66-
A100_TFLOPS_FP16 = 312 # TFLOPS FP16 Tensor Core peak
71+
A100_BW_GBS = mlsysim.Hardware.Cloud.A100.memory.bandwidth.m_as("GB/s")
72+
A100_TFLOPS_FP16 = mlsysim.Hardware.Cloud.A100.compute.peak_flops.m_as("TFLOPs/s")
6773

6874
# Jetson Orin NX: NVIDIA embedded inference platform
69-
ORIN_BW_GBS = 102 # GB/s LPDDR5 memory bandwidth
70-
ORIN_TOPS = 100 # TOPS INT8 equivalent
71-
ORIN_RAM_GB = 16 # GB
75+
ORIN_BW_GBS = mlsysim.Hardware.Edge.Jetson.memory.bandwidth.m_as("GB/s")
76+
ORIN_TOPS = mlsysim.Hardware.Edge.Jetson.compute.precision_flops.get("int8", mlsysim.Hardware.Edge.Jetson.compute.peak_flops).m_as("TFLOPs/s")
77+
ORIN_RAM_GB = mlsysim.Hardware.Edge.Jetson.memory.capacity.m_as("GB")
7278

7379
# Speed of light in fiber — from @eq-latency-physics (ml_systems.qmd):
7480
# Latency_min = 2 * Distance / (0.67 * c) ≈ 2 * Distance / 200,000 km/s
@@ -83,7 +89,7 @@ def _():
8389

8490
return (
8591
mo, ledger, COLORS, LAB_CSS, apply_plotly_theme,
86-
go, np, math,
92+
go, np, math, mlsysim,
8793
H100_BW_GBS, H100_TFLOPS_FP16, H100_RAM_GB,
8894
A100_BW_GBS, A100_TFLOPS_FP16,
8995
ORIN_BW_GBS, ORIN_TOPS, ORIN_RAM_GB,
@@ -449,44 +455,58 @@ def _(
449455
_hw_new = "Jetson Orin NX"
450456
_hw_old = "Prior Orin (20% compute)"
451457

452-
# ── Iron Law calculation ──────────────────────────────────────────────────
453-
# Model parameters for a representative transformer layer inference:
454-
# D = 2 GB (weights loaded per inference for a ~1B param slice in FP16)
455-
# O = AI * D (by definition of Arithmetic Intensity)
456-
# Source: @sec-ml-systems-architectural-anchor (Memory Wall)
458+
# ── Flight Simulator: MLSys·im Engine Evaluation ──────────────────────────
459+
import mlsysim
460+
457461
_AI = act1_ai_slider.value # FLOPs/Byte — user control
458462
_D_GB = 2.0 # GB data moved per inference call
459463
_D_bytes = _D_GB * 1e9 # bytes
460-
461-
# Operations = AI × data
462464
_O_flops = _AI * _D_bytes # FLOPs
463465

464-
# ── New hardware (H100 or Orin) ──────────────────────────────────────────
465-
_bw_new_bps = _bw_new * 1e9 # bytes/s
466-
_r_new_fps = _r_new * 1e12 # FLOPs/s
466+
# Construct the workload for the Engine
467+
mock_workload = mlsysim.Models.CNNWorkload(
468+
name=f"Layer_Slice_AI_{_AI}",
469+
architecture="Generic",
470+
parameters=mlsysim.Q_(_D_bytes / 2, "count"), # Assume FP16 (2 bytes/param)
471+
inference_flops=mlsysim.Q_(_O_flops, "flop")
472+
)
467473

468-
_t_mem_new_ms = (_D_bytes / _bw_new_bps) * 1000 # ms
469-
_t_comp_new_ms = (_O_flops / _r_new_fps) * 1000 # ms
470-
_t_ovh_new_ms = OVERHEAD_MS # ms (fixed)
471-
_t_total_new = _t_mem_new_ms + _t_comp_new_ms + _t_ovh_new_ms
474+
# Resolve hardware objects
475+
_hw_obj_new = mlsysim.Hardware.Cloud.H100 if _ctx == "cloud" else mlsysim.Hardware.Edge.Jetson
476+
_hw_obj_old = mlsysim.Hardware.Cloud.A100 if _ctx == "cloud" else mlsysim.Hardware.Tiny.ESP32 # Using ESP32 as a tiny baseline
477+
478+
# Override dispatcher tax to match the fixed OVERHEAD_MS for the lab
479+
_hw_obj_new.dispatch_tax = mlsysim.Q_(OVERHEAD_MS, "ms")
480+
_hw_obj_old.dispatch_tax = mlsysim.Q_(OVERHEAD_MS, "ms")
481+
482+
# Evaluate New Hardware
483+
profile_new = mlsysim.Engine.solve(
484+
model=mock_workload, hardware=_hw_obj_new, batch_size=1, precision="fp16", efficiency=1.0
485+
)
486+
# Evaluate Old Hardware
487+
profile_old = mlsysim.Engine.solve(
488+
model=mock_workload, hardware=_hw_obj_old, batch_size=1, precision="fp16", efficiency=1.0
489+
)
472490

473-
# ── Old hardware (A100 or prior Orin) ───────────────────────────────────
474-
_bw_old_bps = _bw_old * 1e9
475-
_r_old_fps = _r_old * 1e12
491+
# Extract component times
492+
_t_mem_new_ms = profile_new.latency_memory.m_as("ms")
493+
_t_comp_new_ms = profile_new.latency_compute.m_as("ms")
494+
_t_ovh_new_ms = profile_new.latency_overhead.m_as("ms")
495+
# To match the classic strict addition in the textbook's first introduction:
496+
_t_total_new = _t_mem_new_ms + _t_comp_new_ms + _t_ovh_new_ms
476497

477-
_t_mem_old_ms = (_D_bytes / _bw_old_bps) * 1000
478-
_t_comp_old_ms = (_O_flops / _r_old_fps) * 1000
479-
_t_ovh_old_ms = OVERHEAD_MS
498+
_t_mem_old_ms = profile_old.latency_memory.m_as("ms")
499+
_t_comp_old_ms = profile_old.latency_compute.m_as("ms")
500+
_t_ovh_old_ms = profile_old.latency_overhead.m_as("ms")
480501
_t_total_old = _t_mem_old_ms + _t_comp_old_ms + _t_ovh_old_ms
481502

482503
# ── Ridge points ─────────────────────────────────────────────────────────
483-
# Ridge point = R / BW (FLOPs/Byte at which compute term = memory term)
484-
_ridge_new = (_r_new * 1e12) / (_bw_new * 1e9) # FLOPs/Byte
485-
_ridge_old = (_r_old * 1e12) / (_bw_old * 1e9)
504+
_ridge_new = _hw_obj_new.ridge_point().m_as("flop/byte")
505+
_ridge_old = _hw_obj_old.ridge_point().m_as("flop/byte")
486506

487507
# ── Bottleneck classification ─────────────────────────────────────────────
488-
_is_mem_bound_new = _AI < _ridge_new
489-
_bottleneck_new = "Memory-bound" if _is_mem_bound_new else "Compute-bound"
508+
_is_mem_bound_new = profile_new.bottleneck == "Memory"
509+
_bottleneck_new = profile_new.bottleneck + "-bound"
490510
_bottleneck_color = COLORS["RedLine"] if _is_mem_bound_new else COLORS["BlueLine"]
491511

492512
# ── Latency improvement ───────────────────────────────────────────────────
@@ -545,20 +565,36 @@ def _(
545565
apply_plotly_theme(_fig)
546566

547567
# ── Physics formula display ───────────────────────────────────────────────
548-
_formula_block = f"""
549-
**Iron Law — Live Calculation** (`AI = {_AI} FLOPs/Byte, D = {_D_GB:.1f} GB`)
550-
551-
```
552-
Memory D/BW = {_D_GB:.1f} GB / {_bw_new:,} GB/s = {_t_mem_new_ms:.3f} ms ← {_hw_new}
553-
Compute O/R = {_AI * _D_GB:.1f} GFLOPS / {_r_new:,} TFLOPS = {_t_comp_new_ms:.4f} ms
554-
Overhead L = {_t_ovh_new_ms:.1f} ms (fixed dispatch tax)
555-
─────────────────────────────────────────────────────────────
556-
Total T = {_t_total_new:.3f} ms (vs {_t_total_old:.3f} ms on {_hw_old})
557-
558-
Ridge Point = R / BW = {_ridge_new:,.0f} FLOPs/Byte ({_hw_new})
559-
AI = {_AI} FLOPs/Byte → {'BELOW ridge → Memory-bound' if _is_mem_bound_new else 'ABOVE ridge → Compute-bound'}
560-
```
561-
"""
568+
_accordion = mo.accordion({
569+
"⚙️ Under the Hood: How MLSys·im Calculates This": mo.md(f"""
570+
This "Flight Simulator" uses the exact same `mlsysim` physics engine as the textbook.
571+
Here is the code running in the background to calculate the Iron Law terms:
572+
573+
```python
574+
import mlsysim
575+
576+
# 1. Define the hardware and workload
577+
hw = mlsysim.Hardware.Cloud.H100 if _ctx == 'cloud' else mlsysim.Hardware.Edge.Jetson
578+
workload = mlsysim.Models.Generic(
579+
parameters=mlsysim.Q_({_D_bytes / 2}, "count"), # Assume FP16
580+
inference_flops=mlsysim.Q_({_O_flops}, "flop")
581+
)
582+
583+
# 2. Evaluate the theoretical performance
584+
profile = mlsysim.Engine.solve(
585+
model=workload,
586+
hardware=hw,
587+
batch_size=1,
588+
precision="fp16",
589+
efficiency=1.0
590+
)
591+
592+
print(f"Memory Term: {{profile.latency_memory}}")
593+
print(f"Compute Term: {{profile.latency_compute}}")
594+
print(f"Overhead Term: {{profile.latency_overhead}}")
595+
```
596+
""")
597+
})
562598

563599
# ── Metric cards ──────────────────────────────────────────────────────────
564600
_mem_pct = _t_mem_new_ms / _t_total_new * 100 if _t_total_new > 0 else 0
@@ -635,7 +671,7 @@ def _(
635671
),
636672
mo.as_html(_fig),
637673
mo.Html(_cards_html),
638-
mo.md(_formula_block),
674+
_accordion,
639675
])
640676
# Export bottleneck for downstream cells
641677
return (
@@ -1467,6 +1503,17 @@ def _(mo, COLORS):
14671503
text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 12px;">
14681504
Key Takeaways
14691505
</div>
1506+
1507+
<div style="background:linear-gradient(to right, #f8fafc, #f1f5f9); border-radius:8px; padding:20px; margin-bottom:24px; border-left:4px solid #8b5cf6;">
1508+
<div style="font-weight:800; font-size:1.1rem; color:#6d28d9; margin-bottom:8px;">💎 The Iron Law Nugget</div>
1509+
<div style="color:#334155; font-size:1rem; font-style:italic; line-height:1.6;">
1510+
"The Iron Law of ML Systems states that performance is bounded by the slowest of three things: Computation, Communication, or Memory. You cannot optimize a system without knowing which one is binding."
1511+
</div>
1512+
<div style="margin-top:12px; font-size:0.8rem; color:#64748b;">
1513+
<strong>Source:</strong> Formalized in <em>Reddi, V. J., et al. (2025). Machine Learning Systems. Chapter 2: ML Systems.</em> (Adapted from the fundamental limits defined in Patterson & Hennessy).
1514+
</div>
1515+
</div>
1516+
14701517
<div style="font-size: 0.92rem; color: {COLORS['Text']}; line-height: 1.75;">
14711518
<div style="margin-bottom: 10px;">
14721519
<strong>1. The Memory Wall dominates transformer inference at low Arithmetic Intensity.</strong>

0 commit comments

Comments
 (0)