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.
- counted_float/__init__.py +39 -0
- counted_float/_core/__init__.py +0 -0
- counted_float/_core/_backwards_compat.py +6 -0
- counted_float/_core/_optional_deps.py +49 -0
- counted_float/_core/benchmarking/__init__.py +10 -0
- counted_float/_core/benchmarking/_flops_benchmark_suite.py +189 -0
- counted_float/_core/benchmarking/_flops_micro_benchmark.py +64 -0
- counted_float/_core/benchmarking/_micro_benchmark.py +113 -0
- counted_float/_core/benchmarking/_models.py +31 -0
- counted_float/_core/benchmarking/_time_utils.py +70 -0
- counted_float/_core/counting/__init__.py +3 -0
- counted_float/_core/counting/_builtin_data.py +25 -0
- counted_float/_core/counting/_context_managers.py +95 -0
- counted_float/_core/counting/_counted_float.py +201 -0
- counted_float/_core/counting/_global_counter.py +98 -0
- counted_float/_core/counting/config/__init__.py +6 -0
- counted_float/_core/counting/config/_config.py +54 -0
- counted_float/_core/counting/config/_defaults.py +49 -0
- counted_float/_core/counting/models/__init__.py +13 -0
- counted_float/_core/counting/models/_base.py +6 -0
- counted_float/_core/counting/models/_flop_counts.py +75 -0
- counted_float/_core/counting/models/_flop_type.py +47 -0
- counted_float/_core/counting/models/_flop_weights.py +85 -0
- counted_float/_core/counting/models/_flops_benchmark_result.py +69 -0
- counted_float/_core/counting/models/_fpu_instruction.py +22 -0
- counted_float/_core/counting/models/_fpu_specs.py +94 -0
- counted_float/_core/data/__init__.py +0 -0
- counted_float/_core/data/benchmarks/__init__.py +0 -0
- counted_float/_core/data/benchmarks/apple_m3_max.json +103 -0
- counted_float/_core/data/benchmarks/intel_i5_7200u.json +103 -0
- counted_float/_core/data/benchmarks/intel_i7_1265u.json +103 -0
- counted_float/_core/data/specs/__init__.py +0 -0
- counted_float/_core/data/specs/amd_zen4_r9_7900x.json +52 -0
- counted_float/_core/data/specs/intel_gen11_tiger_lake.json +52 -0
- counted_float/benchmarking/__init__.py +14 -0
- counted_float/config/__init__.py +15 -0
- counted_float-1.0.1.dist-info/METADATA +289 -0
- counted_float-1.0.1.dist-info/RECORD +40 -0
- counted_float-1.0.1.dist-info/WHEEL +4 -0
- 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,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,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}]"
|