feat(chart): GPU utilization paradox visualization
This commit is contained in:
119
src/charts/utilization_gap.py
Normal file
119
src/charts/utilization_gap.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""GPU Utilization Paradox Chart"""
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Circle
|
||||
import numpy as np
|
||||
from src.utils.styling import (
|
||||
get_theme, EXPORT_DPI, BUBBLE_ZONE, NORMAL_ZONE, WARNING_ZONE,
|
||||
GRAY_LIGHT, GRAY_DARK, GRAY_MEDIUM, BLACK, WHITE
|
||||
)
|
||||
from src.utils.export import save_chart_tight
|
||||
|
||||
|
||||
def plot_gpu_utilization() -> str:
|
||||
plt.rcParams.update(get_theme())
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LEFT PANEL: Horizontal bar comparison
|
||||
# ---------------------------------------------------------------------------
|
||||
categories = [
|
||||
"Total AI Infrastructure Spend (2025)",
|
||||
"Effective GPU Utilization (~5%)",
|
||||
"Industry Target (~65%)",
|
||||
"Human Workforce Utilization (~85%)",
|
||||
]
|
||||
# Normalize to percentages for visual comparison
|
||||
values = [100, 5, 65, 85]
|
||||
colors_bar = [GRAY_DARK, BUBBLE_ZONE, NORMAL_ZONE, WARNING_ZONE]
|
||||
|
||||
y_pos = np.arange(len(categories))
|
||||
bars = ax1.barh(y_pos, values, color=colors_bar, edgecolor="white", height=0.6)
|
||||
|
||||
ax1.set_yticks(y_pos)
|
||||
ax1.set_yticklabels(categories, fontsize=11)
|
||||
ax1.set_xlabel("Relative Percentage (%)", fontsize=12)
|
||||
ax1.set_title("GPU Utilization Paradox", fontsize=16, fontweight="bold")
|
||||
ax1.set_xlim(0, 110)
|
||||
ax1.grid(True, alpha=0.3, axis="x")
|
||||
|
||||
# Value labels on bars
|
||||
for bar, val in zip(bars, values):
|
||||
ax1.text(
|
||||
val + 1, bar.get_y() + bar.get_height() / 2,
|
||||
f"{val}%",
|
||||
va="center", fontsize=11, fontweight="bold",
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RIGHT PANEL: Donut chart
|
||||
# ---------------------------------------------------------------------------
|
||||
sizes = [5, 95] # Utilized vs Idle
|
||||
colors_donut = [BUBBLE_ZONE, GRAY_LIGHT]
|
||||
labels_donut = ["Utilized (5%)", "Idle (95%)"]
|
||||
|
||||
wedges, texts, autotexts = ax2.pie(
|
||||
sizes, colors=colors_donut, startangle=90,
|
||||
textprops={"fontsize": 10},
|
||||
autopct="", counterclock=False,
|
||||
)
|
||||
|
||||
# Inner circle for donut
|
||||
centre_circle = Circle((0, 0), 0.65, fc=WHITE)
|
||||
ax2.add_artist(centre_circle)
|
||||
|
||||
# Center text
|
||||
ax2.text(
|
||||
0, 0, "5%\nGPU\nUTIL", ha="center", va="center",
|
||||
fontsize=20, fontweight="bold", color=BUBBLE_ZONE,
|
||||
)
|
||||
|
||||
ax2.set_title("GPU Capacity Breakdown", fontsize=14, fontweight="bold")
|
||||
ax2.legend(wedges, labels_donut, loc="lower right", fontsize=10)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# FIGURE-LEVEL: Title, subtitle, callout, source
|
||||
# ---------------------------------------------------------------------------
|
||||
fig.suptitle(
|
||||
"The GPU Utilization Paradox",
|
||||
fontsize=22, fontweight="bold", y=0.98,
|
||||
)
|
||||
fig.text(
|
||||
0.5, 0.93,
|
||||
"\\$400B+ spent on AI infrastructure — ~5% average GPU utilization",
|
||||
ha="center", fontsize=13, color=GRAY_MEDIUM,
|
||||
)
|
||||
|
||||
# Bold callout
|
||||
fig.text(
|
||||
0.5, 0.02,
|
||||
"\\$295B+ spent | ~5% utilized | ~\\$280B wasted capacity",
|
||||
ha="center", fontsize=14, fontweight="bold",
|
||||
bbox=dict(
|
||||
boxstyle="round,pad=0.5",
|
||||
facecolor=GRAY_LIGHT,
|
||||
edgecolor=BUBBLE_ZONE,
|
||||
linewidth=2,
|
||||
),
|
||||
)
|
||||
|
||||
# Source note
|
||||
fig.text(
|
||||
0.5, 0.07,
|
||||
"Enterprise GPU utilization estimates from industry surveys (2024-2025)",
|
||||
ha="center", fontsize=9, color=GRAY_MEDIUM, style="italic",
|
||||
)
|
||||
|
||||
path = save_chart_tight(fig, "08_gpu_utilization.png")
|
||||
plt.close(fig)
|
||||
return path
|
||||
|
||||
|
||||
def main():
|
||||
path = plot_gpu_utilization()
|
||||
print(f"Chart saved: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user