carm-roofline 1.0.0.dev2__tar.gz → 1.0.0.dev4__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 (92) hide show
  1. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/PKG-INFO +26 -22
  2. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/README.md +23 -20
  3. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/benchmarking.py +4 -4
  4. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/arithmetic.py +67 -64
  5. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/roofline.py +24 -5
  6. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/arithmetic.py +12 -26
  7. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/memory_sweep.py +6 -4
  8. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/roofline.py +1 -17
  9. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/PKG-INFO +26 -22
  10. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/SOURCES.txt +1 -7
  11. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/requires.txt +2 -1
  12. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/top_level.txt +1 -0
  13. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/config.py +3 -2
  14. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/dashboard.py +16 -3
  15. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/pyproject.toml +5 -3
  16. carm_roofline-1.0.0.dev4/results_paths.py +13 -0
  17. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/run_config.py +5 -4
  18. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/test_bench/builder.py +3 -0
  19. carm_roofline-1.0.0.dev2/DBI_AI_Calculator.py +0 -940
  20. carm_roofline-1.0.0.dev2/PMU_AI_Calculator.py +0 -219
  21. carm_roofline-1.0.0.dev2/SDE_AI_Calculator.py +0 -804
  22. carm_roofline-1.0.0.dev2/architecture/tests/REFACTOR.md +0 -2
  23. carm_roofline-1.0.0.dev2/architecture/tests/frequency +0 -0
  24. carm_roofline-1.0.0.dev2/architecture/tests/x86/cache +0 -0
  25. carm_roofline-1.0.0.dev2/architecture/tests/x86/features +0 -0
  26. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/LICENSE +0 -0
  27. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/__init__.py +0 -0
  28. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/architecture.py +0 -0
  29. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/arm.py +0 -0
  30. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/config.py +0 -0
  31. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/detect.py +0 -0
  32. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/frequency.py +0 -0
  33. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/memory.py +0 -0
  34. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/riscv.py +0 -0
  35. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/arm/features.c +0 -0
  36. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/arm/frequency.h +0 -0
  37. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/arm/vlen.c +0 -0
  38. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/frequency.c +0 -0
  39. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/riscv/frequency.h +0 -0
  40. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/riscv/rvv_version.c +0 -0
  41. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/riscv/vlen.c +0 -0
  42. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/x86/cache.c +0 -0
  43. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/x86/features.c +0 -0
  44. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/x86/frequency.h +0 -0
  45. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
  46. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/architecture/x86.py +0 -0
  47. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/arguments.py +0 -0
  48. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/__init__.py +0 -0
  49. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/benchmark.py +0 -0
  50. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/__init__.py +0 -0
  51. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/arm.py +0 -0
  52. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/code_gen/__init__.py +0 -0
  53. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/code_gen/data_type.py +0 -0
  54. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/code_gen/instruction.py +0 -0
  55. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/code_gen/operation.py +0 -0
  56. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/code_gen/register.py +0 -0
  57. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/isa.py +0 -0
  58. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/parameters.py +0 -0
  59. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/riscv.py +0 -0
  60. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/generation/x86.py +0 -0
  61. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/interface.py +0 -0
  62. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/__init__.py +0 -0
  63. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/base.py +0 -0
  64. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/common.py +0 -0
  65. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/memory.py +0 -0
  66. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/memory_sweep.py +0 -0
  67. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/output/mixed.py +0 -0
  68. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/result.py +0 -0
  69. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/__init__.py +0 -0
  70. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/base.py +0 -0
  71. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/memory.py +0 -0
  72. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/benchmark/suites/mixed.py +0 -0
  73. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm.py +1 -1
  74. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/dependency_links.txt +0 -0
  75. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/carm_roofline.egg-info/entry_points.txt +0 -0
  76. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/context.py +0 -0
  77. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/error.py +0 -0
  78. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/exec_interface.py +0 -0
  79. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/__init__.py +0 -0
  80. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/assets/CARM_icon.svg +0 -0
  81. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/assets/CHAMP_logo.svg +0 -0
  82. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/assets/menu_icon.png +0 -0
  83. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/assets/style.css +0 -0
  84. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/gui/gui_utils.py +0 -0
  85. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/output_utils.py +0 -0
  86. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/setup.cfg +0 -0
  87. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/test_bench/__init__.py +0 -0
  88. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/test_bench/test_bench.c +0 -0
  89. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/test_bench/test_bench.h +0 -0
  90. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/test_bench/wrapper.inl +0 -0
  91. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/units.py +0 -0
  92. {carm_roofline-1.0.0.dev2 → carm_roofline-1.0.0.dev4}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.0.dev2
3
+ Version: 1.0.0.dev4
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
@@ -525,9 +525,10 @@ Description-Content-Type: text/markdown
525
525
  License-File: LICENSE
526
526
  Requires-Dist: rich>=14.3.2
527
527
  Requires-Dist: rich-argparse>=1.7.0
528
- Requires-Dist: numpy>=1.24.4
528
+ Requires-Dist: numpy>=2.0.0
529
529
  Requires-Dist: matplotlib>=3.7.2
