Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:24:18

0001 # This file is part of the ACTS project.
0002 #
0003 # Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 #
0005 # This Source Code Form is subject to the terms of the Mozilla Public
0006 # License, v. 2.0. If a copy of the MPL was not distributed with this
0007 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 # detray imports
0010 from impl import read_benchmark_data, plot_benchmark_data, plot_scaling_data
0011 from options import (
0012     common_options,
0013     detector_io_options,
0014     random_track_generator_options,
0015     propagation_options,
0016     plotting_options,
0017 )
0018 from options import (
0019     parse_common_options,
0020     parse_detector_io_options,
0021     parse_plotting_options,
0022 )
0023 from plotting import pyplot_factory as plt_factory
0024 from utils import read_detector_name
0025 from utils import add_track_generator_args, add_propagation_args, add_detector_io_args
0026 
0027 # python imports
0028 import argparse
0029 from collections import namedtuple
0030 import os
0031 import platform
0032 import subprocess
0033 import sys
0034 
0035 # Known hardware backend types
0036 bknd_types = ["cpu", "cuda", "hip_amd", "hip_nvidia", "sycl"]
0037 
0038 # Patterns to be removed from processor names for simplicity
0039 bknd_patterns = [
0040     "CPU",
0041     "(TM)",
0042     "GHz",
0043     "@",
0044     "Core",
0045     "Processor",
0046     "with",
0047     "Radeon",
0048     "Graphics",
0049     "GB",
0050     "Laptop",
0051     "GPU",
0052     "GeForce",
0053 ]
0054 
0055 
0056 # Simpler hardware backend tag
0057 def __compactify_bknd_name(name, patterns=bknd_patterns):
0058     out = ""
0059     for sub_string in name.split(" "):
0060         if any(p in sub_string for p in patterns):
0061             continue
0062 
0063         out = f"{out} {sub_string}"
0064 
0065     # Remove preceding whitespace
0066     return name if len(out[1:]) == 0 else out[1:]
0067 
0068 
0069 # Peek into the benchmark context to get the name of the backend
0070 def __read_context_metadata(logging, input_dir, data_file):
0071     context, _ = read_benchmark_data(logging, input_dir, data_file)
0072     bknd = context["Backend"]
0073     bknd_name = __compactify_bknd_name(context["Backend Name"])
0074     algebra = context["Algebra-plugin"]
0075     setup = context["Detector Setup"]
0076     cores = 0
0077     if "Max no. Threads" in context:
0078         cores = int(context["Max no. Threads"]) / 2  # Adjust for hyperthreading
0079 
0080     return bknd, bknd_name, algebra, setup, cores
0081 
0082 
0083 # Parse and check the user provided input data files
0084 def __parse_input_data_files(args, logging):
0085     input_data_files = []
0086     for file in args.data_files:
0087         if not os.path.isfile(file):
0088             logging.error(f"File not found! ({file})")
0089             sys.exit(1)
0090 
0091         _, file_extension = os.path.splitext(file)
0092 
0093         if file_extension != ".json":
0094             logging.error("Wrong file extension. Should be '.json': " + file)
0095             sys.exit(1)
0096 
0097         input_data_files.append(file)
0098 
0099     return input_data_files
0100 
0101 
0102 # Gather and check benchmark executables and resulting data files for every
0103 # hardware backend type and algebra plugin
0104 def __generate_benchmark_dict(
0105     args,
0106     logging,
0107     bindir,
0108     det_name,
0109     input_data_files,
0110     algebra_plugins,
0111     bench_type="benchmark",
0112 ):
0113     # Bundle benchmark metadata
0114     benchmark_metadata = namedtuple(
0115         "benchmark_metadata",
0116         "name algebra setup bin file cores",
0117         defaults=["Unknown", "Unknown Algebra", "", None, None, 0],
0118     )
0119 
0120     # Resulting dictionary
0121     benchmarks = {"CPU": {}}
0122     if args.cuda:
0123         benchmarks["CUDA"] = {}
0124     if args.hip_amd:
0125         benchmarks["HIP_AMD"] = {}
0126     if args.hip_nvidia:
0127         benchmarks["HIP_NVIDIA"] = {}
0128     if args.sycl:
0129         # benchmarks["SYCL"] = {}
0130         logging.error(f"SYCL propagation {bench_type} is not implemented")
0131 
0132     # Register the input data files for plotting
0133     for f in input_data_files:
0134         if not os.path.isfile(f):
0135             logging.error(f"File does not exist: {f}")
0136             sys.exit(1)
0137 
0138         # Add file to benchmark dict
0139         input_dir = os.path.dirname(f)
0140         file_name = os.path.basename(f)
0141         bknd, bknd_name, algebra, setup, cores = __read_context_metadata(
0142             logging, input_dir, file_name
0143         )
0144 
0145         if bknd not in benchmarks:
0146             benchmarks[bknd] = {}
0147 
0148         if bknd_name not in benchmarks[bknd]:
0149             benchmarks[bknd][bknd_name] = []
0150 
0151         context, _ = read_benchmark_data(logging, input_dir, file_name)
0152         if bench_type in context["executable"]:
0153             benchmarks[bknd][bknd_name].append(
0154                 benchmark_metadata(
0155                     name=bknd_name, algebra=algebra, setup=setup, file=f, cores=cores
0156                 )
0157             )
0158 
0159     # Register benchmarks to be run
0160     for bknd, metadata_dict in benchmarks.items():
0161         # No benchmarks to be run
0162         if len(algebra_plugins) == 0:
0163             break
0164 
0165         # Try to find the processor name
0166         bknd_name = "Unknown"
0167         if bknd == "CUDA" or bknd == "HIP_NVIDIA" or bknd == "SYCL":
0168             bknd_name = "Unknown NVIDIA GPU"
0169             try:
0170                 import nvidia_smi
0171 
0172                 try:
0173                     nvidia_smi.nvmlInit()
0174                     handle = nvidia_smi.nvmlDeviceGetHandleByIndex(0)
0175 
0176                     bknd_name = nvidia_smi.nvmlDeviceGetName(handle)
0177 
0178                     nvidia_smi.nvmlShutdown()
0179 
0180                 except NVMLError as e:
0181                     print(e)
0182 
0183             except ModuleNotFoundError:
0184                 print(
0185                     "Python module 'nvidia_smi' is not installed: Falling back on running nvidia-smi as subprocess"
0186                 )
0187                 # Try to get the GPU name by calling nvidia smi as subprocess
0188                 try:
0189                     gpu_str = str(
0190                         subprocess.check_output(
0191                             [
0192                                 "nvidia-smi",
0193                                 "--query-gpu",
0194                                 "name",
0195                                 "--format=csv,noheader",
0196                             ]
0197                         )
0198                     )
0199                     gpu_str = __compactify_bknd_name(gpu_str[2:])
0200 
0201                     # Strip some unwanted characters
0202                     if gpu_str[-1] == '"' or gpu_str[-1] == "'":
0203                         gpu_str = gpu_str[:-1]
0204                     if gpu_str[-2:] == "\\n":
0205                         gpu_str = gpu_str[:-2]
0206 
0207                     if len(gpu_str) != 0:
0208                         bknd_name = gpu_str.rstrip(os.linesep)
0209 
0210                 except Exception as e:
0211                     # Name remains 'Unknown'
0212                     print(e)
0213 
0214             bknd_name = f"{bknd.removesuffix('_NVIDIA')} {bknd_name}"
0215 
0216         elif bknd == "HIP_AMD":
0217             bknd_name = "Unknown AMD GPU"
0218             try:
0219                 # Get AMD GPU info
0220                 from amdsmi import (
0221                     amdsmi_init,
0222                     amdsmi_shut_down,
0223                     amdsmi_get_processor_handles,
0224                     amdsmi_get_gpu_asic_info,
0225                     AmdSmiException,
0226                 )
0227 
0228                 try:
0229                     amdsmi_init()
0230                     devices = amdsmi_get_processor_handles()
0231                     if len(devices) == 0:
0232                         print("No AMD GPUs on machine")
0233                     else:
0234                         asic_info = amdsmi_get_gpu_asic_info(devices[0])
0235                         bknd_name = asic_info["market_name"]
0236 
0237                 except AmdSmiException as e:
0238                     print(e)
0239                 finally:
0240                     try:
0241                         amdsmi_shut_down()
0242                     except AmdSmiException as e:
0243                         print(e)
0244 
0245             except ModuleNotFoundError:
0246                 print(
0247                     "Python module 'amdsmi' is not installed: Cannot find AMD GPU name"
0248                 )
0249 
0250             bknd_name = f"{bknd.removesuffix('_AMD')} {bknd_name}"
0251         else:
0252             bknd_name = __compactify_bknd_name(platform.processor())
0253 
0254         if bknd_name not in metadata_dict:
0255             metadata_dict[bknd_name] = []
0256 
0257         # The algebra plugins that have already been registered from input file
0258         registered_algebra = [data.algebra for data in metadata_dict[bknd_name]]
0259 
0260         # The requested algebra-plugins
0261         for algebra in algebra_plugins:
0262             # Prefer reading from file if it has been provided by the user
0263             if algebra in registered_algebra:
0264                 continue
0265 
0266             # Parse the detector setup
0267             setup = ""
0268             add_delim = lambda s: s + ", "
0269             if not args.grid_file:
0270                 setup = setup + "no grids"
0271             if not args.material_file:
0272                 if len(setup) != 0:
0273                     setup = add_delim(setup)
0274                 setup = setup + "no mat."
0275             if not args.covariance_transport:
0276                 if len(setup) != 0:
0277                     setup = add_delim(setup)
0278                 setup = setup + "no cov."
0279 
0280             binary = (
0281                 f"{bindir}/detray_propagation_{bench_type}_{bknd.lower()}_{algebra}"
0282             )
0283             file_bknd_name = bknd_name.replace(" ", "_")
0284             file_setup = setup.replace(" ", "_")
0285             file_setup = file_setup.replace(",", "")
0286             file_setup = file_setup.replace(".", "")
0287             data_file = f"{det_name}_{bench_type}_{bknd.lower()}_{file_bknd_name}_{algebra}_{file_setup}.json"
0288             cores = os.cpu_count() / 2  # Correct for logical cores
0289 
0290             metadata = benchmark_metadata(
0291                 name=bknd_name,
0292                 algebra=algebra,
0293                 setup=setup,
0294                 file=data_file,
0295                 cores=cores,
0296             )
0297 
0298             # If the results should not be read from file, run the benchmark
0299             if data_file not in (os.path.basename(f) for f in input_data_files):
0300                 # Register binary if it exists
0301                 if os.path.isdir(bindir) and os.path.isfile(binary):
0302                     metadata = metadata._replace(bin=binary)
0303                 else:
0304                     logging.warning(
0305                         f"Propagation {bench_type} binary not found! ({binary})"
0306                     )
0307                     continue
0308 
0309             metadata_dict[bknd_name].append(metadata)
0310 
0311     return benchmarks
0312 
0313 
0314 # Run all benchmarks in 'benchmark_dict' that were registered with a binary file
0315 def __run_benchmarks(benchmark_dict, args_list, benchmark_options):
0316     for bknd, metadata_dict in benchmark_dict.items():
0317         for bknd_name, metadata_list in metadata_dict.items():
0318             for metadata in metadata_list:
0319                 if metadata.bin is not None:
0320                     subprocess.run(
0321                         [
0322                             metadata.bin,
0323                             f"--bknd_name={bknd_name}",
0324                             f"--benchmark_out=./{metadata.file}",
0325                         ]
0326                         + benchmark_options
0327                         + args_list
0328                     )
0329 
0330 
0331 def __main__():
0332 
0333     # ---------------------------------------------------------------arg parsing
0334 
0335     descr = "Detray Propagation Benchmark"
0336 
0337     # Define options
0338     parent_parsers = [
0339         common_options(descr),
0340         detector_io_options(),
0341         random_track_generator_options(),
0342         propagation_options(),
0343         plotting_options(),
0344     ]
0345 
0346     parser = argparse.ArgumentParser(description=descr, parents=parent_parsers)
0347 
0348     parser.add_argument(
0349         "--bindir",
0350         "-bin",
0351         help=("Directory containing the benchmark executables"),
0352         default="./bin",
0353         type=str,
0354     )
0355     parser.add_argument(
0356         "--cuda",
0357         help=("Run the CUDA propagation benchmarks."),
0358         action="store_true",
0359         default=False,
0360     )
0361     parser.add_argument(
0362         "--hip_amd",
0363         help=("Run the HIP AMD propagation benchmarks."),
0364         action="store_true",
0365         default=False,
0366     )
0367     parser.add_argument(
0368         "--hip_nvidia",
0369         help=("Run the HIP NVIDIA propagation benchmarks."),
0370         action="store_true",
0371         default=False,
0372     )
0373     parser.add_argument(
0374         "--sycl",
0375         help=("Run the SYCL propagation benchmarks (Not implemented)."),
0376         action="store_true",
0377         default=False,
0378     )
0379     parser.add_argument(
0380         "--sort_tracks",
0381         help=("Sort the track samples by theta."),
0382         action="store_true",
0383         default=False,
0384     )
0385     parser.add_argument(
0386         "--benchmark_repetitions",
0387         help=("Number of repeated benchmark runs."),
0388         default=2,
0389         type=int,
0390     )
0391     parser.add_argument(
0392         "--algebra_plugins",
0393         "-ap",
0394         nargs="*",
0395         help=(
0396             "Algebra plugins to be benchmarked (the plugin must be enabled at build time)."
0397         ),
0398         default=[],
0399         type=str,
0400     )
0401     parser.add_argument(
0402         "--data_files",
0403         "-f",
0404         nargs="*",
0405         help=("Read the benchmark results from a Google benchmark json file instead."),
0406         default=[],
0407         type=str,
0408     )
0409 
0410     # Parse options
0411     args = parser.parse_args()
0412 
0413     logging = parse_common_options(args, descr)
0414     parse_detector_io_options(args, logging)
0415     input_dir, out_dir, out_format = parse_plotting_options(args, logging)
0416 
0417     # Check bin path
0418     bindir = args.bindir.strip("/")
0419 
0420     # Get detector name
0421     det_name = read_detector_name(args.geometry_file, logging)
0422     logging.debug("Detector: " + det_name)
0423 
0424     # Check user provided benchmark result files
0425     input_data_files = __parse_input_data_files(args, logging)
0426 
0427     # Unique set of algebra plugins to be included in the plots
0428     algebra_plugins = set(args.algebra_plugins)
0429 
0430     if len(algebra_plugins) == 0 and len(input_data_files) == 0:
0431         logging.error(
0432             "No data file for plotting and no algebra plugins specified to run benchmarks for. Quitting"
0433         )
0434         sys.exit(1)
0435 
0436     # Get dictionary of benchmark files per hardware backend type
0437     benchmarks = __generate_benchmark_dict(
0438         args, logging, bindir, det_name, input_data_files, algebra_plugins, "benchmark"
0439     )
0440 
0441     scaling = __generate_benchmark_dict(
0442         args, logging, bindir, det_name, input_data_files, algebra_plugins, "scaling"
0443     )
0444 
0445     # -----------------------------------------------------------------------run
0446 
0447     # Pass on the options for the detray benchmark executable
0448     args_list = []
0449 
0450     # Add parsed options to argument list
0451     add_detector_io_args(args_list, args)
0452     add_track_generator_args(args_list, args)
0453     add_propagation_args(args_list, args)
0454 
0455     if args.sort_tracks:
0456         args_list.append("--sort_tracks")
0457 
0458     logging.debug(args_list)
0459 
0460     # Pass on the options for google benchmark
0461     benchmark_options = [
0462         f"--benchmark_repetitions={args.benchmark_repetitions}",
0463         # "--benchmark_min_time=50x", taken from user guide, but does not work...
0464         "--benchmark_display_aggregates_only=true",
0465         # "--benchmark_time_unit=ms", taken from user guide, but does not work...
0466         "--benchmark_out_format=json",
0467     ]
0468 
0469     # Run requested benchmark and scaling tests
0470     __run_benchmarks(benchmarks, args_list, benchmark_options)
0471     __run_benchmarks(scaling, args_list, benchmark_options)
0472 
0473     # ----------------------------------------------------------------------plot
0474 
0475     logging.info("Generating plots...\n")
0476 
0477     plot_factory = plt_factory(out_dir, logging)
0478 
0479     def make_label(algebra, setup):
0480         label = f"{algebra}"
0481         if len(setup) != 0:
0482             label = label + f" ({setup})"
0483         return label
0484 
0485     # Plot all data files per hardware backend
0486     # (comparison of different algebra-plugins)
0487     for bknd, metadata_dict in benchmarks.items():
0488         for bknd_name, metadata_list in metadata_dict.items():
0489             for metadata in metadata_list:
0490                 # Get file list and plot labels
0491                 files = [metadata.file for metadata in metadata_list]
0492                 plot_labels = [
0493                     make_label(metadata.algebra, metadata.setup)
0494                     for metadata in metadata_list
0495                 ]
0496 
0497                 file_bknd_name = bknd_name.replace(" ", "_")
0498                 plot_benchmark_data(
0499                     logging,
0500                     input_dir,
0501                     det_name,
0502                     files,
0503                     plot_labels,
0504                     f"hardware backend: {bknd} ({bknd_name})",
0505                     f"prop_benchmark_algebra-plugin_comparison_{bknd}_{file_bknd_name}",
0506                     plot_factory,
0507                     out_format,
0508                 )
0509 
0510     # Plot results for different hardware backends using the same algebra plugin
0511     # (comparison of different hardware backends)
0512     discovered_algs = []
0513     for bknd, metadata_dict in benchmarks.items():
0514         for bknd_name, metadata_list in metadata_dict.items():
0515             for metadata in metadata_list:
0516                 discovered_algs.append(metadata.algebra)
0517 
0518     discovered_algs = set(discovered_algs)
0519 
0520     # Build up the list of data files and corresponding plot labels
0521     for algebra in discovered_algs:
0522         data_files_per_plugin = []
0523         plot_labels = []
0524 
0525         for bknd, metadata_dict in benchmarks.items():
0526 
0527             for bknd_name, metadata_list in metadata_dict.items():
0528 
0529                 for metadata in metadata_list:
0530                     if algebra == metadata.algebra:
0531                         data_files_per_plugin.append(metadata.file)
0532                         label = make_label(f"{bknd_name}", metadata.setup)
0533                         plot_labels.append(label)
0534 
0535         plot_benchmark_data(
0536             logging,
0537             input_dir,
0538             det_name,
0539             data_files_per_plugin,
0540             plot_labels,
0541             f"algebra-plugin: {algebra}",
0542             f"prop_benchmark_backend_comparison_{algebra}",
0543             plot_factory,
0544             out_format,
0545         )
0546 
0547     # Plot all data files per hardware backend for propagation scaling
0548     for bknd, metadata_dict in scaling.items():
0549         for bknd_name, metadata_list in metadata_dict.items():
0550             for metadata in metadata_list:
0551                 # Get file list and plot labels
0552                 files = [metadata.file for metadata in metadata_list]
0553                 plot_labels = [
0554                     make_label(metadata.algebra, metadata.setup)
0555                     for metadata in metadata_list
0556                 ]
0557 
0558                 plot_scaling_data(
0559                     logging,
0560                     input_dir,
0561                     det_name,
0562                     files,
0563                     plot_labels,
0564                     f"hardware backend: {bknd} ({bknd_name})",
0565                     plot_factory,
0566                     out_format,
0567                     [1, 2, 4, 8, 16, 24, 32, 48, 64, 96, 128, 256],
0568                     metadata.cores,
0569                 )
0570 
0571 
0572 # ------------------------------------------------------------------------------
0573 
0574 if __name__ == "__main__":
0575     __main__()
0576 
0577 # ------------------------------------------------------------------------------