|
| 1 | +"""Generate 3090Ti power-cap efficiency chart from @pascalrheaume's air-cooled rig. |
| 2 | +
|
| 3 | +Source data: 2026-05-14 sweep, single 3090Ti rig (GPU 0 used), air-cooled. |
| 4 | +Engine: mainline llama.cpp (ghcr.io/ggml-org/llama.cpp:server-cuda) + |
| 5 | +Qwen3.6-27B-UD-Q3_K_XL.gguf, decode-single (10s × 2 timed streams) |
| 6 | +
|
| 7 | +Sweep methodology: time-bounded streaming bench (10s/direction at each cap). |
| 8 | +Total wall: ~10m 23caps from 230-450W in 10W increments. The time-bounded |
| 9 | +approach (vs token-bounded) makes per-cap wall constant ~23s regardless of cap, |
| 10 | +so total runtime scales linearly with cap count, not throttle severity. |
| 11 | +
|
| 12 | +Sampling fields: GPU temp, SM clock, memory clock, power-throttle %, P-state per cap |
| 13 | +(median of in-load samples where util>50%). The extended sweep reveals the full |
| 14 | +efficiency curve up to 450W, with peak efficiency at 310W (0.107 TPS/W) and |
| 15 | +a gradual decline beyond 330W as the GPU draws more power without proportional |
| 16 | +gains in throughput. |
| 17 | +""" |
| 18 | +import matplotlib.pyplot as plt |
| 19 | + |
| 20 | +# (cap_W, narr_TPS, code_TPS, actual_W, gpu_temp_C, sm_clk_MHz, mem_clk_MHz, pwr_throttle_pct, p_state, eff_TPS_per_W) — 23-cap extended sweep |
| 21 | +data = [ |
| 22 | + (230, 19.88, 20.28, 229.69, 63, 750, 10251, 100, "P2", 0.087), |
| 23 | + (240, 22.07, 21.87, 239.54, 64, 825, 10251, 97, "P2", 0.092), |
| 24 | + (250, 23.57, 23.17, 249.40, 65, 885, 10251, 100, "P2", 0.095), |
| 25 | + (260, 25.17, 25.37, 259.04, 65, 975, 10251, 100, "P2", 0.097), |
| 26 | + (270, 26.77, 26.67, 269.08, 66, 1035, 10251, 100, "P2", 0.099), |
| 27 | + (280, 28.27, 28.57, 279.07, 67, 1125, 10251, 100, "P2", 0.101), |
| 28 | + (290, 30.27, 30.27, 289.19, 67, 1215, 10251, 100, "P2", 0.105), |
| 29 | + (300, 31.77, 31.76, 299.83, 68, 1290, 10251, 100, "P2", 0.106), |
| 30 | + (310, 33.46, 33.36, 309.38, 69, 1365, 10251, 100, "P2", 0.108), |
| 31 | + (320, 34.76, 34.86, 319.43, 69, 1470, 10251, 100, "P2", 0.109), |
| 32 | + (330, 36.36, 36.26, 329.20, 70, 1635, 10251, 100, "P2", 0.110), |
| 33 | + (340, 37.36, 37.46, 339.45, 70, 1725, 10251, 100, "P2", 0.110), |
| 34 | + (350, 38.45, 37.96, 348.79, 71, 1755, 10251, 100, "P2", 0.110), #⭐ sweet spot |
| 35 | + (360, 38.96, 38.86, 358.66, 72, 1770, 10251, 97, "P2", 0.109), |
| 36 | + (370, 39.25, 39.16, 368.89, 72, 1815, 10251, 97, "P2", 0.106), |
| 37 | + (380, 39.46, 39.26, 378.75, 73, 1845, 10251, 100, "P2", 0.104), |
| 38 | + (390, 39.86, 39.76, 388.42, 73, 1860, 10251, 100, "P2", 0.103), |
| 39 | + (400, 40.06, 39.66, 398.67, 74, 1875, 10251, 100, "P2", 0.100), |
| 40 | + (410, 40.06, 40.16, 407.98, 75, 1890, 10251, 97, "P2", 0.098), |
| 41 | + (420, 40.35, 39.96, 418.79, 75, 1905, 10251, 97, "P2", 0.096), |
| 42 | + (430, 40.45, 40.36, 428.56, 76, 1920, 10251, 97, "P2", 0.094), |
| 43 | + (440, 40.55, 40.56, 438.52, 76, 1935, 10251, 100, "P2", 0.092), |
| 44 | + (450, 40.45, 40.76, 448.35, 77, 1935, 10251, 100, "P2", 0.090), # stock TDP |
| 45 | +] |
| 46 | + |
| 47 | +caps = [d[0] for d in data] |
| 48 | +narr = [d[1] for d in data] |
| 49 | +code = [d[2] for d in data] |
| 50 | +draw = [d[3] for d in data] |
| 51 | +gpu_temp = [d[4] for d in data] |
| 52 | +sm_clk = [d[5] for d in data] |
| 53 | +mem_clk = [d[6] for d in data] |
| 54 | +pwr_throttle = [d[7] for d in data] |
| 55 | +p_state = [d[8] for d in data] |
| 56 | +eff = [d[9] for d in data] |
| 57 | + |
| 58 | +plt.rcParams.update({ |
| 59 | + "font.family": "sans-serif", |
| 60 | + "font.size": 12, |
| 61 | + "axes.titlesize": 16, |
| 62 | + "axes.titleweight": "bold", |
| 63 | + "axes.labelsize": 13, |
| 64 | + "figure.facecolor": "white", |
| 65 | + "axes.facecolor": "white", |
| 66 | +}) |
| 67 | + |
| 68 | +fig, ax1 = plt.subplots(figsize=(11, 6.4), dpi=150) |
| 69 | + |
| 70 | +# Left axis: TPS |
| 71 | +color_narr = "#1f77b4" |
| 72 | +color_code = "#2ca02c" |
| 73 | +ax1.plot(caps, narr, "o-", color=color_narr, linewidth=2.2, markersize=6, |
| 74 | + label="Narrative TPS", zorder=3) |
| 75 | +ax1.plot(caps, code, "s-", color=color_code, linewidth=2.2, markersize=6, |
| 76 | + label="Code TPS", zorder=3) |
| 77 | +ax1.set_xlabel("Power cap (W)", fontsize=13) |
| 78 | +ax1.set_ylabel("Wall TPS (single-stream, llama.cpp mainline)", fontsize=13) |
| 79 | +ax1.set_xlim(225, 455) |
| 80 | +ax1.set_ylim(14, 43) |
| 81 | +ax1.grid(True, alpha=0.3, zorder=0) |
| 82 | +ax1.tick_params(axis="both", labelsize=11) |
| 83 | + |
| 84 | +# Right axis: TPS/W efficiency |
| 85 | +ax2 = ax1.twinx() |
| 86 | +color_eff = "#d62728" |
| 87 | +ax2.plot(caps, eff, "^--", color=color_eff, linewidth=1.8, markersize=5, |
| 88 | + alpha=0.9, label="Efficiency (narr TPS/W)", zorder=2) |
| 89 | +ax2.set_ylabel("Efficiency: TPS/W (narrative)", color=color_eff, fontsize=13) |
| 90 | +ax2.tick_params(axis="y", labelcolor=color_eff, labelsize=11) |
| 91 | +ax2.set_ylim(0.07, 0.118) |
| 92 | + |
| 93 | +# Sweet spot annotation: 350W |
| 94 | +ax1.axvline(350,color="goldenrod", linestyle=":", alpha=0.5, linewidth=1.5) |
| 95 | +ax1.annotate( |
| 96 | + "★ 350W cap\n0.11 TPS/W (best efficiency)\n38.45 narr / 37.98 code\nSM 1755 MHz, 78% of stock TDP", |
| 97 | + xy=(350, 38), |
| 98 | + xytext=(320, 28), |
| 99 | + fontsize=10.5, |
| 100 | + fontweight="bold", |
| 101 | + bbox=dict(boxstyle="round,pad=0.4", facecolor="#fff3cd", edgecolor="goldenrod", linewidth=1.2), |
| 102 | + arrowprops=dict(arrowstyle="->", color="goldenrod", lw=1.5), |
| 103 | + zorder=4, |
| 104 | +) |
| 105 | + |
| 106 | +# Stock TDP marker at 450W |
| 107 | +ax1.axvline(450, color="#888", linestyle="--", alpha=0.6, linewidth=1.2) |
| 108 | +ax1.annotate( |
| 109 | + "stock TDP\n450W (GPU 0)", |
| 110 | + xy=(450, 40.06), |
| 111 | + xytext=(455, 42), |
| 112 | + fontsize=10, |
| 113 | + ha="left", |
| 114 | + color="#555", |
| 115 | + fontstyle="italic", |
| 116 | +) |
| 117 | + |
| 118 | +# Title |
| 119 | +ax1.set_title( |
| 120 | + "RTX 3090Ti Qwen3.6-27B + llama.cpp — extended power-cap efficiency curve", |
| 121 | + pad=14, |
| 122 | +) |
| 123 | + |
| 124 | +# Subtitle |
| 125 | +fig.text( |
| 126 | + 0.5, 0.92, |
| 127 | + "1× 3090Ti air-cooled (GPU 0), mainline llama.cpp + Q3_K_XL GGUF, " |
| 128 | + "time-bounded single-stream, 230-450W sweep | data: @pascalrheaume (2026-05-14)", |
| 129 | + ha="center", fontsize=10, color="#666", |
| 130 | + style="italic", |
| 131 | +) |
| 132 | + |
| 133 | +# Combined legend |
| 134 | +lines1, labels1 = ax1.get_legend_handles_labels() |
| 135 | +lines2, labels2 = ax2.get_legend_handles_labels() |
| 136 | +ax1.legend(lines1 + lines2, labels1 + labels2, |
| 137 | + loc="lower right", fontsize=11, framealpha=0.95, |
| 138 | + edgecolor="#ccc") |
| 139 | + |
| 140 | +# Footer |
| 141 | +fig.text( |
| 142 | + 0.99, 0.01, |
| 143 | + "github.com/pascalrheaume/club-3090", |
| 144 | + ha="right", fontsize=9, color="#888", style="italic", |
| 145 | +) |
| 146 | + |
| 147 | +plt.tight_layout(rect=(0, 0.02, 1, 0.92)) |
| 148 | + |
| 149 | +out = "/tmp/power_cap_sweep_3090Ti_Qwen36.png" |
| 150 | +plt.savefig(out, dpi=150, bbox_inches="tight", facecolor="white") |
| 151 | +print(f"Saved: {out}") |
0 commit comments