530
530
  Requires-Dist: tomli>=2.0.0
531
+ Requires-Dist: platformdirs>=4.0.0
531
532
  Provides-Extra: gui
532
533
  Requires-Dist: dash[diskcache]; extra == "gui"
533
534
  Requires-Dist: dash-bootstrap-components; extra == "gui"
@@ -545,42 +546,45 @@ Requires-Dist: carm-roofline[dev,gui]; extra == "all"
545
546
  Dynamic: license-file
546
547
 
547
548
 
548
- # CARM Roofline
549
+ # The CARM Tool
549
550
 
550
- CARM is a Cache-Aware Roofline Model toolkit for CPU benchmarking and visualization.
551
+
552
+ Cache-Aware Roofline Model (CARM) toolkit - benchmark, visualize and profile Intel, AMD, ARM, RISC-V CPUs and NVIDIA, AMD GPUs.
551
553
 
552
554
  The package provides:
553
555
 
554
- - automatic microbenchmark generation for supported ISAs
556
+ - automatic benchmarking for supported ISAs
555
557
  - arithmetic, memory, and roofline benchmark modes
556
- - CLI output in table, JSON, CSV, and plot formats
557
558
  - optional web GUI for interactive result visualization
558
559
 
560
+ > [!WARNING]
561
+ > The development branch and associated python package are still experimental, and do not include all features yet. For a stable release, please refer to the main branch.
562
+
559
563
  ## Installation
560
564
 
561
565
  Install from PyPI:
562
566
 
563
567
  ```bash
568
+ # command-line interface only:
564
569
  pip install carm-roofline
570
+ # with GUI extras:
571
+ pip install carm-roofline[gui]
572
+ # with all extras (GUI + development tools):
573
+ pip install carm-roofline[all]
565
574
  ```
566
575
 
567
- Install with optional GUI dependencies:
568
-
569
- ```bash
570
- pip install "carm-roofline[gui]"
571
- ```
572
-
573
- Install all extras (GUI + development tools):
576
+ To avoid conflicts with other packages, we recommend installing in a virtual environment:
574
577
 
575
578
  ```bash
576
- pip install "carm-roofline[all]"
579
+ python -m venv carm-env
580
+ source carm-env/bin/activate
581
+ pip install carm-roofline[all]
577
582
  ```
578
583
 
579
584
  ## Requirements
580
585
 
581
- - Python 3.10+
582
- - Linux (POSIX)
583
- - GCC toolchain (for compiling benchmark binaries)
586
+ - Python 3.9+
587
+ - GCC (for compiling benchmark binaries)
584
588
 
585
589
  ## Command-Line Interface
586
590
 
@@ -594,7 +598,7 @@ Available subcommands:
594
598
 
595
599
  - `benchmark` - run benchmark suites
596
600
  - `gui` - launch the results dashboard
597
- - `profile` - reserved (not implemented yet)
601
+ - `profile` - coming soon
598
602
 
599
603
  Show help:
600
604
 
@@ -627,12 +631,12 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
627
631
 
628
632
  ## Output and Results
629
633
 
630
- By default, results are written under `carm_results`.
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/`.
631
635
 
632
636
  Control output directory and format(s):
633
637
 
634
638
  ```bash
635
- carm benchmark --output-dir carm_results --output-fmt table json csv plot
639
+ carm benchmark --output-dir ./carm_results --output-fmt table json csv plot
636
640
  ```
637
641
 
638
642
  Use `--name` to label result files:
@@ -652,12 +656,12 @@ carm gui
652
656
  Custom results location and port:
653
657
 
654
658
  ```bash
655
- carm gui --results-dir carm_results --gui-port 8050
659
+ carm gui --results-dir ./carm_results --gui-port 8050
656
660
  ```
657
661
 
658
662
  ## Publications and Citation
659
663
 
660
- If you use CARM in papers or reports, please cite:
664
+ If you use the CARM and the CARM Tool in papers or reports, please cite:
661
665
 
662
666
  <p>
663
667
  <a href="https://doi.org/10.1109/L-CA.2013.6" alt="Publication">
@@ -1,40 +1,43 @@
1
1
 
2
- # CARM Roofline
2
+ # The CARM Tool
3
3
 
4
- CARM is a Cache-Aware Roofline Model toolkit for CPU benchmarking and visualization.
4
+
5
+ Cache-Aware Roofline Model (CARM) toolkit - benchmark, visualize and profile Intel, AMD, ARM, RISC-V CPUs and NVIDIA, AMD GPUs.
5
6
 
6
7
  The package provides:
7
8
 
8
- - automatic microbenchmark generation for supported ISAs
9
+ - automatic benchmarking for supported ISAs
9
10
  - arithmetic, memory, and roofline benchmark modes
10
- - CLI output in table, JSON, CSV, and plot formats
11
11
  - optional web GUI for interactive result visualization
12
12
 
13
+ > [!WARNING]
14
+ > The development branch and associated python package are still experimental, and do not include all features yet. For a stable release, please refer to the main branch.
15
+
13
16
  ## Installation
14
17
 
15
18
  Install from PyPI:
16
19
 
17
20
  ```bash
