chore: fix formatting of inline math expressions

This commit is contained in:
Vijay Janapa Reddi
2026-03-08 10:23:05 -04:00
parent 02b46fec00
commit 42bd4e2a9c
3 changed files with 211 additions and 104 deletions

View File

@@ -2056,7 +2056,7 @@ from mlsysim.fmt import check
class CompressionPayback: class CompressionPayback:
# ┌── 1. LOAD ────────────────────────────────────────── # ┌── 1. LOAD ──────────────────────────────────────────
t_comm_ms = 40 # Original FP32 AllReduce time t_comm_ms = 40 # Original FP32 AllReduce time
ratio = 8 # 1-bit quantization (32x compression, but with protocol overhead) ratio = 8 # 1-bit quantization (32\times compression, but with protocol overhead)
t_overhead_ms = 2 # Compute time to quantize/dequantize t_overhead_ms = 2 # Compute time to quantize/dequantize
# ┌── 2. EXECUTE ─────────────────────────────────────── # ┌── 2. EXECUTE ───────────────────────────────────────
t_compressed_comm = t_comm_ms / ratio t_compressed_comm = t_comm_ms / ratio

View File

@@ -902,7 +902,7 @@ A fat-tree built from switches with **radix**[^fn-switch-radix-density] $k$ supp
# │ # │
# │ Goal: Quantify the synchronization slowdown when moving from a # │ Goal: Quantify the synchronization slowdown when moving from a
# │ non-blocking (1:1) to an oversubscribed (4:1) network. # │ non-blocking (1:1) to an oversubscribed (4:1) network.
# │ Show: slowdown_factor ≈ 4x — inline in notebook result. # │ Show: slowdown_factor ≈ 4\times — inline in notebook result.
# │ How: T = M / (Bisect_BW); Bisect_BW = N * Beta / Oversub_Ratio. # │ How: T = M / (Bisect_BW); Bisect_BW = N * Beta / Oversub_Ratio.
# │ # │
# │ Imports: mlsysim.book (check) # │ Imports: mlsysim.book (check)
@@ -1206,7 +1206,7 @@ class BisectionBottleneck:
waste_val = (1 - rel_throughput) * cluster_cost_m waste_val = (1 - rel_throughput) * cluster_cost_m
# ┌── 3. GUARD (Invariants) ─────────────────────────────────────────── # ┌── 3. GUARD (Invariants) ───────────────────────────────────────────
check(time_b_s == time_a_s * 4, "Scenario B must be 4x slower than Scenario A") check(time_b_s == time_a_s * 4, "Scenario B must be 4\times slower than Scenario A")
check(waste_val > 10, f"Waste should be significant, got ${waste_val:.1f}M") check(waste_val > 10, f"Waste should be significant, got ${waste_val:.1f}M")
# ┌── 4. OUTPUT (Formatting) ────────────────────────────────────────────── # ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────

View File

