carm-roofline 1.0.0.dev5__tar.gz → 1.0.1.dev0__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.
Files changed (99) hide show
  1. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/PKG-INFO +3 -2
  2. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/architecture.py +6 -7
  3. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/config.py +8 -8
  4. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/detect.py +3 -3
  5. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/memory.py +7 -7
  6. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/benchmarking.py +17 -19
  7. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/x86.py +1 -1
  8. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/interface.py +6 -2
  9. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/memory.py +16 -2
  10. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/roofline.py +1 -1
  11. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm.py +45 -39
  12. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/PKG-INFO +3 -2
  13. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/SOURCES.txt +9 -0
  14. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/requires.txt +1 -0
  15. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/top_level.txt +2 -0
  16. carm_roofline-1.0.1.dev0/error.py +5 -0
  17. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/exec_interface.py +0 -2
  18. carm_roofline-1.0.1.dev0/profiling/__init__.py +151 -0
  19. carm_roofline-1.0.1.dev0/profiling/aggregation.py +215 -0
  20. carm_roofline-1.0.1.dev0/profiling/backends.py +179 -0
  21. carm_roofline-1.0.1.dev0/profiling/config.py +114 -0
  22. carm_roofline-1.0.1.dev0/profiling/loaders.py +172 -0
  23. carm_roofline-1.0.1.dev0/profiling/metrics.py +429 -0
  24. carm_roofline-1.0.1.dev0/profiling/model.py +143 -0
  25. carm_roofline-1.0.1.dev0/profiling/output.py +131 -0
  26. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/pyproject.toml +7 -4
  27. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/run_config.py +9 -1
  28. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test/test_memory_suite_generation.py +1 -0
  29. carm_roofline-1.0.1.dev0/workspace.py +44 -0
  30. carm_roofline-1.0.0.dev5/error.py +0 -5
  31. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/LICENSE +0 -0
  32. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/README.md +0 -0
  33. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/__init__.py +0 -0
  34. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/arm.py +0 -0
  35. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/frequency.py +0 -0
  36. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/riscv.py +0 -0
  37. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/features.c +0 -0
  38. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/frequency.h +0 -0
  39. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/vlen.c +0 -0
  40. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/frequency.c +0 -0
  41. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/frequency.h +0 -0
  42. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/rvv_version.c +0 -0
  43. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/vlen.c +0 -0
  44. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/cache.c +0 -0
  45. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/features.c +0 -0
  46. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/frequency.h +0 -0
  47. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
  48. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/architecture/x86.py +0 -0
  49. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/arguments.py +0 -0
  50. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/__init__.py +0 -0
  51. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/benchmark.py +0 -0
  52. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/__init__.py +0 -0
  53. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/arm.py +0 -0
  54. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/__init__.py +0 -0
  55. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/data_type.py +0 -0
  56. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/instruction.py +0 -0
  57. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/operation.py +0 -0
  58. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/register.py +0 -0
  59. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/isa.py +0 -0
  60. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/parameters.py +0 -0
  61. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/generation/riscv.py +0 -0
  62. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/__init__.py +0 -0
  63. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/arithmetic.py +0 -0
  64. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/base.py +0 -0
  65. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/common.py +0 -0
  66. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/memory.py +0 -0
  67. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/memory_sweep.py +0 -0
  68. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/mixed.py +0 -0
  69. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/output/roofline.py +0 -0
  70. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/result.py +0 -0
  71. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/__init__.py +0 -0
  72. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/arithmetic.py +0 -0
  73. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/base.py +0 -0
  74. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/memory_sweep.py +0 -0
  75. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/benchmark/suites/mixed.py +0 -0
  76. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/dependency_links.txt +0 -0
  77. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/entry_points.txt +0 -0
  78. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/context.py +0 -0
  79. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/__init__.py +0 -0
  80. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/assets/CARM_icon.svg +0 -0
  81. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/assets/CHAMP_logo.svg +0 -0
  82. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/assets/menu_icon.png +0 -0
  83. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/assets/style.css +0 -0
  84. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/config.py +0 -0
  85. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/dashboard.py +0 -0
  86. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/gui/gui_utils.py +0 -0
  87. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/output_utils.py +0 -0
  88. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/results_paths.py +0 -0
  89. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/setup.cfg +0 -0
  90. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test/test_isa_codegen_integration.py +0 -0
  91. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test/test_output_module.py +0 -0
  92. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test/test_typed_benchmarks.py +0 -0
  93. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test_bench/__init__.py +0 -0
  94. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test_bench/builder.py +0 -0
  95. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test_bench/test_bench.c +0 -0
  96. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test_bench/test_bench.h +0 -0
  97. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/test_bench/wrapper.inl +0 -0
  98. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/units.py +0 -0
  99. {carm_roofline-1.0.0.dev5 → carm_roofline-1.0.1.dev0}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.0.dev5
