counted-float 1.0.1__py3-none-any.whl

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 (40) hide show
  1. counted_float/__init__.py +39 -0
  2. counted_float/_core/__init__.py +0 -0
  3. counted_float/_core/_backwards_compat.py +6 -0
  4. counted_float/_core/_optional_deps.py +49 -0
  5. counted_float/_core/benchmarking/__init__.py +10 -0
  6. counted_float/_core/benchmarking/_flops_benchmark_suite.py +189 -0
  7. counted_float/_core/benchmarking/_flops_micro_benchmark.py +64 -0
  8. counted_float/_core/benchmarking/_micro_benchmark.py +113 -0
  9. counted_float/_core/benchmarking/_models.py +31 -0
  10. counted_float/_core/benchmarking/_time_utils.py +70 -0
  11. counted_float/_core/counting/__init__.py +3 -0
  12. counted_float/_core/counting/_builtin_data.py +25 -0
  13. counted_float/_core/counting/_context_managers.py +95 -0
  14. counted_float/_core/counting/_counted_float.py +201 -0
  15. counted_float/_core/counting/_global_counter.py +98 -0
  16. counted_float/_core/counting/config/__init__.py +6 -0
  17. counted_float/_core/counting/config/_config.py +54 -0
  18. counted_float/_core/counting/config/_defaults.py +49 -0
  19. counted_float/_core/counting/models/__init__.py +13 -0
  20. counted_float/_core/counting/models/_base.py +6 -0
  21. counted_float/_core/counting/models/_flop_counts.py +75 -0
  22. counted_float/_core/counting/models/_flop_type.py +47 -0
  23. counted_float/_core/counting/models/_flop_weights.py +85 -0
  24. counted_float/_core/counting/models/_flops_benchmark_result.py +69 -0
  25. counted_float/_core/counting/models/_fpu_instruction.py +22 -0
  26. counted_float/_core/counting/models/_fpu_specs.py +94 -0
  27. counted_float/_core/data/__init__.py +0 -0
  28. counted_float/_core/data/benchmarks/__init__.py +0 -0
  29. counted_float/_core/data/benchmarks/apple_m3_max.json +103 -0
  30. counted_float/_core/data/benchmarks/intel_i5_7200u.json +103 -0
  31. counted_float/_core/data/benchmarks/intel_i7_1265u.json +103 -0
  32. counted_float/_core/data/specs/__init__.py +0 -0
  33. counted_float/_core/data/specs/amd_zen4_r9_7900x.json +52 -0
  34. counted_float/_core/data/specs/intel_gen11_tiger_lake.json +52 -0
  35. counted_float/benchmarking/__init__.py +14 -0
  36. counted_float/config/__init__.py +15 -0
  37. counted_float-1.0.1.dist-info/METADATA +289 -0
  38. counted_float-1.0.1.dist-info/RECORD +40 -0
  39. counted_float-1.0.1.dist-info/WHEEL +4 -0
  40. counted_float-1.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,95 @@
