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.
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/PKG-INFO +3 -3
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/README.md +2 -2
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/architecture.py +35 -9
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/benchmarking.py +4 -4
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/arithmetic.py +67 -64
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/roofline.py +24 -5
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/arithmetic.py +12 -26
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/roofline.py +1 -17
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/PKG-INFO +3 -3
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/SOURCES.txt +4 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/pyproject.toml +4 -4
- carm_roofline-1.0.0.dev5/test/test_isa_codegen_integration.py +958 -0
- carm_roofline-1.0.0.dev5/test/test_memory_suite_generation.py +177 -0
- carm_roofline-1.0.0.dev5/test/test_output_module.py +924 -0
- carm_roofline-1.0.0.dev5/test/test_typed_benchmarks.py +818 -0
- carm_roofline-1.0.0.dev5/utils.py +177 -0
- carm_roofline-1.0.0.dev3/utils.py +0 -314
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/LICENSE +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/arm.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/config.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/detect.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/frequency.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/memory.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/riscv.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/features.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/frequency.h +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/arm/vlen.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/frequency.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/frequency.h +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/rvv_version.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/riscv/vlen.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/cache.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/features.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/frequency.h +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/architecture/x86.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/arguments.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/benchmark.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/arm.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/data_type.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/instruction.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/operation.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/code_gen/register.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/isa.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/parameters.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/riscv.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/generation/x86.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/interface.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/base.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/common.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/memory.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/memory_sweep.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/output/mixed.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/result.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/base.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/memory.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/memory_sweep.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/benchmark/suites/mixed.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm.py +1 -1
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/dependency_links.txt +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/entry_points.txt +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/requires.txt +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/carm_roofline.egg-info/top_level.txt +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/context.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/error.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/exec_interface.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/CARM_icon.svg +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/CHAMP_logo.svg +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/menu_icon.png +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/assets/style.css +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/config.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/dashboard.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/gui/gui_utils.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/output_utils.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/results_paths.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/run_config.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/setup.cfg +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/__init__.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/builder.py +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/test_bench.c +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/test_bench.h +0 -0
- {carm_roofline-1.0.0.dev3 → carm_roofline-1.0.0.dev5}/test_bench/wrapper.inl +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
40
|
-
|
|
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(
|
|
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] =
|
|
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:
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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(
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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 = ["
|
|
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
|
-
"
|
|
211
|
+
"test/",
|
|
212
212
|
"run.py",
|
|
213
213
|
"run_gpu.py",
|
|
214
214
|
".*AI_Calculator.py",
|