3
+ Version: 1.0.1.dev0
4
4
  Summary: CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit
5
5
  Author: CHaMP Team
6
6
  License: GNU LESSER GENERAL PUBLIC LICENSE
@@ -518,7 +518,7 @@ Classifier: Intended Audience :: Science/Research
518
518
  Classifier: Topic :: Software Development :: Testing
519
519
  Classifier: Topic :: System :: Benchmark
520
520
  Classifier: Programming Language :: Python :: 3
521
- Classifier: Programming Language :: Python :: 3.10
521
+ Classifier: Programming Language :: Python :: 3.9
522
522
  Classifier: Operating System :: POSIX :: Linux
523
523
  Requires-Python: >=3.9
524
524
  Description-Content-Type: text/markdown
@@ -529,6 +529,7 @@ Requires-Dist: numpy>=2.0.0
529
529
  Requires-Dist: matplotlib>=3.7.2
530
530
  Requires-Dist: tomli>=2.0.0
531
531
  Requires-Dist: platformdirs>=4.0.0
532
+ Requires-Dist: argcomplete>=3.0.0
532
533
  Provides-Extra: gui
533
534
  Requires-Dist: dash[diskcache]; extra == "gui"
534
535
  Requires-Dist: dash-bootstrap-components; extra == "gui"
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from arguments import InsertsArguments, positive_int
8
8
  from benchmark import generation as bench_gen
9
- from error import ConfigurationError
9
+ from error import UserError
10
10
  from output_utils import configure_verbosity, debug, detail, format_if_container, warn
11
11
  from units import Frequency
12
12
 
@@ -28,14 +28,14 @@ def check_isa_compatibility(selected_isas: list[type[BaseISA]]) -> None:
28
28
  families.add(family)
29
29
 
30
30
  if len(families) > 1:
31
- raise ConfigurationError(f"Incompatible ISA families selected: {', '.join(families)}")
31
+ raise UserError(f"Incompatible ISA families selected: {', '.join(families)}")
32
32
 
33
33
  # Check for special incompatibilities within the same family
34
34
  isa_set = set(selected_isas)
35
35
  for incompatible_pair in bench_gen.INCOMPATIBLE_ISAS:
36
36
  if incompatible_pair.issubset(isa_set):
37
37
  pair_names = ", ".join(isa.name for isa in incompatible_pair)
38
- raise ConfigurationError(f"Incompatible ISAs selected: {pair_names}")
38
+ raise UserError(f"Incompatible ISAs selected: {pair_names}")
39
39
 
40
40
 
41
41
  def positive_po2_int(arg: str) -> int:
@@ -278,20 +278,18 @@ class Architecture(InsertsArguments):
278
278
  parser.add_argument(
279
279
  "-i",
280
280
  "--isa",
281
- nargs="*",
281
+ nargs="+",
282
282
  choices=list(ISA_NAME_TO_CLASS.keys()),
283
283
  help="Set of ISAs to test. (Default: automatically detects all available ISAs)",
284
284
  )
285
285
  parser.add_argument(
286
286
  "-l",
287
287
  "--vector-length",
288
- nargs="?",
289
288
  type=positive_po2_int,
290
289
  help="Vector register length in bytes. (Default: automatically detected)",
291
290
  )
292
291
  parser.add_argument(
293
292
  "--vector-lmul",
294
- nargs="?",
295
293
  type=int,
296
294
  choices=(1, 2, 4, 8),
297
295
  help="Vector register group modifier (LMUL). RISC-V specific.",
@@ -309,12 +307,13 @@ class Architecture(InsertsArguments):
309
307
  "--emit-config",
310
308
  type=str,
311
309
  metavar="PATH",
310
+ nargs="?",
311
+ const="topology.toml",
312
312
  help="Generate a template TOML configuration file at the specified path and exit. "
313
313
  "Edit the template and pass it back with --topology-config.",
314
314
  )
315
315
  parser.add_argument(
316
316
  "--frequency",
317
- nargs="?",
318
317
  type=Frequency.from_argparse,
319
318
  help="Processor frequency, e.g. '2GHz', '3200mhz' (Default: automatically detected if possible)",
320
319
  )
