torchbenchx 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ from .benchx import BenchX
2
+
3
+ __all__ = ["BenchX"]
4
+ __version__ = "0.1.0"
5
+
6
+
torchbenchx/benchx.py ADDED
@@ -0,0 +1,376 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
6
+
7
+ import psutil
8
+ import pandas as pd
9
+
10
+ try:
11
+ import plotly.express as px
12
+ except Exception: # pragma: no cover - optional at runtime
13
+ px = None # type: ignore
14
+
15
+
16
+ @dataclass
17
+ class _Result:
18
+ Model: str
19
+ Framework: str
20
+ Device: str
21
+ Latency_ms: float
22
+ Throughput_sps: float
23
+ Memory_MB: float
24
+ Memory_Bytes: int
25
+ Params_M: float
26
+
27
+
28
+ class BenchX:
29
+ def __init__(
30
+ self,
31
+ device: Optional[str] = None,
32
+ batch_size: int = 16,
33
+ runs: int = 30,
34
+ warmup: int = 10,
35
+ ) -> None:
36
+ self._device_pref = device
37
+ self.batch_size = int(batch_size)
38
+ self.runs = int(runs)
39
+ self.warmup = int(warmup)
40
+ self._results: List[_Result] = []
41
+
42
+ # ----------------------------- Public API ----------------------------- #
43
+ def benchmark(
44
+ self,
45
+ model: Any,
46
+ name: str,
47
+ input_shape: Sequence[int] = (3, 224, 224),
48
+ framework: str = "pytorch",
49
+ ) -> Dict[str, Any]:
50
+ framework_lc = framework.lower()
51
+ if framework_lc not in ("pytorch", "tensorflow"):
52
+ raise ValueError("framework must be 'pytorch' or 'tensorflow'")
53
+
54
+ if framework_lc == "pytorch":
55
+ result = self._benchmark_torch(model, name, tuple(input_shape))
56
+ else:
57
+ result = self._benchmark_tf(model, name, tuple(input_shape))
58
+
59
+ self._results.append(_Result(**result))
60
+ return result
61
+
62
+ def summary(self) -> pd.io.formats.style.Styler:
63
+ df = pd.DataFrame([r.__dict__ for r in self._results])
64
+ if df.empty:
65
+ return pd.DataFrame(columns=[
66
+ "Model", "Framework", "Device", "Latency_ms",
67
+ "Throughput_sps", "Memory_MB", "Memory_Bytes", "Params_M",
68
+ ]).style
69
+
70
+ # Add humanized memory column for readability
71
+ df["Memory"] = df["Memory_Bytes"].map(self._humanize_bytes)
72
+ df_sorted = df.sort_values(by=["Throughput_sps"], ascending=False, ignore_index=True)
73
+
74
+ styler = df_sorted.style.format({
75
+ "Latency_ms": "{:.2f}",
76
+ "Throughput_sps": "{:.0f}",
77
+ "Memory_MB": "{:.1f}",
78
+ "Params_M": "{:.2f}",
79
+ })
80
+
81
+ # Highlight best throughput
82
+ if len(df_sorted) > 0:
83
+ max_idx = df_sorted["Throughput_sps"].idxmax()
84
+ def _highlight_best(row: pd.Series) -> List[str]:
85
+ return [
86
+ "background-color: #e6ffed; font-weight: 600;" if row.name == max_idx else ""
87
+ ] * len(row)
88
+ styler = styler.apply(_highlight_best, axis=1)
89
+
90
+ # Stronger, more visible color accents on white
91
+ styler = styler.background_gradient(subset=["Latency_ms"], cmap="Reds_r")
92
+ styler = styler.background_gradient(subset=["Throughput_sps"], cmap="Greens")
93
+ styler = styler.background_gradient(subset=["Memory_MB"], cmap="Purples")
94
+
95
+ # Subtle borders for a neat table look
96
+ styler = styler.set_table_styles([
97
+ {"selector": "table", "props": [
98
+ ("border-collapse", "separate"), ("border-spacing", "0 8px"), ("background", "#ffffff")
99
+ ]},
100
+ {"selector": "th", "props": [("background", "#f0f4ff"), ("font-weight", "700"), ("color", "#222")]} ,
101
+ {"selector": "td", "props": [("border", "1px solid #ddd"), ("padding", "8px 12px"), ("color", "#111")]},
102
+ ])
103
+ try:
104
+ styler = styler.hide_columns(["Memory_Bytes"]) # keep the raw bytes hidden but available
105
+ except Exception:
106
+ pass
107
+ return styler
108
+
109
+ def visualize(
110
+ self,
111
+ metric_x: str = "Latency_ms",
112
+ metric_y: str = "Throughput_sps",
113
+ ) -> Any:
114
+ if px is None:
115
+ raise RuntimeError("plotly is not installed. Please `pip install plotly`. ")
116
+
117
+ df = pd.DataFrame([r.__dict__ for r in self._results])
118
+ if df.empty:
119
+ raise RuntimeError("No results to visualize. Run benchmark() first.")
120
+
121
+ fig = px.scatter(
122
+ df,
123
+ x=metric_x,
124
+ y=metric_y,
125
+ color="Framework",
126
+ size="Params_M",
127
+ hover_data=["Model", "Device", "Memory_MB"],
128
+ title="torchbenchx — Performance Comparison",
129
+ )
130
+ fig.update_layout(legend_title_text="Framework")
131
+ fig.update_traces(marker=dict(opacity=0.85, line=dict(width=1, color="#ffffff")))
132
+ return fig
133
+
134
+ # ----------------------------- Backends ------------------------------ #
135
+ def _benchmark_torch(self, model: Any, name: str, input_shape: Tuple[int, ...]) -> Dict[str, Any]:
136
+ import math
137
+ import numpy as np
138
+ try:
139
+ import torch
140
+ except Exception as exc: # pragma: no cover
141
+ raise RuntimeError("PyTorch is required for framework='pytorch'") from exc
142
+
143
+ device = self._resolve_torch_device()
144
+ model = model.to(device)
145
+ model.eval()
146
+
147
+ batch = self.batch_size
148
+ c, h, w = input_shape
149
+ dummy = torch.randn(batch, c, h, w, device=device)
150
+
151
+ # Parameter count (millions) and bytes
152
+ param_elems = sum(p.numel() for p in model.parameters())
153
+ params_m = param_elems / 1_000_000.0
154
+ param_bytes = sum(p.numel() * p.element_size() for p in model.parameters())
155
+
156
+ # Memory tracking (GPU peak if CUDA, else process RSS delta)
157
+ process = psutil.Process()
158
+ start_rss = process.memory_info().rss
159
+ peak_mb_gpu = 0.0
160
+ if device.type == "cuda":
161
+ torch.cuda.reset_peak_memory_stats(device)
162
+
163
+ # Warmup
164
+ with torch.no_grad():
165
+ for _ in range(self.warmup):
166
+ _ = model(dummy)
167
+ if device.type == "cuda":
168
+ torch.cuda.synchronize(device)
169
+
170
+ # Timed runs
171
+ start = time.perf_counter()
172
+ with torch.no_grad():
173
+ for _ in range(self.runs):
174
+ _ = model(dummy)
175
+ if device.type == "cuda":
176
+ torch.cuda.synchronize(device)
177
+ total = time.perf_counter() - start
178
+
179
+ if device.type == "cuda":
180
+ peak_bytes = torch.cuda.max_memory_allocated(device)
181
+ peak_mb_gpu = peak_bytes / (1024 ** 2)
182
+ end_rss = process.memory_info().rss
183
+ delta_mb_cpu = max(0.0, (end_rss - start_rss) / (1024 ** 2))
184
+ # Estimated activation memory from input (rough, but non-zero and comparable)
185
+ activation_bytes = self.batch_size * c * h * w * 4 # float32
186
+ est_bytes = max(int(param_bytes + activation_bytes), 0)
187
+ # Prefer GPU peak if available, else use estimate (avoid zeroes)
188
+ mem_bytes = int(peak_mb_gpu * (1024 ** 2)) if device.type == "cuda" and peak_mb_gpu > 0 else max(int(delta_mb_cpu * (1024 ** 2)), est_bytes)
189
+ mem_mb = mem_bytes / (1024 ** 2)
190
+
191
+ latency_ms = (total / self.runs) * 1000.0
192
+ throughput_sps = (batch * self.runs) / max(total, 1e-9)
193
+
194
+ return {
195
+ "Model": name,
196
+ "Framework": "PyTorch",
197
+ "Device": str(device),
198
+ "Latency_ms": float(latency_ms),
199
+ "Throughput_sps": float(throughput_sps),
200
+ "Memory_MB": float(mem_mb),
201
+ "Memory_Bytes": int(mem_bytes),
202
+ "Params_M": float(params_m),
203
+ }
204
+
205
+ def _benchmark_tf(self, model: Any, name: str, input_shape: Tuple[int, ...]) -> Dict[str, Any]:
206
+ import numpy as np
207
+ try:
208
+ import tensorflow as tf
209
+ except Exception as exc: # pragma: no cover
210
+ raise RuntimeError("TensorFlow is required for framework='tensorflow'") from exc
211
+
212
+ # TensorFlow typically uses channels-last
213
+ if len(input_shape) != 3:
214
+ raise ValueError("input_shape must be a 3-tuple for images")
215
+ c, h, w = input_shape
216
+ hwc = (h, w, c)
217
+
218
+ batch = self.batch_size
219
+ dummy_np = np.random.randn(batch, *hwc).astype("float32")
220
+ dummy = tf.convert_to_tensor(dummy_np)
221
+
222
+ # Ensure model is built
223
+ _ = model(dummy, training=False)
224
+
225
+ # Parameter count (millions) and bytes
226
+ try:
227
+ params = model.count_params()
228
+ except Exception:
229
+ params = sum(int(v.shape.num_elements() or 0) for v in model.trainable_variables)
230
+ params_m = params / 1_000_000.0
231
+ param_bytes = params * 4 # assume float32
232
+
233
+ # Device string
234
+ device = "GPU" if tf.config.list_physical_devices("GPU") else "CPU"
235
+
236
+ process = psutil.Process()
237
+ start_rss = process.memory_info().rss
238
+
239
+ # Warmup
240
+ for _ in range(self.warmup):
241
+ _ = model(dummy, training=False)
242
+ # Best-effort sync using .numpy() materialization
243
+ _ = _.numpy() if hasattr(_, "numpy") else _
244
+
245
+ # Timed runs
246
+ start = time.perf_counter()
247
+ for _ in range(self.runs):
248
+ out = model(dummy, training=False)
249
+ _ = out.numpy() if hasattr(out, "numpy") else out
250
+ total = time.perf_counter() - start
251
+
252
+ end_rss = process.memory_info().rss
253
+ delta_bytes = max(0, end_rss - start_rss)
254
+ activation_bytes = self.batch_size * h * w * c * 4 # float32
255
+ est_bytes = max(int(param_bytes + activation_bytes), 0)
256
+ mem_bytes = max(delta_bytes, est_bytes)
257
+ mem_mb = mem_bytes / (1024 ** 2)
258
+
259
+ latency_ms = (total / self.runs) * 1000.0
260
+ throughput_sps = (batch * self.runs) / max(total, 1e-9)
261
+
262
+ return {
263
+ "Model": name,
264
+ "Framework": "TensorFlow",
265
+ "Device": device.lower(),
266
+ "Latency_ms": float(latency_ms),
267
+ "Throughput_sps": float(throughput_sps),
268
+ "Memory_MB": float(mem_mb),
269
+ "Memory_Bytes": int(mem_bytes),
270
+ "Params_M": float(params_m),
271
+ }
272
+
273
+ # ----------------------------- Utils --------------------------------- #
274
+ def _resolve_torch_device(self):
275
+ try:
276
+ import torch
277
+ except Exception as exc: # pragma: no cover
278
+ raise RuntimeError("PyTorch is required for framework='pytorch'") from exc
279
+
280
+ if self._device_pref is not None:
281
+ return torch.device(self._device_pref)
282
+ return torch.device("cuda" if torch.cuda.is_available() else "cpu")
283
+
284
+ def _humanize_bytes(self, n: int) -> str:
285
+ units = ["B", "KB", "MB", "GB", "TB"]
286
+ x = float(n)
287
+ idx = 0
288
+ while x >= 1024.0 and idx < len(units) - 1:
289
+ x /= 1024.0
290
+ idx += 1
291
+ if idx == 0:
292
+ return f"{int(x)} {units[idx]}"
293
+ return f"{x:.2f} {units[idx]}"
294
+
295
+ # -------------------------- Enhanced Visuals ------------------------- #
296
+ def visualize(
297
+ self,
298
+ metric_x: str = "Latency_ms",
299
+ metric_y: str = "Throughput_sps",
300
+ ) -> Any:
301
+ if px is None:
302
+ raise RuntimeError("plotly is not installed. Please `pip install plotly`. ")
303
+
304
+ df = pd.DataFrame([r.__dict__ for r in self._results])
305
+ if df.empty:
306
+ raise RuntimeError("No results to visualize. Run benchmark() first.")
307
+
308
+ fig = px.scatter(
309
+ df,
310
+ x=metric_x,
311
+ y=metric_y,
312
+ color="Framework",
313
+ size="Params_M",
314
+ hover_data=["Model", "Device", "Memory_MB"],
315
+ title="torchbenchx — Performance Comparison",
316
+ )
317
+ fig.update_layout(
318
+ legend_title_text="Framework",
319
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
320
+ title=dict(font=dict(size=20, color="#111")),
321
+ xaxis_title=dict(text=metric_x.replace("_", " "), font=dict(size=14)),
322
+ yaxis_title=dict(text=metric_y.replace("_", " "), font=dict(size=14)),
323
+ plot_bgcolor="#fff",
324
+ paper_bgcolor="#fff",
325
+ )
326
+ fig.update_xaxes(showgrid=True, gridcolor="#eee")
327
+ fig.update_yaxes(showgrid=True, gridcolor="#eee")
328
+ fig.update_traces(marker=dict(opacity=0.9, line=dict(width=1, color="#ffffff")))
329
+ return fig
330
+
331
+ def visualize_all(self) -> Dict[str, Any]:
332
+ if px is None:
333
+ raise RuntimeError("plotly is not installed. Please `pip install plotly`. ")
334
+ df = pd.DataFrame([r.__dict__ for r in self._results])
335
+ if df.empty:
336
+ raise RuntimeError("No results to visualize. Run benchmark() first.")
337
+
338
+ figs: Dict[str, Any] = {}
339
+ # Scatter throughput vs latency
340
+ figs["scatter"] = self.visualize("Latency_ms", "Throughput_sps")
341
+
342
+ # Bar: Throughput by model
343
+ figs["throughput_bar"] = px.bar(
344
+ df.sort_values("Throughput_sps", ascending=False),
345
+ x="Model",
346
+ y="Throughput_sps",
347
+ color="Framework",
348
+ title="Throughput (samples/sec) by Model",
349
+ )
350
+ figs["throughput_bar"].update_layout(legend=dict(orientation="h", y=1.02, x=1, xanchor="right"))
351
+ figs["throughput_bar"].update_yaxes(gridcolor="#eee")
352
+
353
+ # Bar: Latency by model (lower is better)
354
+ figs["latency_bar"] = px.bar(
355
+ df.sort_values("Latency_ms", ascending=True),
356
+ x="Model",
357
+ y="Latency_ms",
358
+ color="Framework",
359
+ title="Latency (ms per inference) by Model",
360
+ )
361
+ figs["latency_bar"].update_layout(legend=dict(orientation="h", y=1.02, x=1, xanchor="right"))
362
+ figs["latency_bar"].update_yaxes(gridcolor="#eee")
363
+
364
+ # Bar: Memory by model (MB)
365
+ figs["memory_bar"] = px.bar(
366
+ df.sort_values("Memory_MB", ascending=True),
367
+ x="Model",
368
+ y="Memory_MB",
369
+ color="Framework",
370
+ title="Estimated Memory Usage (MB) by Model",
371
+ )
372
+ figs["memory_bar"].update_layout(legend=dict(orientation="h", y=1.02, x=1, xanchor="right"))
373
+ figs["memory_bar"].update_yaxes(gridcolor="#eee")
374
+ return figs
375
+
376
+
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: torchbenchx
3
+ Version: 0.1.0
4
+ Summary: Unified Deep Learning Benchmark Library for PyTorch and TensorFlow
5
+ Home-page: https://github.com/syeedsaquib/torchbenchx
6
+ Author: Syeed Mohd Saquib
7
+ Author-email: Syeed Mohd Saquib <syeed.saquib@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://pypi.org/project/torchbenchx/
10
+ Project-URL: Repository, https://github.com/syeedsaquib/torchbenchx
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: torch>=2.0
15
+ Requires-Dist: tensorflow>=2.12
16
+ Requires-Dist: plotly
17
+ Requires-Dist: pandas
18
+ Requires-Dist: numpy
19
+ Requires-Dist: psutil
20
+ Dynamic: author
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-python
24
+
25
+ # torchbenchx — Unified Deep Learning Benchmark Library
26
+
27
+ Benchmark. Compare. Visualize.
28
+
29
+ `torchbenchx` is a framework-agnostic deep learning benchmarking library that measures latency, throughput, memory usage, and parameter count of models from both PyTorch and TensorFlow — all inside your Jupyter Notebook, with no servers or dashboards required.
30
+
31
+ It’s designed for researchers, developers, and AI enthusiasts who want quick, reliable model comparisons with interactive visualizations.
32
+
33
+ ## ⚙️ Key Features
34
+
35
+ - **Dual Framework Support**: Benchmark models from both PyTorch and TensorFlow using one unified API.
36
+ - **Latency & Throughput Measurement**: Calculates per-inference latency and samples-per-second throughput.
37
+ - **Model Parameters & Memory Usage**: Automatically estimates memory footprint and parameter count.
38
+ - **Device Awareness**: Detects and runs on CPU or GPU automatically.
39
+ - **Notebook-native Visualization**: Generates Plotly scatter plots of performance metrics directly in Jupyter/Colab.
40
+ - **Minimal Dependencies**: Lightweight and ready to use.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install torchbenchx
46
+ # Backends and viz libs
47
+ pip install torch torchvision tensorflow plotly pandas psutil
48
+ ```
49
+
50
+ Optional: install `notebook` or `jupyterlab` if using outside Colab.
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from torchbenchx import BenchX
56
+ import torchvision.models as tv_models
57
+ import tensorflow as tf
58
+
59
+ bench = BenchX(batch_size=16, runs=30)
60
+
61
+ bench.benchmark(tv_models.resnet18(weights=None), "ResNet18", framework="pytorch")
62
+ bench.benchmark(tf.keras.applications.MobileNetV3Small(), "MobileNetV3", framework="tensorflow")
63
+
64
+ bench.summary() # Styled table
65
+ bench.visualize() # Plotly scatter
66
+ ```
67
+
68
+ ## Core Class: BenchX
69
+
70
+ ```python
71
+ BenchX(device=None, batch_size=16, runs=30, warmup=10)
72
+ ```
73
+
74
+ - **device**: "cuda" or "cpu" — auto-detected if not set (for PyTorch)
75
+ - **batch_size**: Number of samples per inference
76
+ - **runs**: Number of repeated runs for averaging
77
+ - **warmup**: Initial runs ignored to stabilize measurements
78
+
79
+ ### Methods
80
+
81
+ ```python
82
+ benchmark(model, name, input_shape=(3, 224, 224), framework="pytorch")
83
+ ```
84
+ Returns a dict with:
85
+ `{Model, Framework, Device, Latency_ms, Throughput_sps, Memory_MB, Params_M}`
86
+
87
+ ```python
88
+ summary() # Styled pandas DataFrame (stronger colors on white bg)
89
+ visualize(metric_x="Latency_ms", metric_y="Throughput_sps") # Scatter with improved legend/layout
90
+ visualize_all() # Returns dict of figures: scatter, throughput_bar, latency_bar, memory_bar
91
+ ```
92
+
93
+ ## Dependencies
94
+
95
+ - **torch**: PyTorch backend
96
+ - **tensorflow**: TensorFlow backend
97
+ - **plotly**: Interactive plotting
98
+ - **pandas**: Data handling and summary display
99
+ - **psutil**: Memory usage monitoring
100
+
101
+ ## Internal Workflow
102
+
103
+ 1. Warmup phase to stabilize performance
104
+ 2. Timed inference loop across `runs`
105
+ 3. Metrics: latency (ms/inference), throughput (samples/sec)
106
+ 4. Memory estimation (GPU peak or process RSS)
107
+ 5. Results aggregated into a DataFrame and visualized
108
+ 6. Memory estimation: reports `Memory_Bytes`, `Memory_MB`, and a humanized `Memory`
109
+
110
+ ## Use Cases
111
+
112
+ - **Research**: Compare architectures across frameworks
113
+ - **Development**: Optimize inference pipelines
114
+ - **Education**: Demonstrate benchmarking concepts
115
+ - **Model Selection**: Speed vs efficiency trade-offs
116
+
117
+ ## Roadmap
118
+
119
+ - FLOPs estimation
120
+ - Power consumption metrics
121
+ - JAX and ONNX Runtime support
122
+ - Export results to CSV/JSON
123
+ - Leaderboard UI for reproducibility
124
+
125
+ ## License
126
+
127
+ MIT
128
+
129
+
@@ -0,0 +1,7 @@
1
+ torchbenchx/__init__.py,sha256=srvvpGkvMuu5NsNfiukZYUuGNkiyIG8KJOeqh2OgHgA,79
2
+ torchbenchx/benchx.py,sha256=7-2PoIhtZF9HtPTUWJgU8nuMYWHghRsk3lo5EF1OAiw,14397
3
+ torchbenchx-0.1.0.dist-info/licenses/LICENSE,sha256=AbhhpTB2WXAslfTe8Wrx2nnqk5KA3aBOIYvbJ7Qalrc,132
4
+ torchbenchx-0.1.0.dist-info/METADATA,sha256=kmhOSMWTmJjhgfjszSxTUPStvvjeR0DubPJMhRdlybk,4318
5
+ torchbenchx-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ torchbenchx-0.1.0.dist-info/top_level.txt,sha256=OxoRCr-rzZokpSCkiywZm5qfTfLefE3xhrKpFnyfd74,12
7
+ torchbenchx-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ MIT License
2
+ Copyright (c) 2025 Syeed Mohd Saquib
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy...
@@ -0,0 +1 @@
1
+ torchbenchx