trench-mark 1.0.0__tar.gz

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,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: trench-mark
3
+ Version: 1.0.0
4
+ Summary: Automated TensorRT performance profiling for Jetson edge devices.
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pyyaml
File without changes
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trench-mark"
7
+ version = "1.0.0"
8
+ description = "Automated TensorRT performance profiling for Jetson edge devices."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = [
12
+ "pyyaml",
13
+ ]
14
+
15
+ [project.scripts]
16
+ # Terminal Command = "python_folder_name.file_name:function_name"
17
+ trench-mark = "trench_mark.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,373 @@
1
+ import argparse
2
+ import os
3
+ import re
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import threading
8
+ from datetime import datetime
9
+ import yaml
10
+
11
+ class MemoryProfiler:
12
+ def __init__(self):
13
+ self.running = False
14
+ self.max_ram = 0
15
+ self.thread = None
16
+ self.process = None
17
+
18
+ def start(self):
19
+ self.running = True
20
+ self.thread = threading.Thread(target=self._monitor, daemon=True)
21
+ self.thread.start()
22
+
23
+ def _monitor(self):
24
+ if not shutil.which("tegrastats"):
25
+ if logger:
26
+ logger.write("Warning: 'tegrastats' binary not found. Memory profiling disabled.")
27
+ return
28
+ try:
29
+ self.process = subprocess.Popen(
30
+ ["tegrastats"],
31
+ stdout=subprocess.PIPE,
32
+ stderr=subprocess.STDOUT,
33
+ text=True,
34
+ bufsize=1,
35
+ )
36
+ while self.running:
37
+ line = self.process.stdout.readline()
38
+ if not line:
39
+ break
40
+ match = re.search(r"RAM\s+(\d+)/\d+MB", line)
41
+ if match:
42
+ ram = int(match.group(1))
43
+ self.max_ram = max(self.max_ram, ram)
44
+ except Exception as e:
45
+ if logger:
46
+ logger.write(f"Memory profiler error: {e}")
47
+
48
+ def stop(self):
49
+ self.running = False
50
+ if self.process:
51
+ try:
52
+ self.process.terminate()
53
+ except Exception:
54
+ pass
55
+ if self.thread and self.thread.is_alive():
56
+ self.thread.join(timeout=2)
57
+ return self.max_ram
58
+
59
+ class Logger:
60
+ def __init__(self, log_dir="logs"):
61
+ self.log_dir = log_dir
62
+ os.makedirs(self.log_dir, exist_ok=True)
63
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
64
+ self.log_file = os.path.join(self.log_dir, f"benchmark_{timestamp}.log")
65
+ with open(self.log_file, "w", encoding="utf-8") as f:
66
+ f.write("=" * 90 + "\n")
67
+ f.write("TENSORRT BENCHMARK LOG\n")
68
+ f.write("=" * 90 + "\n")
69
+ f.write(f"Started: {datetime.now()}\n")
70
+ f.write(f"Working Directory: {os.getcwd()}\n")
71
+ f.write("=" * 90 + "\n\n")
72
+
73
+ def write(self, message):
74
+ try:
75
+ with open(self.log_file, "a", encoding="utf-8") as f:
76
+ f.write(message)
77
+ if not message.endswith("\n"):
78
+ f.write("\n")
79
+ except Exception:
80
+ pass
81
+
82
+ def command(self, cmd, output, returncode):
83
+ self.write("\n" + "=" * 90)
84
+ self.write("COMMAND")
85
+ self.write("=" * 90)
86
+ self.write(" ".join(cmd))
87
+ self.write("\nRETURN CODE")
88
+ self.write("=" * 90)
89
+ self.write(str(returncode))
90
+ self.write("\nOUTPUT")
91
+ self.write("=" * 90)
92
+ self.write(output if output else "[No output]")
93
+ self.write("\n" + "=" * 90 + "\n")
94
+
95
+ logger = None
96
+
97
+ def run_command(cmd, dry_run=False):
98
+ global logger
99
+ if logger:
100
+ logger.write(f"\nExecuting command: {' '.join(cmd)}")
101
+
102
+ if dry_run:
103
+ simulated_output = (
104
+ f"[DRY-RUN] Command validated: {' '.join(cmd)}\n"
105
+ "&&&& PASSED TensorRT.trtexec [DRY-RUN]\n"
106
+ "mean: 12.50 ms\n"
107
+ "Throughput: 80.00 qps\n"
108
+ )
109
+ if logger:
110
+ logger.command(cmd, simulated_output, 0)
111
+ return simulated_output
112
+
113
+ try:
114
+ result = subprocess.run(
115
+ cmd,
116
+ stdout=subprocess.PIPE,
117
+ stderr=subprocess.STDOUT,
118
+ text=True,
119
+ )
120
+ output = result.stdout
121
+ if logger:
122
+ logger.command(cmd, output, result.returncode)
123
+ return output
124
+ except Exception as e:
125
+ error = f"Command execution failed: {e}"
126
+ if logger:
127
+ logger.command(cmd, error, -1)
128
+ return error
129
+
130
+ def extract_metrics(log):
131
+ latency_match = re.search(r"mean\s*[=:]\s*([\d\.]+)\s*ms", log, re.IGNORECASE)
132
+ throughput_match = re.search(r"Throughput\s*[=:]\s*([\d\.]+)\s*qps", log, re.IGNORECASE)
133
+ latency = float(latency_match.group(1)) if latency_match else None
134
+ throughput = float(throughput_match.group(1)) if throughput_match else None
135
+ return {"latency_ms": latency, "fps": throughput}
136
+
137
+ def log_system_information():
138
+ global logger
139
+ if not logger:
140
+ return
141
+ logger.write("\n" + "=" * 90)
142
+ logger.write("SYSTEM INFORMATION")
143
+ logger.write("=" * 90)
144
+ commands = [
145
+ ["uname", "-a"],
146
+ ["cat", "/etc/nv_tegra_release"],
147
+ ["python3", "--version"],
148
+ ["trtexec", "--help"],
149
+ ]
150
+ for cmd in commands:
151
+ try:
152
+ result = subprocess.run(
153
+ cmd,
154
+ stdout=subprocess.PIPE,
155
+ stderr=subprocess.STDOUT,
156
+ text=True,
157
+ )
158
+ logger.command(cmd, result.stdout, result.returncode)
159
+ except Exception as e:
160
+ logger.write(f"Failed to execute {' '.join(cmd)}: {e}")
161
+
162
+ def parse_arguments():
163
+ parser = argparse.ArgumentParser(
164
+ prog="trt-benchmark",
165
+ description="Automated, memory-safe TensorRT profiling pipeline for Jetson edge devices.",
166
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
167
+ )
168
+ parser.add_argument("-m", "--model", type=str, default=None, help="Path to the ONNX model file.")
169
+ parser.add_argument("-c", "--config", type=str, default="config.yaml", help="Path to config YAML file (used if CLI options are omitted).")
170
+ parser.add_argument("-s", "--input-shape", type=str, default="3x640x640", help="Input tensor shape in CHW format (e.g., 3x640x640).")
171
+ parser.add_argument("--input-name", type=str, default="images", help="Input binding name in the ONNX model.")
172
+ parser.add_argument("-p", "--precisions", nargs="+", choices=["fp32", "fp16", "int8"], default=["fp32", "fp16", "int8"], help="Precision levels to evaluate.")
173
+ parser.add_argument("-b", "--batch-sizes", nargs="+", type=int, default=[1, 4, 8], help="Batch sizes to benchmark.")
174
+ parser.add_argument("-i", "--iterations", type=int, default=500, help="Number of inference cycles per run.")
175
+ parser.add_argument("-w", "--warmup", type=int, default=50, help="Warmup duration in milliseconds.")
176
+ parser.add_argument("--workspace", type=int, default=2048, help="Max workspace size in MB for TensorRT builder.")
177
+ parser.add_argument("--dry-run", action="store_true", help="Simulate execution without running trtexec or compiling engines.")
178
+ return parser.parse_args()
179
+
180
+ def load_configuration(args):
181
+ config = {
182
+ "model_name": None,
183
+ "onnx_path": None,
184
+ "input_shape": args.input_shape,
185
+ "input_name": args.input_name,
186
+ "batch_sizes": args.batch_sizes,
187
+ "precisions": args.precisions,
188
+ "iterations": args.iterations,
189
+ "warmup": args.warmup,
190
+ "workspace": args.workspace,
191
+ }
192
+
193
+ if args.model:
194
+ config["onnx_path"] = args.model
195
+ config["model_name"] = os.path.splitext(os.path.basename(args.model))[0]
196
+ return config
197
+
198
+ if os.path.exists(args.config):
199
+ try:
200
+ with open(args.config, "r", encoding="utf-8") as f:
201
+ file_cfg = yaml.safe_load(f)
202
+ config["model_name"] = file_cfg.get("model", {}).get("name", "model")
203
+ config["onnx_path"] = file_cfg.get("model", {}).get("onnx_path")
204
+ config["input_shape"] = file_cfg.get("model", {}).get("input_shape", args.input_shape)
205
+ config["batch_sizes"] = file_cfg.get("benchmarks", {}).get("batch_sizes", args.batch_sizes)
206
+ config["precisions"] = file_cfg.get("benchmarks", {}).get("precisions", args.precisions)
207
+ config["iterations"] = file_cfg.get("settings", {}).get("iterations", args.iterations)
208
+ config["warmup"] = file_cfg.get("settings", {}).get("warmup", args.warmup)
209
+ return config
210
+ except Exception as e:
211
+ if logger:
212
+ logger.write(f"Failed to read {args.config}: {e}")
213
+
214
+ return config
215
+
216
+ def main():
217
+ global logger
218
+ args = parse_arguments()
219
+ logger = Logger()
220
+ config = load_configuration(args)
221
+
222
+ if not config["onnx_path"]:
223
+ error_msg = f"No ONNX model specified. Provide --model path/to/model.onnx or ensure valid config file exists at '{args.config}'."
224
+ print(f"Error: {error_msg}")
225
+ logger.write(f"FATAL: {error_msg}")
226
+ sys.exit(1)
227
+
228
+ if not args.dry_run and not os.path.exists(config["onnx_path"]):
229
+ error_msg = f"ONNX file '{config['onnx_path']}' does not exist."
230
+ print(f"Error: {error_msg}")
231
+ logger.write(f"FATAL: {error_msg}")
232
+ sys.exit(1)
233
+
234
+ if not args.dry_run:
235
+ log_system_information()
236
+
237
+ model_name = config["model_name"]
238
+ onnx_path = config["onnx_path"]
239
+ batch_sizes = sorted(config["batch_sizes"])
240
+ precisions = config["precisions"]
241
+ iterations = config["iterations"]
242
+ warmup = config["warmup"]
243
+ input_name = config["input_name"]
244
+ input_shape = config["input_shape"]
245
+ workspace = config["workspace"]
246
+
247
+ # FIX 1: Strip accidental '1x' if passed via CLI or YAML
248
+ if input_shape.startswith("1x"):
249
+ input_shape = input_shape[2:]
250
+
251
+ min_shape = f"{input_name}:1x{input_shape}"
252
+ opt_shape = f"{input_name}:1x{input_shape}"
253
+ max_batch = max(batch_sizes)
254
+ max_shape = f"{input_name}:{max_batch}x{input_shape}"
255
+
256
+ # FIX 2: Isolate engine binaries in a models directory
257
+ models_dir = "models"
258
+ os.makedirs(models_dir, exist_ok=True)
259
+
260
+ print("\nTENSORRT EDGE BENCHMARK")
261
+ print(f"Target Model: {onnx_path}")
262
+ if args.dry_run:
263
+ print("[DRY-RUN MODE ENABLED: Simulating commands]")
264
+ print("=" * 75)
265
+ print(f"{'Precision':<10} | {'Batch':<7} | {'Latency (ms)':<15} | {'Throughput (FPS)':<18} | {'Peak RAM (MB)'}")
266
+ print("-" * 75)
267
+
268
+ logger.write("\nBenchmark Configuration Details:")
269
+ logger.write(f"Model: {model_name}")
270
+ logger.write(f"ONNX Path: {onnx_path}")
271
+ logger.write(f"Input Name: {input_name}")
272
+ logger.write(f"Input Shape: {input_shape}")
273
+ logger.write(f"Shape Profile: MIN={min_shape}, OPT={opt_shape}, MAX={max_shape}")
274
+ logger.write(f"Batch Matrix: {batch_sizes}")
275
+ logger.write(f"Precisions: {precisions}")
276
+ logger.write(f"Iterations: {iterations}, Warmup: {warmup} ms\n")
277
+
278
+ for precision in precisions:
279
+ engine_path = os.path.join(models_dir, f"{model_name}_{precision}.engine")
280
+
281
+ # FIX 3: 0-Byte engine check to prevent deserialization crashes
282
+ is_corrupted = not args.dry_run and os.path.exists(engine_path) and os.path.getsize(engine_path) == 0
283
+
284
+ if not os.path.exists(engine_path) or is_corrupted or args.dry_run:
285
+ logger.write("\n" + "#" * 90)
286
+ logger.write(f"BUILDING {precision.upper()} ENGINE")
287
+ logger.write("#" * 90)
288
+
289
+ print(f"Building {precision.upper()} engine... (Saving to {engine_path})")
290
+
291
+ # FIX 4: Jetson Memory Safety & Faster Compilation Flags
292
+ build_cmd = [
293
+ "trtexec",
294
+ f"--onnx={onnx_path}",
295
+ f"--saveEngine={engine_path}",
296
+ f"--minShapes={min_shape}",
297
+ f"--optShapes={opt_shape}",
298
+ f"--maxShapes={max_shape}",
299
+ "--tempdir=.",
300
+ "--tempfileControls=in_memory:deny,temporary:allow",
301
+ f"--memPoolSize=workspace:{workspace}",
302
+ "--builderOptimizationLevel=1",
303
+ "--timingCacheFile=trt.cache"
304
+ ]
305
+
306
+ if precision == "fp16":
307
+ build_cmd.append("--fp16")
308
+ elif precision == "int8":
309
+ build_cmd.extend(["--int8", "--fp16"])
310
+
311
+ run_command(build_cmd, dry_run=args.dry_run)
312
+
313
+ if not args.dry_run and (not os.path.exists(engine_path) or os.path.getsize(engine_path) == 0):
314
+ print(f"{precision.upper()} engine build failed or returned 0 bytes.")
315
+ logger.write(f"\n{precision.upper()} ENGINE BUILD FAILED.")
316
+ continue
317
+
318
+ logger.write(f"\n{precision.upper()} engine ready: {engine_path}")
319
+ else:
320
+ logger.write(f"\nFound existing valid {precision.upper()} engine in {models_dir}/. Skipping compilation.")
321
+
322
+ for bs in batch_sizes:
323
+ logger.write("\n" + "-" * 90)
324
+ logger.write(f"RUNNING INFERENCE | Precision={precision.upper()} | Batch={bs}")
325
+ logger.write("-" * 90)
326
+
327
+ shape = f"{input_name}:{bs}x{input_shape}"
328
+ infer_cmd = [
329
+ "trtexec",
330
+ f"--loadEngine={engine_path}",
331
+ f"--shapes={shape}",
332
+ f"--iterations={iterations}",
333
+ f"--warmUp={warmup}",
334
+ "--noDataTransfers",
335
+ ]
336
+
337
+ profiler = MemoryProfiler()
338
+ if not args.dry_run:
339
+ profiler.start()
340
+
341
+ infer_log = run_command(infer_cmd, dry_run=args.dry_run)
342
+
343
+ peak_ram = profiler.stop() if not args.dry_run else 0
344
+ metrics = extract_metrics(infer_log)
345
+
346
+ logger.write(f"\nExtracted metrics for {precision.upper()} Batch {bs}:")
347
+ logger.write(f"Latency: {metrics['latency_ms']}")
348
+ logger.write(f"Throughput: {metrics['fps']}")
349
+ logger.write(f"Peak RAM: {peak_ram} MB")
350
+
351
+ if metrics["latency_ms"] is not None and metrics["fps"] is not None:
352
+ print(f"{precision.upper():<10} | {bs:<7} | {metrics['latency_ms']:<15.2f} | {metrics['fps']:<18.2f} | {peak_ram}")
353
+ else:
354
+ print(f"{precision.upper():<10} | {bs:<7} | {'FAILED':<15} | {'FAILED':<18} | {peak_ram}")
355
+ logger.write("\nMetric extraction failed. Full trtexec output saved above.")
356
+
357
+ logger.write("\n" + "=" * 90)
358
+ logger.write("BENCHMARK COMPLETED")
359
+ logger.write(f"Completed: {datetime.now()}")
360
+ logger.write("=" * 90)
361
+ print("=" * 75)
362
+ print(f"Full execution log saved to: {logger.log_file}")
363
+
364
+ if __name__ == "__main__":
365
+ try:
366
+ main()
367
+ except Exception as e:
368
+ if logger:
369
+ logger.write("\n" + "=" * 90)
370
+ logger.write("UNHANDLED EXCEPTION")
371
+ logger.write("=" * 90)
372
+ logger.write(str(e))
373
+ print(f"Benchmark failed: {e}")
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: trench-mark
3
+ Version: 1.0.0
4
+ Summary: Automated TensorRT performance profiling for Jetson edge devices.
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pyyaml
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/trench-mark/__init__.py
4
+ src/trench-mark/cli.py
5
+ src/trench_mark.egg-info/PKG-INFO
6
+ src/trench_mark.egg-info/SOURCES.txt
7
+ src/trench_mark.egg-info/dependency_links.txt
8
+ src/trench_mark.egg-info/entry_points.txt
9
+ src/trench_mark.egg-info/requires.txt
10
+ src/trench_mark.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trench-mark = trench_mark.cli:main
@@ -0,0 +1 @@
1
+ trench-mark