21
+ # command-line interface only:
18
22
  pip install carm-roofline
23
+ # with GUI extras:
24
+ pip install carm-roofline[gui]
25
+ # with all extras (GUI + development tools):
26
+ pip install carm-roofline[all]
19
27
  ```
20
28
 
21
- Install with optional GUI dependencies:
22
-
23
- ```bash
24
- pip install "carm-roofline[gui]"
25
- ```
26
-
27
- Install all extras (GUI + development tools):
29
+ To avoid conflicts with other packages, we recommend installing in a virtual environment:
28
30
 
29
31
  ```bash
30
- pip install "carm-roofline[all]"
32
+ python -m venv carm-env
33
+ source carm-env/bin/activate
34
+ pip install carm-roofline[all]
31
35
  ```
32
36
 
33
37
  ## Requirements
34
38
 
35
- - Python 3.10+
36
- - Linux (POSIX)
37
- - GCC toolchain (for compiling benchmark binaries)
39
+ - Python 3.9+
40
+ - GCC (for compiling benchmark binaries)
38
41
 
39
42
  ## Command-Line Interface
40
43
 
@@ -48,7 +51,7 @@ Available subcommands:
48
51
 
49
52
  - `benchmark` - run benchmark suites
50
53
  - `gui` - launch the results dashboard
51
- - `profile` - reserved (not implemented yet)
54
+ - `profile` - coming soon
52
55
 
53
56
  Show help:
54
57
 
@@ -81,12 +84,12 @@ carm benchmark --dry-run --test arithmetic --test-time 1 --verbose 4
81
84
 
82
85
  ## Output and Results
83
86
 
84
- By default, results are written under `carm_results`.
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/`.
85
88
 
86
89
  Control output directory and format(s):
87
90
 
88
91
  ```bash
89
- carm benchmark --output-dir carm_results --output-fmt table json csv plot
92
+ carm benchmark --output-dir ./carm_results --output-fmt table json csv plot
90
93
  ```
91
94
 
92
95
  Use `--name` to label result files:
@@ -106,12 +109,12 @@ carm gui
106
109
  Custom results location and port:
107
110
 
108
111
  ```bash
109
- carm gui --results-dir carm_results --gui-port 8050
112
+ carm gui --results-dir ./carm_results --gui-port 8050
110
113
  ```
111
114
 
112
115
  ## Publications and Citation
113
116
 
114
- If you use CARM in papers or reports, please cite:
117
+ If you use the CARM and the CARM Tool in papers or reports, please cite:
115
118
 
116
119
  <p>
117
120
  <a href="https://doi.org/10.1109/L-CA.2013.6" alt="Publication">
@@ -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()
@@ -16,7 +16,7 @@ from output_utils import debug
16
16
  from units import Bandwidth, Bytes
17
17
 
18
18
  from ..benchmark import MemoryBenchmark
19
- from ..generation import MemoryBenchmarkParams
19
+ from ..generation import MemoryBenchmarkParams, MemoryLayoutMode
20
20
  from .base import ISABenchmarkSuite
21
21
 
22
22
  if TYPE_CHECKING:
@@ -92,8 +92,7 @@ class MemorySweepBenchmarkSuite(ISABenchmarkSuite):
92
92
  (level_num, level_info.name)
93
93
  for level_num, level_info in zip(
94
94
  topology.available_cache_levels(),
95
- topology,
96
- strict=True,
95
+ iter(topology),
97
96
  )
98
97
  ]
99
98
 
@@ -159,13 +158,15 @@ class MemorySweepBenchmarkSuite(ISABenchmarkSuite):
159
158
  size_per_thread = Bytes(int(size_bytes))
160
159
 
161
160
  # Classify: find the smallest cache level whose per-thread capacity
162
- # is ≥ the sweep size. Fall back to DRAM label if none matches.
161
+ # is >= the sweep size. Fall back to DRAM label if none matches.
163
162
  assigned_level_name: str = dram_level_name
164
163
  assigned_affinity: CacheAwareThreadAffinity = dram_affinity
165
164
  for _lvl_num, lvl_name, avail, aff in cache_levels_for_classification:
166
165
  if size_per_thread <= avail:
167
166
  assigned_level_name = lvl_name
168
167
  assigned_affinity = aff
168
+ # L1 can suffer from 4k aliasing - use single-array layout to prevent speculative forwarding issues
169
+ mem_layout = MemoryLayoutMode.single if _lvl_num == 1 else MemoryLayoutMode.split
169
170
  break
170
171
 
171
172
  # Use sweep{i:02d} as memory_level_name to guarantee unique C
@@ -178,6 +179,7 @@ class MemorySweepBenchmarkSuite(ISABenchmarkSuite):
178
179
  load_store_ratio=benchmark.ld_st_ratio,
179
180
  size_per_thread=size_per_thread,
180
181
  memory_level_name=sweep_label,
182
+ layout_mode=mem_layout,
181
183
  )
182
184
 
183
185
  bench_spec = isa.generate_memory(params, context)
@@ -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