@@ -1,4 +1,4 @@
# viz.py # viz/plots.py
# Centralized Visualization Style for MLSys Book # Centralized Visualization Style for MLSys Book
# Ensures all generated figures across Vol 1 & 2 share a consistent, # Ensures all generated figures across Vol 1 & 2 share a consistent,
# MIT Press-ready aesthetic. # MIT Press-ready aesthetic.
@@ -6,11 +6,11 @@
try: try:
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
_viz_available = True _matplotlib_available = True
except ImportError: except ImportError:
plt = None plt = None
np = None np = None
_viz_available = False _matplotlib_available = False
# --- Brand & Book Palette --- # --- Brand & Book Palette ---
COLORS = { COLORS = {
@@ -30,128 +30,235 @@ COLORS = {
def set_book_style(): def set_book_style():
"""Applies the global matplotlib style configuration.""" """Applies the global matplotlib style configuration."""
if not _viz_available: if not _matplotlib_available:
raise ImportError( raise ImportError("matplotlib is required for plot generation.")
"matplotlib and numpy are required for plot generation. "
"Install them with: pip install matplotlib numpy"
)
plt.rcParams.update({ plt.rcParams.update({
'font.family': 'sans-serif', 'font.family': 'sans-serif',
'font.sans-serif': [ 'font.sans-serif': ['Helvetica', 'Helvetica Neue', 'Arial', 'DejaVu Sans'],
'Helvetica', # macOS native
'Helvetica Neue', # macOS modern variant
'Nimbus Sans L', # Free Helvetica clone (TeX/Linux)
'TeX Gyre Heros', # Free Helvetica clone (TeX)
'Arial', # Windows fallback
'DejaVu Sans', # Universal last resort
],
'font.size': 10, 'font.size': 10,
'text.color': COLORS['primary'], 'text.color': COLORS['primary'],
'axes.labelsize': 11, 'axes.labelsize': 11,
'axes.labelcolor': COLORS['primary'],
'axes.titlesize': 12, 'axes.titlesize': 12,
'axes.titleweight': 'bold', 'axes.titleweight': 'bold',
'axes.edgecolor': COLORS['primary'],
'axes.linewidth': 0.8,
'axes.spines.top': False,
'axes.spines.right': False,
'xtick.labelsize': 9,
'ytick.labelsize': 9,
'xtick.color': COLORS['primary'],
'ytick.color': COLORS['primary'],
'axes.grid': True, 'axes.grid': True,
'grid.color': COLORS['grid'], 'grid.color': COLORS['grid'],
'grid.alpha': 0.4, 'grid.alpha': 0.4,
'grid.linestyle': '--', 'grid.linestyle': '--',
'grid.linewidth': 0.6,
'legend.fontsize': 9,
'legend.frameon': False,
'legend.title_fontsize': 10,
'lines.linewidth': 2.0,
'lines.markersize': 7,
'figure.dpi': 300, 'figure.dpi': 300,
'savefig.bbox': 'tight', 'savefig.bbox': 'tight'
'savefig.pad_inches': 0.1,
'figure.figsize': (8, 5),
'figure.autolayout': True
}) })
# --- Lightweight helpers --- def setup_plot(figsize=(8, 5)):
def setup_plot(figsize=None):
"""One-line plot setup for QMD blocks.""" """One-line plot setup for QMD blocks."""
set_book_style() set_book_style()
fig, ax = plt.subplots(figsize=figsize) fig, ax = plt.subplots(figsize=figsize)
return fig, ax, COLORS, plt return fig, ax, COLORS, plt
def bar_compare(labels, values, title, ylabel, goal_line=None, colors=None): def plot_roofline(hardware_node, workloads=None):
"""Creates a standard comparison bar chart with value labels.""" """
fig, ax, COLORS, plt = setup_plot() Plots a publication-quality Roofline Model for a given HardwareNode.
if colors is None:
colors = [COLORS['BlueLine'], COLORS['GreenLine'], COLORS['OrangeLine'], COLORS['VioletLine']]
bars = ax.bar(labels, values, color=colors[:len(labels)], alpha=0.8, edgecolor='white', linewidth=1)
ax.set_title(title)
ax.set_ylabel(ylabel)
# Add value labels
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + (max(values)*0.02),
f'{height:.2f}', ha='center', va='bottom', fontsize=9, fontweight='bold')
if goal_line: Features:
ax.axhline(y=goal_line, color=COLORS['RedLine'], linestyle='--', linewidth=1.5, label='Constraint') - Ridge point annotated with numeric value
ax.legend() - Memory-bound and compute-bound regions shaded and labeled
- Memory bandwidth ceiling (diagonal) and compute ceiling (flat)
return fig - Workloads plotted with bottleneck classification
"""
# 1. PARAMETERS
peak_flops = hardware_node.compute.peak_flops.to("TFLOPs/s").magnitude
peak_bw = hardware_node.memory.bandwidth.to("GB/s").magnitude
ridge_point = peak_flops / (peak_bw / 1000) # FLOP/Byte
def plot_latency_breakdown(profile, title="Latency Breakdown"): # 2. AXIS RANGE
"""Plots a stacked bar showing Compute vs Memory vs Overhead.""" x_min, x_max = 0.1, 10000
fig, ax, COLORS, plt = setup_plot(figsize=(6, 5)) x = np.logspace(np.log10(x_min), np.log10(x_max), 500)
comp = profile.latency_compute.m_as('ms')
mem = profile.latency_memory.m_as('ms')
ovh = profile.latency_overhead.m_as('ms')
labels = ['Latency']
ax.bar(labels, [comp], label='Compute', color=COLORS['BlueLine'], alpha=0.8)
ax.bar(labels, [mem], bottom=[comp], label='Memory', color=COLORS['OrangeLine'], alpha=0.8)
ax.bar(labels, [ovh], bottom=[comp+mem], label='Overhead', color=COLORS['RedLine'], alpha=0.8)
ax.set_title(title)
ax.set_ylabel("Time (ms)")
ax.legend(loc='upper right')
total = comp + mem + ovh
ax.text(0, total/2, f"Total: {total:.2f} ms", ha='center', fontweight='bold', bbox=dict(facecolor='white', alpha=0.8))
return fig
def plot_roofline(profile, title="System Roofline Analysis"): # 3. ROOFLINE CURVES
"""Plots the hardware roofline and the current workload point.""" y_mem = peak_bw * x / 1000 # BW * AI, converted to TFLOP/s
fig, ax, COLORS, plt = setup_plot() y_compute = np.full_like(x, peak_flops)
y_roof = np.minimum(y_mem, y_compute)
# 4. PLOT
fig, ax, colors, _ = setup_plot(figsize=(9, 5.5))
# Shaded regions
mem_mask = x <= ridge_point
comp_mask = x >= ridge_point
ax.fill_between(
x[mem_mask],
y_roof[mem_mask] * 0.001,
y_roof[mem_mask],
color=colors["OrangeL"],
alpha=0.5,
label="Memory-bound region",
)
ax.fill_between(
x[comp_mask],
y_roof[comp_mask] * 0.001,
y_roof[comp_mask],
color=colors["BlueFill"],
alpha=0.5,
label="Compute-bound region",
)
# Roofline line
ax.loglog(
x,
y_roof,
color=colors["BlueLine"],
linewidth=2.5,
zorder=5,
)
# Memory bandwidth ceiling label (on the slope)
slope_x = ridge_point * 0.08
slope_y = peak_bw * slope_x / 1000
ax.text(
slope_x,
slope_y * 1.6,
f"BW ceiling: {peak_bw:.0f} GB/s",
color=colors["OrangeLine"],
fontsize=8.5,
fontweight="bold",
rotation=38,
ha="center",
va="bottom",
)
# Compute ceiling label (on the flat)
ax.text(
ridge_point * 8,
peak_flops * 1.12,
f"Compute ceiling: {peak_flops:.0f} TFLOP/s",
color=colors["BlueLine"],
fontsize=8.5,
fontweight="bold",
ha="center",
va="bottom",
)
# Ridge point
ax.plot(
ridge_point,
peak_flops,
"D",
color=colors["crimson"],
markersize=9,
zorder=10,
)
ax.annotate(
f"Ridge Point\n{ridge_point:.1f} FLOP/Byte",
xy=(ridge_point, peak_flops),
xytext=(ridge_point * 3, peak_flops * 0.35),
fontsize=8.5,
fontweight="bold",
color=colors["crimson"],
ha="center",
arrowprops=dict(
arrowstyle="->",
color=colors["crimson"],
lw=1.2,
),
)
# Vertical dashed line at ridge point
ax.axvline(
ridge_point,
color=colors["crimson"],
linestyle=":",
linewidth=0.8,
alpha=0.5,
)
# Region labels
ax.text(
x_min * 1.5,
peak_flops * 0.6,
"MEMORY\nBOUND",
color=colors["OrangeLine"],
fontsize=11,
fontweight="bold",
alpha=0.25,
ha="left",
va="center",
)
ax.text(
x_max * 0.4,
peak_flops * 0.6,
"COMPUTE\nBOUND",
color=colors["BlueLine"],
fontsize=11,
fontweight="bold",
alpha=0.25,
ha="right",
va="center",
)
# Plot workloads
if workloads:
from ..core.engine import Engine
workload_colors = [
colors["crimson"],
colors["GreenLine"],
colors["VioletLine"],
colors["BrownLine"],
]
for i, model in enumerate(workloads):
profile = Engine.solve(model, hardware_node, efficiency=1.0)
ai = profile.arithmetic_intensity.magnitude
perf = min(peak_bw * ai / 1000, peak_flops)
c = workload_colors[i % len(workload_colors)]
bound = "memory" if ai < ridge_point else "compute"
ax.plot(ai, perf, "o", color=c, markersize=9, zorder=10)
ax.annotate(
f"{model.name}\n({bound}-bound)",
xy=(ai, perf),
xytext=(ai * 0.3, perf * 0.4),
fontsize=8,
fontweight="bold",
color=c,
ha="center",
arrowprops=dict(arrowstyle="->", color=c, lw=1),
)
ax.set_xlabel("Arithmetic Intensity (FLOP/Byte)")
ax.set_ylabel("Performance (TFLOP/s)")
ax.set_title(f"Roofline: {hardware_node.name}")
ax.set_xlim(x_min, x_max)
ax.set_ylim(peak_flops * 0.001, peak_flops * 2)
ax.legend(loc="lower right", fontsize=8, framealpha=0.9)
return fig, ax
def plot_evaluation_scorecard(evaluation):
"""
Visualizes the supply-vs-demand scorecard for a SystemEvaluation.
Follows the LEGO-style visualization pattern.
"""
# 1. PARAMETERS
from ..core.constants import Q_
l1_metrics = evaluation.feasibility.metrics
l2_metrics = evaluation.performance.metrics
max_perf = profile.peak_flops_actual.m_as('GFLOPs/s') # 2. CALCULATION
max_bw = profile.peak_bw_actual.m_as('GB/s') l1_ratio = (l1_metrics['weight_size'] / l1_metrics['capacity']).to_base_units().magnitude
ridge_point = max_perf / max_bw l2_ratio = (l2_metrics['latency'] / l2_metrics.get('sla_latency', Q_("1000 ms"))).to_base_units().magnitude
ai = profile.arithmetic_intensity.m_as('flop/byte') levels = ['Memory (RAM)', 'Latency (SLA)']
achieved_perf = min(ai * max_bw, max_perf) ratios = [l1_ratio, l2_ratio]
# X axis: Arithmetic Intensity # 3. OUTPUT (Visualization)
x = np.logspace(np.log10(ridge_point/100), np.log10(ridge_point*100), 100) fig, ax, colors, plt = setup_plot(figsize=(8, 4))
y = np.minimum(x * max_bw, max_perf) bar_colors = [colors['RedLine'] if r > 1.0 else colors['GreenLine'] for r in ratios]
bars = ax.barh(levels, ratios, color=bar_colors, alpha=0.7, edgecolor='black')
ax.plot(x, y, color=COLORS['primary'], linewidth=2, label='Roofline') ax.axvline(1.0, color=colors['primary'], linestyle='--', linewidth=2, label='Physical Limit / SLA')
ax.scatter([ai], [achieved_perf], color=COLORS['RedLine'], s=100, zorder=5, label=f'Workload (AI={ai:.1f})')
ax.set_xscale('log') for i, (bar, ratio) in enumerate(zip(bars, ratios)):
ax.set_yscale('log') ax.text(bar.get_width() + 0.05, bar.get_y() + bar.get_height()/2, f"{ratio:.1%}",
ax.set_xlabel('Arithmetic Intensity (FLOP/Byte)') va='center', fontweight='bold', color=bar_colors[i])
ax.set_ylabel('Performance (GFLOPs/s)')
ax.set_title(title) ax.set_xlim(0, max(max(ratios) + 0.5, 1.5))
ax.grid(True, which="both", ls="-", alpha=0.2) ax.set_xlabel('Resource Utilization (Demand / Supply)')
ax.legend() ax.set_title(f'System Evaluation: {evaluation.scenario_name}')
return fig, ax
return fig