carm-roofline 1.0.0.dev3__tar.gz → 1.0.0.dev5__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 (90) hide show
  1. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/PKG-INFO +3 -3
  2. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/README.md +2 -2
  3. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/architecture.py +35 -9
  4. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/benchmarking.py +4 -4
  5. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/arithmetic.py +67 -64
  6. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/roofline.py +24 -5
  7. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/arithmetic.py +12 -26
  8. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/roofline.py +1 -17
  9. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/PKG-INFO +3 -3
  10. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/SOURCES.txt +4 -0
  11. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/pyproject.toml +4 -4
  12. carm_roofline-1.0.0.dev5/test/test_isa_codegen_integration.py +958 -0
  13. carm_roofline-1.0.0.dev5/test/test_memory_suite_generation.py +177 -0
  14. carm_roofline-1.0.0.dev5/test/test_output_module.py +924 -0
  15. carm_roofline-1.0.0.dev5/test/test_typed_benchmarks.py +818 -0
  16. carm_roofline-1.0.0.dev5/utils.py +177 -0
  17. carm_roofline-1.0.0.dev3/utils.py +0 -314
  18. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/LICENSE +0 -0
  19. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/__init__.py +0 -0
  20. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/arm.py +0 -0
  21. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/config.py +0 -0
  22. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/detect.py +0 -0
  23. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/frequency.py +0 -0
  24. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/memory.py +0 -0
  25. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/riscv.py +0 -0
  26. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/features.c +0 -0
  27. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/frequency.h +0 -0
  28. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/vlen.c +0 -0
  29. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/frequency.c +0 -0
  30. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/frequency.h +0 -0
  31. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/rvv_version.c +0 -0
  32. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/vlen.c +0 -0
  33. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/cache.c +0 -0
  34. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/features.c +0 -0
  35. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/frequency.h +0 -0
  36. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
  37. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/x86.py +0 -0
  38. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/arguments.py +0 -0
  39. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/__init__.py +0 -0
  40. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/benchmark.py +0 -0
  41. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/__init__.py +0 -0
  42. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/arm.py +0 -0
  43. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/__init__.py +0 -0
  44. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/data_type.py +0 -0
  45. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/instruction.py +0 -0
  46. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/operation.py +0 -0
  47. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/register.py +0 -0
  48. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/isa.py +0 -0
  49. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/parameters.py +0 -0
  50. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/riscv.py +0 -0
  51. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/x86.py +0 -0
  52. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/interface.py +0 -0
  53. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/__init__.py +0 -0
  54. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/base.py +0 -0
  55. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/common.py +0 -0
  56. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/memory.py +0 -0
  57. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/memory_sweep.py +0 -0
  58. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/mixed.py +0 -0
  59. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/result.py +0 -0
  60. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/__init__.py +0 -0
  61. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/base.py +0 -0
  62. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/memory.py +0 -0
  63. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/memory_sweep.py +0 -0
  64. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/mixed.py +0 -0
  65. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm.py +1 -1
  66. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/dependency_links.txt +0 -0
  67. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/entry_points.txt +0 -0
  68. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/requires.txt +0 -0
  69. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/top_level.txt +0 -0
  70. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/context.py +0 -0
  71. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/error.py +0 -0
  72. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/exec_interface.py +0 -0
  73. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/__init__.py +0 -0
  74. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/CARM_icon.svg +0 -0
  75. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/CHAMP_logo.svg +0 -0
  76. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/menu_icon.png +0 -0
  77. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/style.css +0 -0
  78. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/config.py +0 -0
  79. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/dashboard.py +0 -0
  80. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/gui_utils.py +0 -0
  81. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/output_utils.py +0 -0
  82. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/results_paths.py +0 -0
  83. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/run_config.py +0 -0
  84. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/setup.cfg +0 -0
  85. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/__init__.py +0 -0
  86. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/builder.py +0 -0
  87. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/test_bench.c +0 -0
  88. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/test_bench.h +0 -0
  89. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/wrapper.inl +0 -0
  90. {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.0.dev3
3
+ Version: 1.0.0.dev5
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
@@ -622,7 +622,7 @@ Other usage examples:
622
622
  # Short roofline benchmark (1 second per micro-bench)
623
623
  carm benchmark --test roofline --test-time 1
624
624
  # Arithmetic-only
625
- carm benchmark --test arithmetic --instruction add --data-type f32
625
+ carm benchmark --test arithmetic --instruction add mul fma --data-type f32
626
626
  # Memory-only
627
627
  carm benchmark --test memory --mem-target L2 --ld-st-ratio 2:1
628
628
  # Dry run (generate benchmark code, skip compile/execute):
@@ -631,7 +631,7 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
631
631
 
632
632
  ## Output and Results
633
633
 
634
- By default, results are written under the platform user data directory for the `carm` app (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
634
+ By default, results are written to the user data directory (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
635
635
 
636
636
  Control output directory and format(s):
637
637
 
@@ -75,7 +75,7 @@ Other usage examples:
75
75
  # Short roofline benchmark (1 second per micro-bench)
76
76
  carm benchmark --test roofline --test-time 1
77
77
  # Arithmetic-only
78
- carm benchmark --test arithmetic --instruction add --data-type f32
78
+ carm benchmark --test arithmetic --instruction add mul fma --data-type f32
79
79
  # Memory-only
80
80
  carm benchmark --test memory --mem-target L2 --ld-st-ratio 2:1
81
81
  # Dry run (generate benchmark code, skip compile/execute):
@@ -84,7 +84,7 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
84
84
 
85
85
  ## Output and Results
86
86
 
87
- By default, results are written under the platform user data directory for the `carm` app (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
87
+ By default, results are written to the user data directory (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
88
88
 
89
89
  Control output directory and format(s):
90
90
 
@@ -103,6 +103,36 @@ class ISAFrequencies:
103
103
  base_frequency = Frequency(base_frequency)
104
104
  return ISAFrequencies({isa.name: base_frequency for isa in isas})
105
105
 
106
+ @staticmethod
107
+ def from_detected(detected: DetectedArchitecture, isa_list: list[type[BaseISA]]) -> ISAFrequencies:
108
+ """Create ISAFrequencies from auto-detected architecture data.
109
+
110
+ Initializes all ISAs to the base frequency, then overrides with any per-ISA
111
+ specific frequencies that were detected.
112
+
113
+ Args:
114
+ detected: DetectedArchitecture object from probing
115
+ isa_list: List of ISA classes for this architecture
116
+
117
+ Returns:
118
+ ISAFrequencies with appropriate frequencies from detected data
119
+
120
+ Raises:
121
+ ValueError: If no frequency information is available
122
+ """
123
+ base_freq = detected.frequency
124
+ if not base_freq:
125
+ raise ValueError("Frequency could not be auto-detected, specify it using --frequency <freq>")
126
+
127
+ # Start with all ISAs at base frequency
128
+ isa_freqs_dict = {isa.name: base_freq for isa in isa_list}
129
+
130
+ # Override with any specific ISA frequencies that were detected
131
+ if detected.isa_frequencies:
132
+ isa_freqs_dict.update(detected.isa_frequencies)
133
+
134
+ return ISAFrequencies(isa_freqs_dict)
135
+
106
136
  def __getitem__(self, isa_name: str) -> Frequency:
107
137
  """Get frequency for a specific ISA.
108
138
 
@@ -111,6 +141,9 @@ class ISAFrequencies:
111
141
 
112
142
  Returns:
113
143
  Frequency wrapper for that ISA
144
+
145
+ Raises:
146
+ KeyError: If ISA not found
114
147
  """
115
148
  return self._isa_frequencies[isa_name]
116
149
 
@@ -177,7 +210,7 @@ class Architecture(InsertsArguments):
177
210
  "Specify them using a TOML config file with --topology-config <path>"
178
211
  )
179
212
 
180
- # Handle frequency: user arg (Frequency wrapper) overrides all, otherwise use detected per-ISA frequencies
213
+ # Handle frequency: user arg (Frequency wrapper) overrides all, otherwise use detected
181
214
  if args.frequency is not None:
182
215
  # args.frequency is already a Frequency wrapper from frequency_type()
183
216
  self.frequency = ISAFrequencies.from_base_frequency(args.frequency, self.isa)
@@ -185,14 +218,7 @@ class Architecture(InsertsArguments):
185
218
  d_freq = detected.frequency if detected.isa_frequencies is None else detected.isa_frequencies
186
219
  _override_warn("frequency", args.frequency, d_freq)
187
220
  else:
188
- isa_freqs = detected.isa_frequencies
189
- # if there are isa-specific frequencies, use them; otherwise fall back to base frequency if available
190
- if isa_freqs:
191
- self.frequency = ISAFrequencies(isa_freqs)
192
- elif base_freq := detected.frequency:
193
- self.frequency = ISAFrequencies.from_base_frequency(base_freq, self.isa)
194
- else:
195
- raise ValueError("Frequency could not be auto-detected, specify it using --frequency <freq>")
221
+ self.frequency = ISAFrequencies.from_detected(detected, self.isa)
196
222
 
197
223
  self.nominal_frequency: Frequency | None = detected.frequency_nominal
198
224
 
@@ -102,7 +102,7 @@ class Benchmarking(InsertsArguments):
102
102
  self.data_type: DataType = args.data_type
103
103
  self.threads: int = args.threads
104
104
  self.interleaved: bool = args.interleaved
105
- self.instruction: ArithmeticOperation = args.instruction
105
+ self.instructions: set[ArithmeticOperation] = set(args.instruction)
106
106
  self.num_ops: Operations = Operations(args.num_ops)
107
107
  self.ld_st_ratio: LoadStoreRatio = args.ld_st_ratio
108
108
  self.arith_mem_ratio: int = args.arith_mem_ratio
@@ -163,10 +163,10 @@ class Benchmarking(InsertsArguments):
163
163
  )
164
164
  parser.add_argument(
165
165
  "--instruction",
166
- default=ArithmeticOperation.fma,
167
- nargs="?",
166
+ default=[ArithmeticOperation.add, ArithmeticOperation.fma],
167
+ nargs="+",
168
168
  action=enum_action(ArithmeticOperation),
169
- help="Arithmetic instruction benchmarked (Default: 'fma')",
169
+ help="Arithmetic instruction(s) to benchmark (Default: add fma)",
170
170
  )
171
171
  parser.add_argument(
172
172
  "--ld-st-ratio",
@@ -29,26 +29,19 @@ def _collect_gops_by_isa(isa_suites: dict[str, ISABenchmarkSuite]) -> dict[str,
29
29
  gops_by_isa: dict[str, float] = {}
30
30
 
31
31
  for isa, suite in sorted(isa_suites.items(), key=lambda kv: kv[0]):
32
- benches: dict[str, Any]
33
- if hasattr(suite, "get_arithmetic_benchmarks"):
34
- benches = suite.get_arithmetic_benchmarks()
35
- else:
36
- benches = getattr(suite, "benchmarks", {})
37
-
38
32
  values: list[float] = []
39
- for bench in benches.values():
40
- res = getattr(bench, "results", None)
41
- if not isinstance(res, ArithmeticBenchmarkResult):
33
+ for bench in suite.get_arithmetic_benchmarks().values():
34
+ if not isinstance(bench.results, ArithmeticBenchmarkResult):
42
35
  continue
43
36
 
44
- gops_value = float(res.performance.value) / 1e9
37
+ gops_value = float(bench.results.performance.value) / 1e9
45
38
  if not math.isfinite(gops_value):
46
39
  warn(f"Skipping invalid GOPS value for ISA {isa}: {gops_value}")
47
40
  continue
48
41
  values.append(gops_value)
49
42
 
50
43
  if values:
51
- gops_by_isa[isa] = sum(values) / len(values)
44
+ gops_by_isa[isa] = max(values)
52
45
 
53
46
  return gops_by_isa
54
47
 
@@ -64,47 +57,53 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
64
57
  isa_cls.name: isa_cls.from_architecture(context.architecture) for isa_cls in context.architecture.isa
65
58
  }
66
59
 
67
- arithmetic_metrics: dict[str, dict[str, Any]] = {}
60
+ arithmetic_metrics: list[dict[str, Any]] = []
68
61
 
69
62
  for isa, suite in sorted(isa_suites.items(), key=lambda kv: kv[0]):
70
63
  frequency = context.architecture.get_frequency_for_isa(isa)
71
- benches = suite.get_arithmetic_benchmarks()
72
- assert len(benches) == 1, "Expected exactly one arithmetic benchmark per ISA for summary output"
73
- benchmark_name, benchmark = next(iter(benches.items()))
74
- res = benchmark.results
75
- assert isinstance(res, ArithmeticBenchmarkResult), "Expected ArithmeticBenchmarkResult for arithmetic summary"
76
-
77
64
  isa_instance = isa_instances.get(isa)
78
65
  if isa_instance is None:
79
66
  warn(f"No ISA instance available for {isa}; skipping metrics")
80
67
  continue
81
68
 
82
- ops_per_inst = isa_instance.ops_per_inst(benchmark.params.data_type, benchmark.params.operation)
83
- total_insts = (benchmark.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
84
- cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
85
- ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
86
- ops_per_cycle = ops_per_inst * ipc
87
-
88
- arithmetic_metrics[isa] = {
89
- "benchmark": benchmark_name,
90
- "gops": float(res.performance.value) / 1e9,
91
- "gops_display": str(res.performance),
92
- "ipc": ipc,
93
- "frequency_hz": float(frequency.value),
94
- "frequency_display": str(frequency),
95
- "ops_per_instruction": ops_per_inst,
96
- "ops_per_cycle": ops_per_cycle,
97
- "time_seconds": float(res.time_taken.value),
98
- "time_display": str(res.time_taken),
99
- "repetitions": int(res.num_repetitions),
100
- "cycles": float(cycles.value),
101
- "cycles_display": str(cycles),
102
- }
69
+ benches = suite.get_arithmetic_benchmarks()
70
+ for benchmark_name, benchmark in benches.items():
71
+ res = benchmark.results
72
+ assert isinstance(res, ArithmeticBenchmarkResult), (
73
+ "Expected ArithmeticBenchmarkResult for arithmetic summary"
74
+ )
75
+
76
+ ops_per_inst = isa_instance.ops_per_inst(benchmark.params.data_type, benchmark.params.operation)
77
+ total_insts = (benchmark.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
78
+ cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
79
+ ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
80
+ ops_per_cycle = ops_per_inst * ipc
81
+
82
+ arithmetic_metrics.append(
83
+ {
84
+ "isa": isa,
85
+ "operation": benchmark.params.operation.name,
86
+ "benchmark": benchmark_name,
87
+ "gops": float(res.performance.value) / 1e9,
88
+ "gops_display": str(res.performance),
89
+ "ipc": ipc,
90
+ "frequency_hz": float(frequency.value),
91
+ "frequency_display": str(frequency),
92
+ "ops_per_instruction": ops_per_inst,
93
+ "ops_per_cycle": ops_per_cycle,
94
+ "time_seconds": float(res.time_taken.value),
95
+ "time_display": str(res.time_taken),
96
+ "repetitions": int(res.num_repetitions),
97
+ "cycles": float(cycles.value),
98
+ "cycles_display": str(cycles),
99
+ }
100
+ )
103
101
 
104
102
  table = Table(title="Arithmetic Performance Summary")
105
103
 
106
104
  # normal output columns
107
105
  table.add_column("ISA", style="cyan")
106
+ table.add_column("Op", style="magenta")
108
107
  table.add_column("GOPS", justify="right")
109
108
  table.add_column("IPC", justify="right")
110
109
  table.add_column("Frequency", justify="right")
@@ -116,7 +115,8 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
116
115
  table.add_column("Repetitions", justify="right")
117
116
  table.add_column("Cycles", justify="right")
118
117
 
119
- for isa, metrics in sorted(arithmetic_metrics.items(), key=lambda kv: kv[0]):
118
+ # sort rows by ISA and operation
119
+ for metrics in sorted(arithmetic_metrics, key=lambda row: (row["isa"], row["operation"])):
120
120
  ipc = float(metrics["ipc"])
121
121
 
122
122
  if print_extra:
@@ -130,7 +130,8 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
130
130
  extra_args = ()
131
131
 
132
132
  table.add_row(
133
- isa,
133
+ metrics["isa"],
134
+ metrics["operation"],
134
135
  str(metrics["gops_display"]),
135
136
  f"{ipc:.2f}",
136
137
  str(metrics["frequency_display"]),
@@ -152,34 +153,36 @@ def _write_json(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
152
153
 
153
154
  for isa, suite in sorted(isa_suites.items(), key=lambda kv: kv[0]):
154
155
  frequency = context.architecture.get_frequency_for_isa(isa)
155
- benches = suite.get_arithmetic_benchmarks()
156
- assert len(benches) == 1, "Expected exactly one arithmetic benchmark per ISA for JSON output"
157
- benchmark_name, benchmark = next(iter(benches.items()))
158
- res = benchmark.results
159
- assert isinstance(res, ArithmeticBenchmarkResult), "Expected ArithmeticBenchmarkResult for arithmetic JSON"
160
-
161
156
  isa_instance = isa_instances.get(isa)
162
157
  if isa_instance is None:
163
158
  warn(f"No ISA instance available for {isa}; skipping metrics")
164
159
  continue
165
160
 
166
- ops_per_inst = isa_instance.ops_per_inst(benchmark.params.data_type, benchmark.params.operation)
167
- total_insts = (benchmark.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
168
- cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
169
- ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
170
-
171
- results[isa] = {
172
- "benchmark": benchmark_name,
173
- "operation": benchmark.params.operation.name,
174
- "gops": float(res.performance.value) / 1e9,
175
- "ipc": ipc,
176
- "frequency_hz": float(frequency.value),
177
- "ops_per_instruction": ops_per_inst,
178
- "ops_per_cycle": ops_per_inst * ipc,
179
- "time_seconds": float(res.time_taken.value),
180
- "repetitions": int(res.num_repetitions),
181
- "cycles": float(cycles.value),
182
- }
161
+ benches = suite.get_arithmetic_benchmarks()
162
+ isa_results: dict[str, object] = {}
163
+ for benchmark_name, benchmark in benches.items():
164
+ res = benchmark.results
165
+ assert isinstance(res, ArithmeticBenchmarkResult), "Expected ArithmeticBenchmarkResult for arithmetic JSON"
166
+
167
+ ops_per_inst = isa_instance.ops_per_inst(benchmark.params.data_type, benchmark.params.operation)
168
+ total_insts = (benchmark.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
169
+ cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
170
+ ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
171
+
172
+ isa_results[benchmark.params.operation.name] = {
173
+ "benchmark": benchmark_name,
174
+ "operation": benchmark.params.operation.name,
175
+ "gops": float(res.performance.value) / 1e9,
176
+ "ipc": ipc,
177
+ "frequency_hz": float(frequency.value),
178
+ "ops_per_instruction": ops_per_inst,
179
+ "ops_per_cycle": ops_per_inst * ipc,
180
+ "time_seconds": float(res.time_taken.value),
181
+ "repetitions": int(res.num_repetitions),
182
+ "cycles": float(cycles.value),
183
+ }
184
+
185
+ results[isa] = isa_results
183
186
 
184
187
  out_dir = context.run_config.output_dir / "arithmetic"
185
188
  out_dir.mkdir(parents=True, exist_ok=True)
@@ -8,6 +8,7 @@ from datetime import datetime
8
8
  from pathlib import Path
9
9
  from typing import TYPE_CHECKING
10
10
 
11
+ from benchmark.generation.code_gen.operation import ArithmeticOperation
11
12
  from output_utils import info, warn
12
13
  from units import Cycles
13
14
 
@@ -167,7 +168,12 @@ def _collect_memory_metrics(
167
168
  return bandwidth_by_level, ipc_by_level
168
169
 
169
170
 
170
- def _collect_fp_metrics(context: CARMContext, suite: ISABenchmarkSuite, isa_name: str) -> tuple[float, float]:
171
+ def _collect_fp_metrics(
172
+ context: CARMContext,
173
+ suite: ISABenchmarkSuite,
174
+ isa_name: str,
175
+ operation: ArithmeticOperation | None = None,
176
+ ) -> tuple[float, float]:
171
177
  frequency = context.architecture.get_frequency_for_isa(isa_name)
172
178
  fp_gflops = 0.0
173
179
  fp_ipc = 0.0
@@ -179,6 +185,8 @@ def _collect_fp_metrics(context: CARMContext, suite: ISABenchmarkSuite, isa_name
179
185
  for bench in suite.get_arithmetic_benchmarks().values():
180
186
  if bench.results is None:
181
187
  continue
188
+ if operation is not None and bench.params.operation != operation:
189
+ continue
182
190
  fp_gflops = float(bench.results.performance.value) / 1e9
183
191
  ops_per_inst = isa_instance.ops_per_inst(bench.params.data_type, bench.params.operation)
184
192
  total_insts = (
@@ -214,8 +222,19 @@ def _write_csv(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite], o
214
222
 
215
223
  bandwidth_by_level, icycle_by_level = _collect_memory_metrics(context, suite, isa)
216
224
 
217
- fp_inst = context.benchmarking.instruction.name
218
- fp_gflops, fp_icycle = _collect_fp_metrics(context, suite, isa)
225
+ # Select primary instruction: prefer non-fma if available, else fall back to fma
226
+ primary_instruction = next(
227
+ (op for op in context.benchmarking.instructions if op != ArithmeticOperation.fma),
228
+ ArithmeticOperation.fma,
229
+ )
230
+
231
+ fp_inst = primary_instruction.name
232
+ fp_gflops, fp_icycle = _collect_fp_metrics(context, suite, isa, operation=primary_instruction)
233
+
234
+ fma_gflops = 0.0
235
+ fma_icycle = 0.0
236
+ if ArithmeticOperation.fma in context.benchmarking.instructions:
237
+ fma_gflops, fma_icycle = _collect_fp_metrics(context, suite, isa, operation=ArithmeticOperation.fma)
219
238
 
220
239
  def round_helper(x: float) -> str:
221
240
  return f"{x:.3g}" if x < 1 else f"{x:.3f}"
@@ -240,8 +259,8 @@ def _write_csv(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite], o
240
259
  round_helper(icycle_by_level["DRAM"]),
241
260
  round_helper(fp_gflops),
242
261
  round_helper(fp_icycle),
243
- 0,
244
- 0,
262
+ round_helper(fma_gflops),
263
+ round_helper(fma_icycle),
245
264
  ]
246
265
 
247
266
  secondary_headers = [
@@ -5,8 +5,6 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- from units import Performance
9
-
10
8
  from ..benchmark import ArithmeticBenchmark
11
9
  from ..generation import ArithmeticBenchmarkParams
12
10
  from .base import ISABenchmarkSuite
@@ -19,7 +17,7 @@ if TYPE_CHECKING:
19
17
  class ArithmeticBenchmarkSuite(ISABenchmarkSuite):
20
18
  """Suite for arithmetic-only benchmarks (TestType.ARITHMETIC).
21
19
 
22
- Measures peak arithmetic performance for a specific operation.
20
+ Measures peak arithmetic performance across one or more operations.
23
21
  """
24
22
 
25
23
  @classmethod
@@ -61,29 +59,17 @@ class ArithmeticBenchmarkSuite(ISABenchmarkSuite):
61
59
  # This also helps us deal with SMT, ensuring threads are properly distributed across cores
62
60
  thread_affinity = architecture.memory_topology.plan_thread_affinity(benchmark.threads, 1)
63
61
 
64
- # Generate arithmetic benchmark
65
- params = ArithmeticBenchmarkParams(
66
- data_type=benchmark.data_type,
67
- thread_affinity=thread_affinity.cpu_ids,
68
- operation=benchmark.instruction,
69
- num_ops=benchmark.num_ops,
70
- )
71
- spec = isa.generate_arithmetic(params, context)
62
+ for operation in benchmark.instructions:
63
+ params = ArithmeticBenchmarkParams(
64
+ data_type=benchmark.data_type,
65
+ thread_affinity=thread_affinity.cpu_ids,
66
+ operation=operation,
67
+ num_ops=benchmark.num_ops,
68
+ )
69
+ spec = isa.generate_arithmetic(params, context)
72
70
 
73
- # Add to ISA suite using the benchmark's authoritative name
74
- arith_benchmark = ArithmeticBenchmark(params=params, spec=spec)
75
- suite.add_benchmark(arith_benchmark.name, arith_benchmark)
71
+ # Add to ISA suite using the benchmark's authoritative name
72
+ arith_benchmark = ArithmeticBenchmark(params=params, spec=spec)
73
+ suite.add_benchmark(arith_benchmark.name, arith_benchmark)
76
74
 
77
75
  return suite
78
-
79
- def get_peak_performance(self) -> Performance:
80
- """Get peak GOPS from arithmetic portion of roofline suite.
81
-
82
- Returns:
83
- Peak GOPS from arithmetic benchmarks, or None if unavailable.
84
- """
85
- arith_benchmarks = self.get_arithmetic_benchmarks()
86
- if len(arith_benchmarks) > 1:
87
- raise ValueError("Expected exactly one arithmetic benchmark for peak performance calculation")
88
-
89
- return super().get_peak_performance()
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- from units import ArithmeticIntensity, Bandwidth, Performance
8
+ from units import ArithmeticIntensity, Bandwidth
9
9
 
10
10
  from .arithmetic import ArithmeticBenchmarkSuite
11
11
  from .base import ISABenchmarkSuite
@@ -53,22 +53,6 @@ class RooflineBenchmarkSuite(ISABenchmarkSuite):
53
53
 
54
54
  return suite
55
55
 
56
- def get_peak_performance(self) -> Performance:
57
- """Get peak GOPS from arithmetic portion of roofline suite.
58
-
59
- Returns:
60
- Peak GOPS from arithmetic benchmarks, or None if unavailable.
61
- """
62
- arith_benchmarks = self.get_arithmetic_benchmarks()
63
- if len(arith_benchmarks) > 1:
64
- raise ValueError("Expected exactly one arithmetic benchmark for peak performance calculation")
65
-
66
- arith_benchmark = next(iter(arith_benchmarks.values()))
67
- if arith_benchmark.results is None:
68
- raise ValueError("Arithmetic benchmark missing performance result for roofline benchmarking")
69
-
70
- return arith_benchmark.results.performance
71
-
72
56
  def get_bandwidth_by_level(self) -> dict[str, Bandwidth]:
73
57
  """Get peak bandwidth for each cache level from memory benchmarks.
74
58
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.0.dev3
3
+ Version: 1.0.0.dev5
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
@@ -622,7 +622,7 @@ Other usage examples:
622
622
  # Short roofline benchmark (1 second per micro-bench)
623
623
  carm benchmark --test roofline --test-time 1
624
624
  # Arithmetic-only
625
- carm benchmark --test arithmetic --instruction add --data-type f32
625
+ carm benchmark --test arithmetic --instruction add mul fma --data-type f32
626
626
  # Memory-only
627
627
  carm benchmark --test memory --mem-target L2 --ld-st-ratio 2:1
628
628
  # Dry run (generate benchmark code, skip compile/execute):
@@ -631,7 +631,7 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
631
631
 
632
632
  ## Output and Results
633
633
 
634
- By default, results are written under the platform user data directory for the `carm` app (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
634
+ By default, results are written to the user data directory (via platformdirs). On linux, this is typically `~/.local/share/carm/`, on Windows, `%APPDATA%\carm\`, and on MacOS, `~/Library/Application Support/carm/`.
635
635
 
636
636
  Control output directory and format(s):
637
637
 
@@ -76,6 +76,10 @@ gui/assets/CARM_icon.svg
76
76
  gui/assets/CHAMP_logo.svg
77
77
  gui/assets/menu_icon.png
78
78
  gui/assets/style.css
79
+ test/test_isa_codegen_integration.py
80
+ test/test_memory_suite_generation.py
81
+ test/test_output_module.py
82
+ test/test_typed_benchmarks.py
79
83
  test_bench/__init__.py
80
84
  test_bench/builder.py
81
85
  test_bench/test_bench.c
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "carm-roofline"
3
- version = "1.0.0.dev3"
3
+ version = "1.0.0.dev5"
4
4
  description = "CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -112,7 +112,7 @@ exclude = [
112
112
  "test_bench",
113
113
  "Test",
114
114
  "CustomClient",
115
- "refactor_tests", # Test code with temporary workarounds
115
+ "test", # Test code with temporary workarounds
116
116
  "venv",
117
117
  ".venv*"
118
118
  ]
@@ -171,7 +171,7 @@ line-ending = "lf"
171
171
 
172
172
  [tool.pytest.ini_options]
173
173
  minversion = "7.0"
174
- testpaths = ["refactor_tests"]
174
+ testpaths = ["test"]
175
175
  python_files = ["test_*.py"]
176
176
  python_classes = ["Test*"]
177
177
  python_functions = ["test_*"]
@@ -208,7 +208,7 @@ warn_unused_configs = true
208
208
  # Exclude legacy code from type checking
209
209
  exclude = [
210
210
  "legacy_bench_gen/",
211
- "refactor_tests/",
211
+ "test/",
212
212
  "run.py",
213
213
  "run_gpu.py",
214
214
  ".*AI_Calculator.py",