@@ -18,7 +18,7 @@ from __future__ import annotations
18
18
  from pathlib import Path
19
19
  from typing import cast
20
20
 
21
- from error import ConfigurationError
21
+ from error import UserError
22
22
  from units import Bytes
23
23
 
24
24
  from .memory import SimpleMemoryTopology
@@ -81,13 +81,13 @@ def load_memory_topology_from_toml(config_path: Path | str) -> SimpleMemoryTopol
81
81
  """
82
82
  config_path = Path(config_path)
83
83
  if not config_path.exists():
84
- raise ConfigurationError(f"Config file not found: {config_path}")
84
+ raise UserError(f"Config file not found: {config_path}")
85
85
 
86
86
  try:
87
87
  with open(config_path, "rb") as f:
88
88
  config = tomllib.load(f)
89
89
  except Exception as e:
90
- raise ConfigurationError(f"Failed to parse TOML file '{config_path}': {e}") from e
90
+ raise UserError(f"Failed to parse TOML file '{config_path}': {e}") from e
91
91
 
92
92
  total_cpus = _read_optional_int(config, "total_cpus")
93
93
  smt_degree = cast(int, _read_optional_int(config, "smt_degree", 1))
@@ -96,7 +96,7 @@ def load_memory_topology_from_toml(config_path: Path | str) -> SimpleMemoryTopol
96
96
  # Extract cache levels
97
97
  cache_levels_data = config.get("cache_levels", [])
98
98
  if not isinstance(cache_levels_data, list) or not cache_levels_data:
99
- raise ConfigurationError("Configuration must contain at least one [[cache_levels]] section")
99
+ raise UserError("Configuration must contain at least one [[cache_levels]] section")
100
100
 
101
101
  # Parse and validate each cache level (processed in document order; order determines level numbers)
102
102
  level_sizes: list[Bytes] = []
@@ -104,17 +104,17 @@ def load_memory_topology_from_toml(config_path: Path | str) -> SimpleMemoryTopol
104
104
 
105
105
  for level_dict in cache_levels_data:
106
106
  if not isinstance(level_dict, dict):
107
- raise ConfigurationError(f"Each [[cache_levels]] entry must be a table, got: {type(level_dict).__name__}")
107
+ raise UserError(f"Each [[cache_levels]] entry must be a table, got: {type(level_dict).__name__}")
108
108
  try:
109
109
  instances = level_dict["instances"]
110
110
  size_str = level_dict["size"]
111
111
  except KeyError as e:
112
- raise ConfigurationError(f"Cache level section missing required field: {e}") from e
112
+ raise UserError(f"Cache level section missing required field: {e}") from e
113
113
 
114
114
  if not isinstance(instances, int):
115
- raise ConfigurationError(f"'instances' must be an integer, got: {instances!r}")
115
+ raise UserError(f"'instances' must be an integer, got: {instances!r}")
116
116
  if not isinstance(size_str, str):
117
- raise ConfigurationError(f"'size' must be a string, got: {size_str!r}")
117
+ raise UserError(f"'size' must be a string, got: {size_str!r}")
118
118
 
119
119
  size = Bytes.from_string(size_str)
120
120
  level_sizes.append(size)
@@ -15,7 +15,7 @@ from dataclasses import dataclass
15
15
  from pathlib import Path
16
16
  from typing import TYPE_CHECKING, Any
17
17
 
18
- from error import ConfigurationError
18
+ from error import UserError
19
19
  from output_utils import debug, detail, warn
20
20
  from units import Frequency
21
21
 
@@ -244,7 +244,7 @@ def detect_cache(ctx: TestContext) -> dict[str, Any]:
244
244
  else:
245
245
  raise ValueError("MemoryTopology parsed but contains no CPU data")
246
246
  except Exception as e:
247
- raise ConfigurationError(
247
+ raise UserError(
248
248
  f"Failed to parse native system memory topology from /sys: {e}. "
249
249
  "For non-native execution or when sysfs is unavailable, "
250
250
  "please provide cache hierarchy explicitly with --topology-config."
@@ -424,7 +424,7 @@ def native_detect(threads: int = 1) -> DetectedArchitecture:
424
424
  """
425
425
  machine = platform.machine().lower()
426
426
  if machine == "":
