120 lines
3.8 KiB
Python
120 lines
3.8 KiB
Python
"""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()
|