1
+ """
2
+ Context manager that conveniently allows collection of flop counts for the enclosed code block,
3
+ as well as providing .pause() and .resume() methods to control flop counting.
4
+ """
5
+
6
+ from counted_float._core.counting._global_counter import GLOBAL_COUNTER
7
+ from counted_float._core.counting.models import FlopCounts
8
+
9
+
10
+ # =================================================================================================
11
+ # FlopCountingContext
12
+ # =================================================================================================
13
+ class FlopCountingContext:
14
+ """
15
+ Context manager that can be used to count FLOP operations in a block of code. Only floating-point
16
+ operations of CountedFloat objects are counted. So make sure all math uses this type.
17
+
18
+ Flops need to be registered by either of the following:
19
+ - calls to register_flops(...)
20
+ - using CountedFloat() objects in the computations
21
+
22
+ LIMITATIONS:
23
+ - this context manager is not thread-safe
24
+ - not _all_ floating-point operations are counted, see the docs for more details.
25
+ """
26
+
27
+ # -------------------------------------------------------------------------
28
+ # Constructor
29
+ # -------------------------------------------------------------------------
30
+ def __init__(self):
31
+ # Active/inactive flag (toggled by __enter__ and __exit__ + by pause() and resume() methods)
32
+ # When inactive:
33
+ # - current count == self.__cnt_subtotal
34
+ # When active:
35
+ # - current count == GLOBAL_COUNTER - self.__cnt_start_snapshot
36
+ self.__active: bool = False
37
+
38
+ # flop count bookkeeping
39
+ self.__cnt_subtotal: FlopCounts = FlopCounts()
40
+ self.__cnt_start_snapshot: FlopCounts = FlopCounts()
41
+
42
+ # -------------------------------------------------------------------------
43
+ # Properties
44
+ # -------------------------------------------------------------------------
45
+ def is_active(self) -> bool:
46
+ return self.__active
47
+
48
+ def flop_counts(self) -> FlopCounts:
49
+ """Returns current total flop count for this context manager. See constructor comments for details."""
50
+ if self.__active:
51
+ return GLOBAL_COUNTER.flop_counts() - self.__cnt_start_snapshot
52
+ else:
53
+ return self.__cnt_subtotal.copy()
54
+
55
+ # -------------------------------------------------------------------------
56
+ # Pause/Resume
57
+ # -------------------------------------------------------------------------
58
+ def pause(self):
59
+ if self.__active:
60
+ self.__cnt_subtotal = self.flop_counts()
61
+ self.__cnt_start_snapshot = FlopCounts()
62
+ self.__active = False
63
+
64
+ def resume(self):
65
+ if not self.__active:
66
+ self.__cnt_start_snapshot = GLOBAL_COUNTER.flop_counts() - self.__cnt_subtotal
67
+ self.__cnt_subtotal = FlopCounts()
68
+ self.__active = True
69
+
70
+ # -------------------------------------------------------------------------
71
+ # Context manager interface
72
+ # -------------------------------------------------------------------------
73
+ def __enter__(self):
74
+ self.resume()
75
+ return self
76
+
77
+ def __exit__(self, exc_type, exc_val, exc_tb):
78
+ self.pause()
79
+
80
+
81
+ # =================================================================================================
82
+ # PauseFlopCounting
83
+ # =================================================================================================
84
+ class PauseFlopCounting:
85
+ """
86
+ Context manager that pauses flop counting for the enclosed code block. This acts globally, across all
87
+ active FlopCountingContext instances.
88
+ """
89
+
90
+ def __enter__(self):
91
+ GLOBAL_COUNTER.pause()
92
+ return self
93
+
94
+ def __exit__(self, exc_type, exc_val, exc_tb):
95
+ GLOBAL_COUNTER.resume()
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ from ._global_counter import GLOBAL_COUNTER
6
+ from .models import FlopCounts
7
+
8
+
9
+ class CountedFloat(float):
10
+ # -------------------------------------------------------------------------
11
+ # FLOP COUNTING
12
+ # -------------------------------------------------------------------------
13
+ @classmethod
14
+ def get_global_flop_counts(cls) -> FlopCounts:
15
+ """
16
+ Returns the global FLOP counts for all CountedFloat instances.
17
+ """
18
+ return GLOBAL_COUNTER.flop_counts()
19
+
20
+ # -------------------------------------------------------------------------
21
+ # CONSTRUCTOR
22
+ # -------------------------------------------------------------------------
23
+ def __new__(cls, value: float):
24
+ self = super().__new__(cls, value)
25
+ return self
26
+
27
+ def __str__(self):
28
+ return self.__repr__()
29
+
30
+ def __repr__(self):
31
+ return f"CountedFloat({super().__repr__()})"
32
+
33
+ def __hash__(self):
34
+ return super().__hash__()
35
+
36
+ # -------------------------------------------------------------------------
37
+ # OVERLOADED MATH OPERATIONS
38
+ # -------------------------------------------------------------------------
39
+ def __abs__(self) -> CountedFloat:
40
+ """abs(x)"""
41
+ GLOBAL_COUNTER.incr_abs()
42
+ return CountedFloat(super().__abs__())
43
+
44
+ def __neg__(self) -> CountedFloat:
45
+ """-x"""
46
+ GLOBAL_COUNTER.incr_minus()
47
+ return CountedFloat(super().__neg__())
48
+
49
+ def __eq__(self, other) -> bool:
50
+ """x==other or other==x"""
51
+ if isinstance(other, int) and other == 0:
52
+ GLOBAL_COUNTER.incr_cmp_zero()
53
+ else:
54
+ GLOBAL_COUNTER.incr_equals()
55
+ return super().__eq__(other)
56
+
57
+ def __ne__(self, other) -> bool:
58
+ """x!=other or other!=x"""
59
+ if isinstance(other, int) and other == 0:
60
+ GLOBAL_COUNTER.incr_cmp_zero()
61
+ else:
62
+ GLOBAL_COUNTER.incr_equals()
63
+ return super().__ne__(other)
64
+
65
+ def __lt__(self, other):
66
+ """x<other"""
67
+ if isinstance(other, int) and other == 0:
68
+ GLOBAL_COUNTER.incr_cmp_zero()
69
+ else:
70
+ GLOBAL_COUNTER.incr_lte()
71
+ return super().__lt__(other)
72
+
73
+ def __le__(self, other):
74
+ """x<=other"""
75
+ if isinstance(other, int) and other == 0:
76
+ GLOBAL_COUNTER.incr_cmp_zero()
77
+ else:
78
+ GLOBAL_COUNTER.incr_lte()
79
+ return super().__le__(other)
80
+
81
+ def __gt__(self, other):
82
+ """x>other"""
83
+ if isinstance(other, int) and other == 0:
84
+ GLOBAL_COUNTER.incr_cmp_zero()
85
+ else:
86
+ GLOBAL_COUNTER.incr_gte()
87
+ return super().__gt__(other)
88
+
89
+ def __ge__(self, other):
90
+ """x>=other"""
91
+ if isinstance(other, int) and other == 0:
92
+ GLOBAL_COUNTER.incr_cmp_zero()
93
+ else:
94
+ GLOBAL_COUNTER.incr_gte()
95
+ return super().__ge__(other)
96
+
97
+ def __round__(self, n=None) -> int:
98
+ """round(x, n)"""
99
+ if n:
100
+ raise ValueError("only n==None or n==0 are supported in a CountedFloat")
101
+ GLOBAL_COUNTER.incr_rnd() # assuming n=0, otherwise we can't reliably count the flops
102
+ return super().__round__()
103
+
104
+ def __floor__(self) -> int:
105
+ """math.floor(x)"""
106
+ GLOBAL_COUNTER.incr_rnd()
107
+ return super().__floor__()
108
+
109
+ def __ceil__(self) -> int:
110
+ """math.ceil(x)"""
111
+ GLOBAL_COUNTER.incr_rnd()
112
+ return super().__ceil__()
113
+
114
+ def __add__(self, other) -> CountedFloat:
115
+ """x+other"""
116
+ GLOBAL_COUNTER.incr_add()
117
+ return CountedFloat(super().__add__(other))
118
+
119
+ def __radd__(self, other) -> CountedFloat:
120
+ """other+x"""
121
+ GLOBAL_COUNTER.incr_add()
122
+ return CountedFloat(super().__radd__(other))
123
+
124
+ def __sub__(self, other) -> CountedFloat:
125
+ """x-other"""
126
+ GLOBAL_COUNTER.incr_sub()
127
+ return CountedFloat(super().__sub__(other))
128
+
129
+ def __rsub__(self, other) -> CountedFloat:
130
+ """other-x"""
131
+ GLOBAL_COUNTER.incr_sub()
132
+ return CountedFloat(super().__rsub__(other))
133
+
134
+ def __mul__(self, other) -> CountedFloat:
135
+ """x*other or other*x"""
136
+ GLOBAL_COUNTER.incr_mul()
137
+ return CountedFloat(super().__mul__(other))
138
+
139
+ def __rmul__(self, other) -> CountedFloat:
140
+ """other*x"""
141
+ GLOBAL_COUNTER.incr_mul()
142
+ return CountedFloat(super().__rmul__(other))
143
+
144
+ def __truediv__(self, other) -> CountedFloat:
145
+ """x/other"""
146
+ GLOBAL_COUNTER.incr_div()
147
+ return CountedFloat(super().__truediv__(other))
148
+
149
+ def __rtruediv__(self, other) -> CountedFloat:
150
+ """other/x"""
151
+ GLOBAL_COUNTER.incr_div()
152
+ return CountedFloat(super().__rtruediv__(other))
153
+
154
+ def __pow__(self, other) -> CountedFloat:
155
+ """x**other"""
156
+ if isinstance(other, int) and other == 2:
157
+ GLOBAL_COUNTER.incr_mul() # x^2 = x*x
158
+ else:
159
+ GLOBAL_COUNTER.incr_pow()
160
+ return CountedFloat(super().__pow__(other))
161
+
162
+ def __rpow__(self, other) -> CountedFloat:
163
+ """other**x"""
164
+ if isinstance(other, int) and other == 2:
165
+ GLOBAL_COUNTER.incr_pow2()
166
+ else:
167
+ GLOBAL_COUNTER.incr_pow()
168
+ return CountedFloat(super().__rpow__(other))
169
+
170
+
171
+ # -------------------------------------------------------------------------
172
+ # override some methods of math module
173
+ # -------------------------------------------------------------------------
174
+ original_math_sqrt = math.sqrt
175
+ original_math_log2 = math.log2
176
+
177
+
178
+ def math_sqrt(x: float) -> float | CountedFloat:
179
+ if isinstance(x, CountedFloat):
180
+ GLOBAL_COUNTER.incr_sqrt()
181
+ return CountedFloat(original_math_sqrt(x))
182
+ else:
183
+ return original_math_sqrt(x)
184
+
185
+
186
+ def math_log2(x: float) -> float | CountedFloat:
187
+ if isinstance(x, CountedFloat):
188
+ GLOBAL_COUNTER.incr_log2()
189
+ return CountedFloat(original_math_log2(x))
190
+ else:
191
+ return original_math_log2(x)
192
+
193
+
194
+ def math_pow(x: float, y: float) -> float | CountedFloat:
195
+ return x**y
196
+
197
+
198
+ # override math module methods
199
+ math.sqrt = math_sqrt
200
+ math.log2 = math_log2
201
+ math.pow = math_pow
@@ -0,0 +1,98 @@
1
+ from counted_float._core.counting.models import FlopCounts
2
+
3
+
4
+ class GlobalFlopCounter:
5
+ """
6
+ Global counter for FLOP operations. Essentially this class wraps around a FlopCounts object,
7
+ limiting access to its fields (only allowing incrementing them) and providing a way to access copies of the counts.
8
+ On top of this, the class allows pausing and resuming counting globally.
9
+ """
10
+
11
+ # -------------------------------------------------------------------------
12
+ # Constructor
13
+ # -------------------------------------------------------------------------
14
+ def __init__(self):
15
+ self.__counts = FlopCounts()
16
+ self.__incr = 1 # 1 if enabled, 0 if paused
17
+
18
+ # -------------------------------------------------------------------------
19
+ # Pause / Resume / Status API
20
+ # -------------------------------------------------------------------------
21
+ def pause(self):
22
+ self.__incr = 0
23
+
24
+ def resume(self):
25
+ self.__incr = 1
26
+
27
+ def reset(self):
28
+ self.__counts.reset()
29
+ self.resume()
30
+
31
+ def is_active(self) -> bool:
32
+ return self.__incr > 0
33
+
34
+ def flop_counts(self) -> FlopCounts:
35
+ return self.__counts.copy()
36
+
37
+ def total_count(self) -> int:
38
+ """Shorthand for self.flop_counts().total_count()"""
39
+ return self.__counts.total_count()
40
+
41
+ def __getattr__(self, item):
42
+ # provide shorthand access to the counts
43
+ if item in FlopCounts.field_names():
44
+ return getattr(self.__counts, item)
45
+ else:
46
+ raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'")
47
+
48
+ # -------------------------------------------------------------------------
49
+ # Incrementing counts
50
+ # -------------------------------------------------------------------------
51
+ def incr_abs(self):
52
+ self.__counts.ABS += self.__incr
53
+
54
+ def incr_minus(self):
55
+ self.__counts.MINUS += self.__incr
56
+
57
+ def incr_equals(self):
58
+ self.__counts.EQUALS += self.__incr
59
+
60
+ def incr_gte(self):
61
+ self.__counts.GTE += self.__incr
62
+
63
+ def incr_lte(self):
64
+ self.__counts.LTE += self.__incr
65
+
66
+ def incr_cmp_zero(self):
67
+ self.__counts.CMP_ZERO += self.__incr
68
+
69
+ def incr_rnd(self):
70
+ self.__counts.RND += self.__incr
71
+
72
+ def incr_add(self):
73
+ self.__counts.ADD += self.__incr
74
+
75
+ def incr_sub(self):
76
+ self.__counts.SUB += self.__incr
77
+
78
+ def incr_mul(self):
79
+ self.__counts.MUL += self.__incr
80
+
81
+ def incr_div(self):
82
+ self.__counts.DIV += self.__incr
83
+
84
+ def incr_sqrt(self):
85
+ self.__counts.SQRT += self.__incr
86
+
87
+ def incr_pow2(self):
88
+ self.__counts.POW2 += self.__incr
89
+
90
+ def incr_log2(self):
91
+ self.__counts.LOG2 += self.__incr
92
+
93
+ def incr_pow(self):
94
+ self.__counts.POW += self.__incr
95
+
96
+
97
+ # --- global variable through which we access the global counter ---
98
+ GLOBAL_COUNTER = GlobalFlopCounter()
@@ -0,0 +1,6 @@
1
+ from ._config import get_flop_weights, set_flop_weights
2
+ from ._defaults import (
3
+ get_default_consensus_flop_weights,
4
+ get_default_empirical_flop_weights,
5
+ get_default_theoretical_flop_weights,
6
+ )
@@ -0,0 +1,54 @@
1
+ from ..models import FlopWeights
2
+ from ._defaults import get_default_consensus_flop_weights
3
+
4
+
5
+ # =================================================================================================
6
+ # Central class for weight configuration
7
+ # =================================================================================================
8
+ class Config:
9
+ """Class to hold configuration settings for the counted_float package."""
10
+
11
+ # -------------------------------------------------------------------------
12
+ # Internal State
13
+ # -------------------------------------------------------------------------
14
+
15
+ # these are the weights that are used to calculate weighted flop counts; update with set_flop_weights(...)
16
+ __weights: FlopWeights = get_default_consensus_flop_weights()
17
+
18
+ # -------------------------------------------------------------------------
19
+ # Configuration Methods
20
+ # -------------------------------------------------------------------------
21
+ @classmethod
22
+ def set_flop_weights(cls, weights: FlopWeights):
23
+ """
24
+ Set the weights for the flops used in the package. These weights will be used in any calculation of
25
+ weighted flops, going forward.
26
+ :param weights: FlopWeights instance containing the weights.
27
+ """
28
+ cls.__weights = weights
29
+
30
+ @classmethod
31
+ def get_flop_weights(cls) -> FlopWeights:
32
+ """
33
+ Get the currently configured flop weights.
34
+ """
35
+ return cls.__weights.model_copy()
36
+
37
+
38
+ # =================================================================================================
39
+ # Functional accessors
40
+ # =================================================================================================
41
+ def set_flop_weights(weights: FlopWeights):
42
+ """
43
+ Set the weights for the flops used in the package. These weights will be used in any calculation of
44
+ weighted flops, going forward.
45
+ :param weights: FlopWeights instance containing the weights.
46
+ """
47
+ Config.set_flop_weights(weights)
48
+
49
+
50
+ def get_flop_weights() -> FlopWeights:
51
+ """
52
+ Get the currently configured flop weights.
53
+ """
54
+ return Config.get_flop_weights()
@@ -0,0 +1,49 @@
1
+ from functools import cache
2
+
3
+ from counted_float._core.counting._builtin_data import BuiltInData
4
+
5
+ from ..models import FlopWeights
6
+
7
+
8
+ @cache
9
+ def get_default_consensus_flop_weights(rounded: bool = True) -> FlopWeights:
10
+ """
11
+ Get the default CONSENSUS flop weights.
12
+ Computed as the geo-mean of the unrounded empirical and theoretical weights, rounded to the nearest integer.
13
+ """
14
+ weights = FlopWeights.as_geo_mean(
15
+ [
16
+ get_default_empirical_flop_weights(rounded=False),
17
+ get_default_theoretical_flop_weights(rounded=False),
18
+ ]
19
+ )
20
+ if rounded:
21
+ return weights.round()
22
+ else:
23
+ return weights
24
+
25
+
26
+ @cache
27
+ def get_default_empirical_flop_weights(rounded: bool = True) -> FlopWeights:
28
+ """
29
+ Get the default EMPIRICAL flop weights.
30
+ Computed as the geo-mean of flop weights estimated from built-in benchmark results.
31
+ """
32
+ weights = FlopWeights.as_geo_mean([v.flop_weights for v in BuiltInData.benchmarks().values()])
33
+ if rounded:
34
+ return weights.round()
35
+ else:
36
+ return weights
37
+
38
+
39
+ @cache
40
+ def get_default_theoretical_flop_weights(rounded: bool = True) -> FlopWeights:
41
+ """
42
+ Get the default THEORETICAL flop weights.
43
+ Computed as the geo-mean of flop weights estimated from built-in instruction latency analyse.
44
+ """
45
+ weights = FlopWeights.as_geo_mean([v.flop_weights for v in BuiltInData.specs().values()])
46
+ if rounded:
47
+ return weights.round()
48
+ else:
49
+ return weights
@@ -0,0 +1,13 @@
1
+ from ._base import MyBaseModel
2
+ from ._flop_counts import FlopCounts
3
+ from ._flop_type import FlopType
4
+ from ._flop_weights import FlopWeights
5
+ from ._flops_benchmark_result import (
6
+ BenchmarkSettings,
7
+ FlopsBenchmarkDurations,
8
+ FlopsBenchmarkResults,
9
+ Quantiles,
10
+ SystemInfo,
11
+ )
12
+ from ._fpu_instruction import FPUInstruction
13
+ from ._fpu_specs import InstructionLatencies
@@ -0,0 +1,6 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class MyBaseModel(BaseModel):
5
+ def show(self):
6
+ print(self.model_dump_json(indent=4))
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+
5
+ from ._flop_type import FlopType
6
+ from ._flop_weights import FlopWeights
7
+
8
+
9
+ @dataclasses.dataclass(slots=True)
10
+ class FlopCounts:
11
+ """
12
+ Class to keep track of flop counts per flop type. The implementation is different from
13
+ the FlopWeights class, for two reasons:
14
+ - there's no need for (de)serialization, hence no usage of Pydantic
15
+ - we want to minimize overhead of flop counting, hence use no dict in favor of explicit fields per flop type
16
+ """
17
+
18
+ # --- Counting fields ---------------------------------
19
+ ABS: int = 0
20
+ MINUS: int = 0
21
+ EQUALS: int = 0
22
+ GTE: int = 0
23
+ LTE: int = 0
24
+ CMP_ZERO: int = 0
25
+ RND: int = 0
26
+ ADD: int = 0
27
+ SUB: int = 0
28
+ MUL: int = 0
29
+ DIV: int = 0
30
+ SQRT: int = 0
31
+ POW2: int = 0
32
+ LOG2: int = 0
33
+ POW: int = 0
34
+
35
+ # --- math --------------------------------------------
36
+ def __add__(self, other: FlopCounts) -> FlopCounts:
37
+ return FlopCounts(**{attr: getattr(self, attr) + getattr(other, attr) for attr in self.field_names()})
38
+
39
+ def __sub__(self, other: FlopCounts) -> FlopCounts:
40
+ return FlopCounts(**{attr: getattr(self, attr) - getattr(other, attr) for attr in self.field_names()})
41
+
42
+ # --- extract info ------------------------------------
43
+ def as_dict(self) -> dict[FlopType, int]:
44
+ """Return the flop counts as a dictionary with FlopType keys."""
45
+ return {flop_type: getattr(self, flop_type.name) for flop_type in FlopType}
46
+
47
+ def total_count(self) -> int:
48
+ """Sum of all flop counts."""
49
+ return sum(getattr(self, attr) for attr in self.field_names())
50
+
51
+ def total_weighted_cost(self, weights: FlopWeights | None = None) -> float:
52
+ """
53
+ Returns a weighted total count of all flops (counterpart of the unweighted total_count() method),
54
+ using the provided weights in the computations.
55
+ When omitted, the currently configured weights (see Config class) will be used.
56
+ """
57
+ if not weights:
58
+ from counted_float._core.counting.config import get_flop_weights
59
+
60
+ weights = get_flop_weights()
61
+
62
+ return sum([getattr(self, flop_type.name) * weights.weights[flop_type] for flop_type in FlopType])
63
+
64
+ # --- other -------------------------------------------
65
+ def reset(self):
66
+ """Reset all counts to 0"""
67
+ for attr in self.field_names():
68
+ setattr(self, attr, 0)
69
+
70
+ def copy(self) -> FlopCounts:
71
+ return FlopCounts(**dataclasses.asdict(self))
72
+
73
+ @classmethod
74
+ def field_names(cls) -> list[str]:
75
+ return [field.name for field in dataclasses.fields(cls)]
@@ -0,0 +1,47 @@
1
+ from counted_float._core._backwards_compat import StrEnum
2
+
3
+
4
+ class FlopType(StrEnum):
5
+ """
6
+ Enum describing the different types of floating-point operations,
7
+ each of which are counted separately and can potentially have different weights.
8
+
9
+ Enum Math ~corresponding
10
+ Member Operation x87 instruction(s)
11
+ ------- ---------- -------------------
12
+
13
+ ABS abs(x) FABS
14
+ MINUS -x FCHS
15
+ EQUALS x == y FCOM
16
+ GTE x >= y FCOM
17
+ LTE x <= y FCOM
18
+ CMP_ZERO x >= 0 FTST
19
+ RND ceil(x) or floor(x) FRNDINT
20
+ ADD x + y FADD
21
+ SUB x - y FSUB
22
+ MUL x * y FMUL
23
+ DIV x / y FDIV
24
+ SQRT sqrt(x) FSQRT
25
+ POW2 2**x > F2XM1
26
+ LOG2 log2(x) FYLX2
27
+ POW x**y > F2XM1 + FYLX2 + FMUL
28
+ """
29
+
30
+ ABS = "abs(x)"
31
+ MINUS = "-x"
32
+ EQUALS = "x==y"
33
+ GTE = "x>=y"
34
+ LTE = "x<=y"
35
+ CMP_ZERO = "x>=0"
36
+ RND = "round(x)"
37
+ ADD = "x+y"
38
+ SUB = "x-y"
39
+ MUL = "x*y"
40
+ DIV = "x/y"
41
+ SQRT = "sqrt(x)"
42
+ POW2 = "2^x"
43
+ LOG2 = "log2(x)"
44
+ POW = "x^y"
45
+
46
+ def long_name(self) -> str:
47
+ return f"FlopType.{self.name:<9} [{self.value}]"