427
- raise ConfigurationError(
427
+ raise UserError(
428
428
  "Unable to detect machine architecture (platform.machine() returned empty string). Please specify the "
429
429
  "architecture manually (--isa <arch>)."
430
430
  )
@@ -9,7 +9,7 @@ from enum import Enum
9
9
  from pathlib import Path
10
10
  from typing import Protocol, runtime_checkable
11
11
 
12
- from error import ConfigurationError
12
+ from error import UserError
13
13
  from output_utils import debug, format_if_container
14
14
  from units import Bytes
15
15
 
@@ -168,26 +168,26 @@ class SimpleMemoryTopology:
168
168
  """
169
169
 
170
170
  if len(level_sizes) != len(instances_per_level):
171
- raise ConfigurationError(
171
+ raise UserError(
172
172
  "Level sizes and instances_per_level must have same length: "
173
173
  f"{len(level_sizes)} vs {len(instances_per_level)}"
174
174
  )
175
175
  if not level_sizes:
176
- raise ConfigurationError("At least one cache level must be provided")
176
+ raise UserError("At least one cache level must be provided")
177
177
 
178
178
  if any(inst <= 0 for inst in instances_per_level):
179
- raise ConfigurationError("All values in instances_per_level must be positive integers")
179
+ raise UserError("All values in instances_per_level must be positive integers")
180
180
 
181
181
  if total_cpus is not None and total_cpus <= 0:
182
- raise ConfigurationError(f"total_cpus must be positive when provided, got {total_cpus}")
182
+ raise UserError(f"total_cpus must be positive when provided, got {total_cpus}")
183
183
 
184
184
  if cpu_offset < 0:
185
- raise ConfigurationError(f"cpu_offset must be >= 0, got {cpu_offset}")
185
+ raise UserError(f"cpu_offset must be >= 0, got {cpu_offset}")
186
186
 
187
187
  if total_cpus is not None:
188
188
  for idx, instances in enumerate(instances_per_level, 1):
189
189
  if instances > total_cpus:
190
- raise ConfigurationError(
190
+ raise UserError(
191
191
  f"Cache level {idx}: instances ({instances}) cannot exceed total_cpus ({total_cpus})"
192
192
  )
193
193
 
@@ -11,7 +11,7 @@ from enum import Enum
11
11
  from arguments import InsertsArguments, enum_action, positive_float, positive_int
12
12
  from benchmark.generation.code_gen.operation import ArithmeticOperation
13
13
  from output_utils import warn
14
- from units import Operations
14
+ from units import Bytes, Operations
15
15
 
16
16
  from .generation import DataType
17
17
 
@@ -66,27 +66,29 @@ def ld_st_ratio_type(arg: str) -> LoadStoreRatio:
66
66
  return LoadStoreRatio(ld, st)
67
67
 
68
68
 
69
- def mem_test_size_type(arg: str) -> int | str:
69
+ def mem_test_size_type(arg: str) -> Bytes | None:
70
70
  """Parse a memory test size specifier.
71
71
 
72
72
  Accepts either:
73
- - A positive integer (KiB)
73
+ - A quantity with a binary unit (e.g. "16 KiB", "2 MiB", "8 GiB")
74
74
  - The string "auto" (case-insensitive) for automatic sizing
75
75
 
76
76
  Returns:
77
- int (KiB value) or str (literal "auto")
77
+ Bytes or None (for "auto")
78
78
 
79
79
  Raises:
80
80
  argparse.ArgumentTypeError on invalid input.
81
81
  """
82
82
  s = str(arg).strip().lower()
83
83
  if s == "auto":
84
- return "auto"
84
+ return None
85
85
  try:
86
- return positive_int(arg)
87
- except (ValueError, argparse.ArgumentTypeError) as e:
86
+ return Bytes.from_argparse(arg)
87
+ except argparse.ArgumentTypeError:
88
+ raise
89
+ except Exception as e:
88
90
  raise argparse.ArgumentTypeError(
89
- f"invalid memory test size: {arg!r}. Must be a positive integer or 'auto'"
91
+ f"invalid memory test size: {arg!r}. Expected a binary unit string like '16 KiB', '2 MiB', or 'auto'. ({e})"
90
92
  ) from e
91
93
 
92
94
 
@@ -106,7 +108,7 @@ class Benchmarking(InsertsArguments):
106
108
  self.num_ops: Operations = Operations(args.num_ops)
107
109
  self.ld_st_ratio: LoadStoreRatio = args.ld_st_ratio
108
110
  self.arith_mem_ratio: int = args.arith_mem_ratio
109
- self.mem_test_sizes: list[int | str] | None = args.mem_test_sizes
111
+ self.mem_test_sizes: list[Bytes | None] | None = args.mem_test_sizes
110
112
  self.verbose: int = args.verbose
111
113
  self.test_time: float = args.test_time
112
114
  if self.test_time < 10.0:
@@ -132,7 +134,6 @@ class Benchmarking(InsertsArguments):
132
134
  "-m",
133
135
  "--mem-target",
134
136
  default="all",
135
- nargs="?",
136
137
  choices=["L1", "L2", "L3", "DRAM", "all"],
137
138
  help="Target memory level for 'memory' and 'mixed' tests (Default: all)",
138
139
  )
@@ -140,7 +141,6 @@ class Benchmarking(InsertsArguments):
140
141
  "-o",
141
142
  "--num-ops",
142
143
  default=32 * 1024,
143
- nargs="?",
144
144
  type=positive_int,
145
145
  help="Number of arithmetic operations to perform in the arithmetic test (Default: 32768)",
146
146
  )
@@ -148,7 +148,6 @@ class Benchmarking(InsertsArguments):
148
148
  "-d",
149
149
  "--data-type",
150
150
  default=DataType.f32,
151
- nargs="?",
152
151
  action=enum_action(DataType),
153
152
  help="Data type for benchmark operations (Default: f32)",
154
153
  )
@@ -171,7 +170,6 @@ class Benchmarking(InsertsArguments):
171
170
  parser.add_argument(
172
171
  "--ld-st-ratio",
173
172
  default=LoadStoreRatio(2, 1),
174
- nargs="?",
175
173
  type=ld_st_ratio_type,
176
174
  help="Load-to-store ratio for memory access patterns. Format: 'LD:ST' (e.g., '2:1') or a single "
177
175
  "integer for 'N:1' ratio. Use '1:0' for load-only or '0:1' for store-only tests. (Default: 2:1)",
@@ -179,22 +177,22 @@ class Benchmarking(InsertsArguments):
179
177
  parser.add_argument(
180
178
  "--arith-mem-ratio",
181
179
  default=2,
182
- nargs="?",
183
180
  type=positive_int,
184
181
  help="Ratio between arithmetic and memory operations for 'mixed' test (Default: 2)",
185
182
  )
186
183
  parser.add_argument(
187
184
  "--mem-test-sizes",
188
- nargs="*",
185
+ nargs="+",
189
186
  type=mem_test_size_type,
190
- help="Size of the test arrays for each memory level in KiB. 'auto' can be used to automatically determine "
191
- "sizes based on the cache sizes, e.g. '--mem-test-sizes 32 256 2048 65536' / '--mem-test-sizes auto auto "
192
- "2048 auto' for L1, L2, L3, DRAM tests respectively. (Default: auto for all levels)",
187
+ help="Size of the test arrays for each memory level. Accepts binary unit strings like '16KiB', '2MiB', "
188
+ "'8GiB', or 'auto' to automatically size based on detected caches. "
189
+ "e.g. '--mem-test-sizes 16KiB 256KiB 2MiB 64GiB' / "
190
+ "'--mem-test-sizes auto auto 2MiB auto' for L1, L2, L3, DRAM respectively. "
191
+ "(Default: auto for all levels)",
193
192
  )
194
193
  parser.add_argument(
195
194
  "--test-time",
196
195
  default=25.0,
197
- nargs="?",
198
196
  type=positive_float,
199
197
  help="Target runtime for each individual microbenchmark, in seconds. Low runtime may lead to inaccurate "
200
198
  "results. (Default: 25.0)",
@@ -172,7 +172,7 @@ class X86AVX2(X86AVX):
172
172
  super().__init__(**kwargs)
173
173
 
174
174
 
175
- class X86AVX512(X86Scalar):
175
+ class X86AVX512(X86SSE):
176
176
  name = "x86_avx512"
177
177
  BITS = 512
178
178
 
@@ -9,7 +9,6 @@ This module coordinates the entire benchmark pipeline:
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import tempfile
13
12
  from pathlib import Path
14
13
  from typing import TYPE_CHECKING
15
14
 
@@ -20,6 +19,7 @@ from test_bench.builder import (
20
19
  create_microbenchmark_header,
21
20
  run_microbenchmarks,
22
21
  )
22
+ from workspace import workspace_context
23
23
 
24
24
  from .benchmark import BaseBenchmark
25
25
  from .result import parse_benchmark_output
@@ -120,9 +120,13 @@ def run_full_benchmark(
120
120
 
121
121
  debug(f"Flattened benchmarks for compilation: {list(flat_benchmarks.keys())}")
122
122
 
123
- with tempfile.TemporaryDirectory(prefix="carm-test-bench-") as workspace_dir:
123
+ keep_workspace = context.run_config.dry_run or context.run_config.keep_artifacts
124
+ with workspace_context(keep=keep_workspace, prefix="carm-benchmark-") as workspace_dir:
124
125
  workspace = Path(workspace_dir)
125
126
 
127
+ if keep_workspace:
128
+ detail(f"Artifacts will be kept in: {workspace}")
129
+
126
130
  # Keep generated artifacts in a writable temporary workspace.
127
131
  # Static C sources remain in-package and are included explicitly at compile time.
128
132
  generated_header = workspace / "microbenchmarks.h"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections import defaultdict
6
+ from collections.abc import Iterator
6
7
  from dataclasses import dataclass
7
8
  from typing import TYPE_CHECKING
8
9
 
@@ -68,6 +69,11 @@ class MemoryBenchmarkSuite(ISABenchmarkSuite):
68
69
 
69
70
  previous_size_per_thread: Bytes | None = None
70
71
 
72
+ # Iterator over user-provided per-level sizes (if any)
73
+ user_sizes: Iterator[Bytes | None] | None = None
74
+ if benchmark.mem_test_sizes is not None:
75
+ user_sizes = iter(benchmark.mem_test_sizes)
76
+
71
77
  mem_level_indices = architecture.memory_topology.available_cache_levels()
72
78
  # Generate memory benchmarks for each memory level
73
79
  for mem_level_idx, mem_level_info in zip(mem_level_indices, architecture.memory_topology):
@@ -76,7 +82,7 @@ class MemoryBenchmarkSuite(ISABenchmarkSuite):
76
82
  # Plan thread affinity for this memory level
77
83
  thread_affinity = architecture.memory_topology.plan_thread_affinity(benchmark.threads, mem_level_idx)
78
84
  avail_size_per_thread: Bytes = thread_affinity.total_cache_bytes // thread_affinity.num_threads
79
- is_first_target = mem_level_idx == mem_level_indices[0]
85
+ _is_first_target = mem_level_idx == mem_level_indices[0]
80
86
  is_final_target = mem_level_idx == mem_level_indices[-1]
81
87
 
82
88
  if is_final_target and previous_size_per_thread is not None:
@@ -84,6 +90,13 @@ class MemoryBenchmarkSuite(ISABenchmarkSuite):
84
90
  else:
85
91
  target_size_per_thread = avail_size_per_thread * _CACHE_COVERAGE
86
92
 
93
+ # Override with user-provided size (if any) for this cache level.
94
+ # A None entry in the list (from "auto") keeps the computed value.
95
+ if user_sizes is not None:
96
+ user_size = next(user_sizes, None)
97
+ if user_size is not None:
98
+ target_size_per_thread = user_size
99
+
87
100
  # Warn if the per-thread dataset also fits in a smaller (closer-to-CPU) cache level.
88
101
  # E.g. targeting L3 with 16 threads, but per-thread data fits in each thread's L2:
89
102
  # the hardware will serve requests from L2, not L3, skewing the bandwidth result.
@@ -99,7 +112,8 @@ class MemoryBenchmarkSuite(ISABenchmarkSuite):
99
112
  )
100
113
 
101
114
  # Generate memory benchmark for this level
102
- layout_mode = MemoryLayoutMode.single if is_first_target else MemoryLayoutMode.split
115
+ # Use split layout only for the DRAM
116
+ layout_mode = MemoryLayoutMode.split if is_final_target else MemoryLayoutMode.single
103
117
  params = MemoryBenchmarkParams(
104
118
  data_type=data_type,
105
119
  thread_affinity=thread_affinity.cpu_ids,
@@ -65,7 +65,7 @@ class RooflineBenchmarkSuite(ISABenchmarkSuite):
65
65
  for bench in self.get_memory_benchmarks().values():
66
66
  if bench.cache_level is None:
67
67
  raise ValueError(
68
- f"Memory benchmark {bench.name} missing cache level annotation for roofline benchmarking"
68
+ f"Memory benchmark {bench.name} missing cache level annotation for roofline benchmarki>ng"
69
69
  )
70
70
  if bench.results is None:
71
71
  raise ValueError(f"Memory benchmark {bench.name} missing bandwidth result for roofline benchmarking")
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # PYTHON_ARGCOMPLETE_OK
2
3
  from __future__ import annotations
3
4
 
4
5
  import argparse
@@ -9,6 +10,7 @@ from dataclasses import dataclass
9
10
  from pathlib import Path
10
11
  from typing import cast
11
12
 
13
+ import argcomplete
12
14
  from rich_argparse import RichHelpFormatter
13
15
 
14
16
  from architecture import set_execution_interface
@@ -16,9 +18,10 @@ from arguments import InsertsArguments, TopLevelHelpFormatter
16
18
  from benchmark.interface import run_full_benchmark
17
19
  from benchmark.output import output_benchmark_results
18
20
  from context import Architecture, Benchmarking, CARMContext, ExecutionInterface, RunConfig
19
- from error import ConfigurationError
21
+ from error import UserError
20
22
  from gui.config import GUIConfig
21
- from output_utils import configure_verbosity, debug, detail, error, info, warn
23
+ from output_utils import configure_verbosity, debug, detail, error, info
24
+ from profiling import ProfileConfig, profile_main
22
25
 
23
26
  RichHelpFormatter.styles.update(
24
27
  {
@@ -45,38 +48,27 @@ def _handle_benchmark(args: argparse.Namespace) -> int:
45
48
  info(f"Generated template TOML configuration: {args.emit_config}")
46
49
  return 0
47
50
  except OSError as e:
48
- error(f"Failed to emit template config: {e}")
49
- return 1
51
+ raise OSError(f"Failed to write template config: {e}") from e
50
52
 
51
- try:
52
- exec_iface = ExecutionInterface(args)
53
- # Make exec_iface globally available for architecture feature detection
54
- set_execution_interface(exec_iface)
55
- architecture = Architecture(args)
56
- benchmarking = Benchmarking(args)
57
- run_config = RunConfig(args)
58
-
59
- context = CARMContext(
60
- architecture=architecture, benchmarking=benchmarking, run_config=run_config, exec_interface=exec_iface
61
- )
53
+ exec_iface = ExecutionInterface(args)
54
+ # Make exec_iface globally available for architecture feature detection
55
+ set_execution_interface(exec_iface)
56
+ architecture = Architecture(args)
57
+ benchmarking = Benchmarking(args)
58
+ run_config = RunConfig(args)
62
59
 
63
- benchmark_suites = run_full_benchmark(context)
60
+ context = CARMContext(
61
+ architecture=architecture, benchmarking=benchmarking, run_config=run_config, exec_interface=exec_iface
62
+ )
64
63
 
65
- if run_config.dry_run:
66
- detail("Dry run finished: benchmark code generated successfully.")
67
- return 0
64
+ benchmark_suites = run_full_benchmark(context)
68
65
 
69
- output_benchmark_results(context, benchmark_suites)
66
+ if run_config.dry_run:
67
+ detail("Dry run finished: benchmark code generated successfully.")
70
68
  return 0
71
69
 
72
- except ConfigurationError as e:
73
- error(str(e))
74
- debug(str(traceback.format_exc()))
75
- return 1
76
- except Exception as e:
77
- error(f"Unexpected error: {e.__class__.__name__}: {e}")
78
- debug(str(traceback.format_exc()))
79
- return 1
70
+ output_benchmark_results(context, benchmark_suites)
71
+ return 0
80
72
 
81
73
 
82
74
  def _handle_gui(args: argparse.Namespace) -> int:
@@ -93,15 +85,15 @@ def _handle_gui(args: argparse.Namespace) -> int:
93
85
  error('Failed to import GUI dependencies. Install optional GUI extras with: pip install "carm-roofline[gui]"')
94
86
  debug(str(traceback.format_exc()))
95
87
  return 1
96
- except Exception as e:
97
- error(f"Unexpected error: {e.__class__.__name__}: {e}")
98
- debug(str(traceback.format_exc()))
99
- return 1
100
88
 
101
89
 
102
- def _handle_profile(_args: argparse.Namespace) -> int:
103
- warn("The 'profile' subcommand is not implemented yet. Use PMU_AI_Calculator.py or DBI_AI_Calculator.py for now.")
104
- return 1
90
+ def _handle_profile(args: argparse.Namespace) -> int:
91
+ configure_verbosity(args.verbose)
92
+ args_str = ", ".join(f"{k}={v}" for k, v in vars(args).items() if v is not None)
93
+ debug(f"parsed profile arguments: {args_str}")
94
+
95
+ config = ProfileConfig(args)
96
+ return profile_main(config)
105
97
 
106
98
 
107
99
  @dataclass
@@ -130,10 +122,12 @@ SUBPARSERS = (
130
122
  ),
131
123
  SubParserInfo(
132
124
  name="profile",
133
- help_short="Profile applications (TBI)",
134
- help_long="Profile applications with CARM metrics (TBI)",
125
+ help_short="Profile applications with PAPI",
126
+ help_long="Profile instrumented applications to compute roofline metrics (AI, GFLOP/s, bandwidth). "
127
+ "Supports MPI, threaded, and hybrid applications. "
128
+ "Usage: carm profile [options] -- <command>",
135
129
  handler=_handle_profile,
136
- arg_inserting_modules=(),
130
+ arg_inserting_modules=(ProfileConfig,),
137
131
  ),
138
132
  )
139
133
 
@@ -168,6 +162,8 @@ def _create_parser() -> argparse.ArgumentParser:
168
162
 
169
163
  def main(argv: Sequence[str] | None = None) -> int:
170
164
  parser = _create_parser()
165
+ argcomplete.autocomplete(parser)
166
+
171
167
  if argv is None:
172
168
  argv = sys.argv[1:]
173
169
 
@@ -181,7 +177,17 @@ def main(argv: Sequence[str] | None = None) -> int:
181
177
  return 2
182
178
 
183
179
  handler = cast(Callable[[argparse.Namespace], int], args.handler)
184
- return handler(args)
180
+
181
+ try:
182
+ return handler(args)
183
+ except UserError as e:
184
+ error(str(e))
185
+ debug(str(traceback.format_exc()))
186
+ return 1
187
+ except Exception as e:
188
+ error(f"Unexpected error: {e.__class__.__name__}: {e}")
189
+ debug(str(traceback.format_exc()))
190
+ return 1
185
191
 
186
192
 
187
193
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.0.dev5
3
+ Version: 1.0.1.dev0
4
4
  Summary: CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit
5
5
  Author: CHaMP Team
6
6
  License: GNU LESSER GENERAL PUBLIC LICENSE
@@ -518,7 +518,7 @@ Classifier: Intended Audience :: Science/Research
518
518
  Classifier: Topic :: Software Development :: Testing
519
519
  Classifier: Topic :: System :: Benchmark
520
520
  Classifier: Programming Language :: Python :: 3
521
- Classifier: Programming Language :: Python :: 3.10
521
+ Classifier: Programming Language :: Python :: 3.9
522
522
  Classifier: Operating System :: POSIX :: Linux
523
523
  Requires-Python: >=3.9
524
524
  Description-Content-Type: text/markdown
@@ -529,6 +529,7 @@ Requires-Dist: numpy>=2.0.0
529
529
  Requires-Dist: matplotlib>=3.7.2
530
530
  Requires-Dist: tomli>=2.0.0
531
531
  Requires-Dist: platformdirs>=4.0.0
532
+ Requires-Dist: argcomplete>=3.0.0
532
533
  Provides-Extra: gui
533
534
  Requires-Dist: dash[diskcache]; extra == "gui"
534
535
  Requires-Dist: dash-bootstrap-components; extra == "gui"
@@ -11,6 +11,7 @@ results_paths.py
11
11
  run_config.py
12
12
  units.py
13
13
  utils.py
14
+ workspace.py
14
15
  architecture/__init__.py
15
16
  architecture/architecture.py
16
17
  architecture/arm.py
@@ -76,6 +77,14 @@ gui/assets/CARM_icon.svg
76
77
  gui/assets/CHAMP_logo.svg
77
78
  gui/assets/menu_icon.png
78
79
  gui/assets/style.css
80
+ profiling/__init__.py
81
+ profiling/aggregation.py
82
+ profiling/backends.py
83
+ profiling/config.py
84
+ profiling/loaders.py
85
+ profiling/metrics.py
86
+ profiling/model.py
87
+ profiling/output.py
79
88
  test/test_isa_codegen_integration.py
80
89
  test/test_memory_suite_generation.py
81
90
  test/test_output_module.py
@@ -4,6 +4,7 @@ numpy>=2.0.0
4
4
  matplotlib>=3.7.2
5
5
  tomli>=2.0.0
6
6
  platformdirs>=4.0.0
7
+ argcomplete>=3.0.0
7
8
 
8
9
  [all]
9
10
  carm-roofline[dev,gui]
@@ -7,8 +7,10 @@ error
7
7
  exec_interface
8
8
  gui
9
9
  output_utils
10
+ profiling
10
11
  results_paths
11
12
  run_config
12
13
  test_bench
13
14
  units
14
15
  utils
16
+ workspace
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class UserError(Exception):
5
+ """Raised for "expected" errors that occur from the user misconfiguration or incompatible arguments."""