Skip to content

Commit 8f06e41

Browse files
committed
Add JSON output to benchmarks and upload as artifacts
- Update benchmark-hardware.py to save results to JSON - Update benchmark-jupyter.ipynb to save results to JSON - Update benchmark-jupyterbook.md to save results to JSON - Add CI step to collect and display benchmark results - Add CI step to upload benchmark results as artifact
1 parent ef69a1a commit 8f06e41

File tree

4 files changed

+198
-13
lines changed

4 files changed

+198
-13
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ jobs:
4545
shell: bash -l {0}
4646
run: |
4747
echo "=== Jupyter Kernel Execution ==="
48-
jupyter nbconvert --to notebook --execute scripts/benchmark-jupyter.ipynb --output benchmark-jupyter-executed.ipynb
48+
cd scripts
49+
jupyter nbconvert --to notebook --execute benchmark-jupyter.ipynb --output benchmark-jupyter-executed.ipynb
4950
echo "Notebook executed successfully"
51+
cd ..
5052
- name: Run Jupyter-Book Benchmark
5153
shell: bash -l {0}
5254
run: |
@@ -61,9 +63,40 @@ jobs:
6163
# Create minimal _toc.yml
6264
echo "format: jb-book" > benchmark_test/_toc.yml
6365
echo "root: benchmark-jupyterbook" >> benchmark_test/_toc.yml
64-
# Build
65-
jb build benchmark_test --path-output benchmark_build/
66+
# Build (run from benchmark_test so JSON is written there)
67+
cd benchmark_test
68+
jb build . --path-output ../benchmark_build/
69+
cd ..
6670
echo "Jupyter-Book build completed successfully"
71+
- name: Collect and Display Benchmark Results
72+
shell: bash -l {0}
73+
run: |
74+
echo "=== Collecting Benchmark Results ==="
75+
mkdir -p benchmark_results
76+
77+
# Copy results from each pathway
78+
cp benchmark_results_bare_metal.json benchmark_results/ 2>/dev/null || echo "No bare metal results"
79+
cp scripts/benchmark_results_jupyter.json benchmark_results/ 2>/dev/null || echo "No jupyter results"
80+
cp benchmark_test/benchmark_results_jupyterbook.json benchmark_results/ 2>/dev/null || echo "No jupyterbook results"
81+
82+
# Display summary
83+
echo ""
84+
echo "============================================================"
85+
echo "BENCHMARK RESULTS SUMMARY"
86+
echo "============================================================"
87+
for f in benchmark_results/*.json; do
88+
if [ -f "$f" ]; then
89+
echo ""
90+
echo "--- $(basename $f) ---"
91+
cat "$f"
92+
fi
93+
done
94+
- name: Upload Benchmark Results
95+
uses: actions/upload-artifact@v5
96+
with:
97+
name: benchmark-results
98+
path: benchmark_results/
99+
if-no-files-found: warn
67100
- name: Download "build" folder (cache)
68101
uses: dawidd6/action-download-artifact@v11
69102
with:

scripts/benchmark-hardware.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66
import time
77
import platform
88
import os
9+
import json
10+
from datetime import datetime
11+
12+
# Global results dictionary
13+
RESULTS = {
14+
"pathway": "bare_metal",
15+
"timestamp": datetime.now().isoformat(),
16+
"system": {},
17+
"benchmarks": {}
18+
}
919

1020
def get_cpu_info():
1121
"""Get CPU information."""
@@ -16,12 +26,20 @@ def get_cpu_info():
1626
print(f"Processor: {platform.processor()}")
1727
print(f"Python: {platform.python_version()}")
1828

19-
# Try to get CPU frequency
29+
RESULTS["system"]["platform"] = platform.platform()
30+
RESULTS["system"]["processor"] = platform.processor()
31+
RESULTS["system"]["python"] = platform.python_version()
32+
RESULTS["system"]["cpu_count"] = os.cpu_count()
33+
34+
# Try to get CPU model
35+
cpu_model = None
36+
cpu_mhz = None
2037
try:
2138
with open('/proc/cpuinfo', 'r') as f:
2239
for line in f:
2340
if 'model name' in line:
24-
print(f"CPU Model: {line.split(':')[1].strip()}")
41+
cpu_model = line.split(':')[1].strip()
42+
print(f"CPU Model: {cpu_model}")
2543
break
2644
except:
2745
pass
@@ -31,26 +49,33 @@ def get_cpu_info():
3149
with open('/proc/cpuinfo', 'r') as f:
3250
for line in f:
3351
if 'cpu MHz' in line:
34-
print(f"CPU MHz: {line.split(':')[1].strip()}")
52+
cpu_mhz = line.split(':')[1].strip()
53+
print(f"CPU MHz: {cpu_mhz}")
3554
break
3655
except:
3756
pass
3857

58+
RESULTS["system"]["cpu_model"] = cpu_model
59+
RESULTS["system"]["cpu_mhz"] = cpu_mhz
60+
3961
# CPU count
4062
print(f"CPU Count: {os.cpu_count()}")
4163

4264
# Check for GPU
65+
gpu_info = None
4366
try:
4467
import subprocess
4568
result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total', '--format=csv,noheader'],
4669
capture_output=True, text=True, timeout=5)
4770
if result.returncode == 0:
48-
print(f"GPU: {result.stdout.strip()}")
71+
gpu_info = result.stdout.strip()
72+
print(f"GPU: {gpu_info}")
4973
else:
5074
print("GPU: None detected")
5175
except:
5276
print("GPU: None detected (nvidia-smi not available)")
5377

78+
RESULTS["system"]["gpu"] = gpu_info
5479
print()
5580

5681
def benchmark_cpu_pure_python():
@@ -59,11 +84,14 @@ def benchmark_cpu_pure_python():
5984
print("CPU BENCHMARK: Pure Python")
6085
print("=" * 60)
6186

87+
results = {}
88+
6289
# Integer computation
6390
start = time.perf_counter()
6491
total = sum(i * i for i in range(10_000_000))
6592
elapsed = time.perf_counter() - start
6693
print(f"Integer sum (10M iterations): {elapsed:.3f} seconds")
94+
results["integer_sum_10m"] = elapsed
6795

6896
# Float computation
6997
start = time.perf_counter()
@@ -72,7 +100,10 @@ def benchmark_cpu_pure_python():
72100
total += (i * 0.1) ** 0.5
73101
elapsed = time.perf_counter() - start
74102
print(f"Float sqrt (1M iterations): {elapsed:.3f} seconds")
103+
results["float_sqrt_1m"] = elapsed
75104
print()
105+
106+
RESULTS["benchmarks"]["pure_python"] = results
76107

77108
def benchmark_cpu_numpy():
78109
"""NumPy CPU benchmark."""
@@ -82,6 +113,8 @@ def benchmark_cpu_numpy():
82113
print("CPU BENCHMARK: NumPy")
83114
print("=" * 60)
84115

116+
results = {}
117+
85118
# Matrix multiplication
86119
n = 3000
87120
A = np.random.randn(n, n)
@@ -91,6 +124,7 @@ def benchmark_cpu_numpy():
91124
C = A @ B
92125
elapsed = time.perf_counter() - start
93126
print(f"Matrix multiply ({n}x{n}): {elapsed:.3f} seconds")
127+
results["matmul_3000x3000"] = elapsed
94128

95129
# Element-wise operations
96130
x = np.random.randn(50_000_000)
@@ -99,7 +133,10 @@ def benchmark_cpu_numpy():
99133
y = np.cos(x**2) + np.sin(x)
100134
elapsed = time.perf_counter() - start
101135
print(f"Element-wise ops (50M elements): {elapsed:.3f} seconds")
136+
results["elementwise_50m"] = elapsed
102137
print()
138+
139+
RESULTS["benchmarks"]["numpy"] = results
103140

104141
def benchmark_gpu_jax():
105142
"""JAX benchmark (GPU if available, otherwise CPU)."""
@@ -125,6 +162,12 @@ def benchmark_gpu_jax():
125162
print(f"GPU Available: {has_gpu}")
126163
print()
127164

165+
results = {
166+
"backend": default_backend,
167+
"has_gpu": has_gpu,
168+
"devices": str(devices)
169+
}
170+
128171
# Warm-up JIT compilation
129172
print("Warming up JIT compilation...")
130173
n = 1000
@@ -141,12 +184,14 @@ def matmul(a, b):
141184
C = matmul(A, B).block_until_ready()
142185
warmup_time = time.perf_counter() - start
143186
print(f"Warm-up (includes JIT compile, {n}x{n}): {warmup_time:.3f} seconds")
187+
results["matmul_1000x1000_warmup"] = warmup_time
144188

145189
# Actual benchmark (compiled)
146190
start = time.perf_counter()
147191
C = matmul(A, B).block_until_ready()
148192
elapsed = time.perf_counter() - start
149193
print(f"Matrix multiply compiled ({n}x{n}): {elapsed:.3f} seconds")
194+
results["matmul_1000x1000_compiled"] = elapsed
150195

151196
# Larger matrix
152197
n = 3000
@@ -158,12 +203,14 @@ def matmul(a, b):
158203
C = matmul(A, B).block_until_ready()
159204
warmup_time = time.perf_counter() - start
160205
print(f"Warm-up (recompile for {n}x{n}): {warmup_time:.3f} seconds")
206+
results["matmul_3000x3000_warmup"] = warmup_time
161207

162208
# Benchmark compiled
163209
start = time.perf_counter()
164210
C = matmul(A, B).block_until_ready()
165211
elapsed = time.perf_counter() - start
166212
print(f"Matrix multiply compiled ({n}x{n}): {elapsed:.3f} seconds")
213+
results["matmul_3000x3000_compiled"] = elapsed
167214

168215
# Element-wise GPU benchmark
169216
x = jax.random.normal(key, (50_000_000,))
@@ -177,19 +224,24 @@ def elementwise_ops(x):
177224
y = elementwise_ops(x).block_until_ready()
178225
warmup_time = time.perf_counter() - start
179226
print(f"Element-wise warm-up (50M): {warmup_time:.3f} seconds")
227+
results["elementwise_50m_warmup"] = warmup_time
180228

181229
# Compiled
182230
start = time.perf_counter()
183231
y = elementwise_ops(x).block_until_ready()
184232
elapsed = time.perf_counter() - start
185233
print(f"Element-wise compiled (50M): {elapsed:.3f} seconds")
234+
results["elementwise_50m_compiled"] = elapsed
186235

187236
print()
237+
RESULTS["benchmarks"]["jax"] = results
188238

189239
except ImportError as e:
190240
print(f"JAX not available: {e}")
241+
RESULTS["benchmarks"]["jax"] = {"error": str(e)}
191242
except Exception as e:
192243
print(f"JAX benchmark failed: {e}")
244+
RESULTS["benchmarks"]["jax"] = {"error": str(e)}
193245

194246
def benchmark_numba():
195247
"""Numba CPU benchmark."""
@@ -201,6 +253,8 @@ def benchmark_numba():
201253
print("CPU BENCHMARK: Numba")
202254
print("=" * 60)
203255

256+
results = {}
257+
204258
@numba.jit(nopython=True)
205259
def numba_sum(n):
206260
total = 0
@@ -213,12 +267,14 @@ def numba_sum(n):
213267
result = numba_sum(10_000_000)
214268
warmup_time = time.perf_counter() - start
215269
print(f"Integer sum warm-up (includes compile): {warmup_time:.3f} seconds")
270+
results["integer_sum_10m_warmup"] = warmup_time
216271

217272
# Compiled run
218273
start = time.perf_counter()
219274
result = numba_sum(10_000_000)
220275
elapsed = time.perf_counter() - start
221276
print(f"Integer sum compiled (10M): {elapsed:.3f} seconds")
277+
results["integer_sum_10m_compiled"] = elapsed
222278

223279
@numba.jit(nopython=True, parallel=True)
224280
def numba_parallel_sum(arr):
@@ -234,19 +290,32 @@ def numba_parallel_sum(arr):
234290
result = numba_parallel_sum(arr)
235291
warmup_time = time.perf_counter() - start
236292
print(f"Parallel sum warm-up (50M): {warmup_time:.3f} seconds")
293+
results["parallel_sum_50m_warmup"] = warmup_time
237294

238295
# Compiled
239296
start = time.perf_counter()
240297
result = numba_parallel_sum(arr)
241298
elapsed = time.perf_counter() - start
242299
print(f"Parallel sum compiled (50M): {elapsed:.3f} seconds")
300+
results["parallel_sum_50m_compiled"] = elapsed
243301

244302
print()
303+
RESULTS["benchmarks"]["numba"] = results
245304

246305
except ImportError as e:
247306
print(f"Numba not available: {e}")
307+
RESULTS["benchmarks"]["numba"] = {"error": str(e)}
248308
except Exception as e:
249309
print(f"Numba benchmark failed: {e}")
310+
RESULTS["benchmarks"]["numba"] = {"error": str(e)}
311+
312+
313+
def save_results(output_path="benchmark_results_bare_metal.json"):
314+
"""Save benchmark results to JSON file."""
315+
with open(output_path, 'w') as f:
316+
json.dump(RESULTS, f, indent=2)
317+
print(f"\nResults saved to: {output_path}")
318+
250319

251320
if __name__ == "__main__":
252321
print("\n" + "=" * 60)
@@ -259,6 +328,9 @@ def numba_parallel_sum(arr):
259328
benchmark_numba()
260329
benchmark_gpu_jax()
261330

331+
# Save results to JSON
332+
save_results("benchmark_results_bare_metal.json")
333+
262334
print("=" * 60)
263335
print("BENCHMARK COMPLETE")
264336
print("=" * 60)

0 commit comments

Comments
 (0)