carm-roofline 1.0.0.dev4__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.
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/PKG-INFO +5 -4
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/README.md +2 -2
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/architecture.py +41 -16
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/config.py +8 -8
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/detect.py +3 -3
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/memory.py +7 -7
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/benchmarking.py +17 -19
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/x86.py +1 -1
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/interface.py +6 -2
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/memory.py +16 -2
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/roofline.py +1 -1
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm.py +45 -39
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/PKG-INFO +5 -4
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/SOURCES.txt +13 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/requires.txt +1 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/top_level.txt +2 -0
- carm_roofline-1.0.1.dev0/error.py +5 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/exec_interface.py +0 -2
- carm_roofline-1.0.1.dev0/profiling/__init__.py +151 -0
- carm_roofline-1.0.1.dev0/profiling/aggregation.py +215 -0
- carm_roofline-1.0.1.dev0/profiling/backends.py +179 -0
- carm_roofline-1.0.1.dev0/profiling/config.py +114 -0
- carm_roofline-1.0.1.dev0/profiling/loaders.py +172 -0
- carm_roofline-1.0.1.dev0/profiling/metrics.py +429 -0
- carm_roofline-1.0.1.dev0/profiling/model.py +143 -0
- carm_roofline-1.0.1.dev0/profiling/output.py +131 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/pyproject.toml +10 -7
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/run_config.py +9 -1
- carm_roofline-1.0.1.dev0/test/test_isa_codegen_integration.py +958 -0
- carm_roofline-1.0.1.dev0/test/test_memory_suite_generation.py +178 -0
- carm_roofline-1.0.1.dev0/test/test_output_module.py +924 -0
- carm_roofline-1.0.1.dev0/test/test_typed_benchmarks.py +818 -0
- carm_roofline-1.0.1.dev0/utils.py +177 -0
- carm_roofline-1.0.1.dev0/workspace.py +44 -0
- carm_roofline-1.0.0.dev4/error.py +0 -5
- carm_roofline-1.0.0.dev4/utils.py +0 -314
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/LICENSE +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/arm.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/frequency.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/riscv.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/features.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/frequency.h +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/arm/vlen.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/frequency.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/frequency.h +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/rvv_version.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/riscv/vlen.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/cache.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/features.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/frequency.h +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/architecture/x86.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/arguments.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/benchmark.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/arm.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/data_type.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/instruction.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/operation.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/code_gen/register.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/isa.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/parameters.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/generation/riscv.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/arithmetic.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/base.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/common.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/memory.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/memory_sweep.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/mixed.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/output/roofline.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/result.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/arithmetic.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/base.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/memory_sweep.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/benchmark/suites/mixed.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/dependency_links.txt +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/carm_roofline.egg-info/entry_points.txt +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/context.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/assets/CARM_icon.svg +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/assets/CHAMP_logo.svg +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/assets/menu_icon.png +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/assets/style.css +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/config.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/dashboard.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/gui/gui_utils.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/output_utils.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/results_paths.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/setup.cfg +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/test_bench/__init__.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/test_bench/builder.py +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/test_bench/test_bench.c +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/test_bench/test_bench.h +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/test_bench/wrapper.inl +0 -0
- {carm_roofline-1.0.0.dev4 → carm_roofline-1.0.1.dev0}/units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: carm-roofline
|
|
3
|
-
Version: 1.0.
|
|
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.
|
|
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"
|
|
@@ -622,7 +623,7 @@ Other usage examples:
|
|
|
622
623
|
# Short roofline benchmark (1 second per micro-bench)
|
|
623
624
|
carm benchmark --test roofline --test-time 1
|
|
624
625
|
# Arithmetic-only
|
|
625
|
-
carm benchmark --test arithmetic --instruction add --data-type f32
|
|
626
|
+
carm benchmark --test arithmetic --instruction add mul fma --data-type f32
|
|
626
627
|
# Memory-only
|
|
627
628
|
carm benchmark --test memory --mem-target L2 --ld-st-ratio 2:1
|
|
628
629
|
# Dry run (generate benchmark code, skip compile/execute):
|
|
@@ -631,7 +632,7 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
|
|
|
631
632
|
|
|
632
633
|
## Output and Results
|
|
633
634
|
|
|
634
|
-
By default, results are written
|
|
635
|
+
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
636
|
|
|
636
637
|
Control output directory and format(s):
|
|
637
638
|
|
|
@@ -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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
38
|
+
raise UserError(f"Incompatible ISAs selected: {pair_names}")
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def positive_po2_int(arg: str) -> int:
|
|
@@ -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
|
|
|
@@ -252,20 +278,18 @@ class Architecture(InsertsArguments):
|
|
|
252
278
|
parser.add_argument(
|
|
253
279
|
"-i",
|
|
254
280
|
"--isa",
|
|
255
|
-
nargs="
|
|
281
|
+
nargs="+",
|
|
256
282
|
choices=list(ISA_NAME_TO_CLASS.keys()),
|
|
257
283
|
help="Set of ISAs to test. (Default: automatically detects all available ISAs)",
|
|
258
284
|
)
|
|
259
285
|
parser.add_argument(
|
|
260
286
|
"-l",
|
|
261
287
|
"--vector-length",
|
|
262
|
-
nargs="?",
|
|
263
288
|
type=positive_po2_int,
|
|
264
289
|
help="Vector register length in bytes. (Default: automatically detected)",
|
|
265
290
|
)
|
|
266
291
|
parser.add_argument(
|
|
267
292
|
"--vector-lmul",
|
|
268
|
-
nargs="?",
|
|
269
293
|
type=int,
|
|
270
294
|
choices=(1, 2, 4, 8),
|
|
271
295
|
help="Vector register group modifier (LMUL). RISC-V specific.",
|
|
@@ -283,12 +307,13 @@ class Architecture(InsertsArguments):
|
|
|
283
307
|
"--emit-config",
|
|
284
308
|
type=str,
|
|
285
309
|
metavar="PATH",
|
|
310
|
+
nargs="?",
|
|
311
|
+
const="topology.toml",
|
|
286
312
|
help="Generate a template TOML configuration file at the specified path and exit. "
|
|
287
313
|
"Edit the template and pass it back with --topology-config.",
|
|
288
314
|
)
|
|
289
315
|
parser.add_argument(
|
|
290
316
|
"--frequency",
|
|
291
|
-
nargs="?",
|
|
292
317
|
type=Frequency.from_argparse,
|
|
293
318
|
help="Processor frequency, e.g. '2GHz', '3200mhz' (Default: automatically detected if possible)",
|
|
294
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
112
|
+
raise UserError(f"Cache level section missing required field: {e}") from e
|
|
113
113
|
|
|
114
114
|
if not isinstance(instances, int):
|
|
115
|
-
raise
|
|
115
|
+
raise UserError(f"'instances' must be an integer, got: {instances!r}")
|
|
116
116
|
if not isinstance(size_str, str):
|
|
117
|
-
raise
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
182
|
+
raise UserError(f"total_cpus must be positive when provided, got {total_cpus}")
|
|
183
183
|
|
|
184
184
|
if cpu_offset < 0:
|
|
185
|
-
raise
|
|
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
|
|
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) ->
|
|
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
|
|
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
|
-
|
|
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
|
|
84
|
+
return None
|
|
85
85
|
try:
|
|
86
|
-
return
|
|
87
|
-
except
|
|
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}.
|
|
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[
|
|
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
|
|
191
|
-
"
|
|
192
|
-
"
|
|
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)",
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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")
|