flopscope 0.2.0__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.
- benchmarks/__init__.py +1 -0
- benchmarks/__main__.py +6 -0
- benchmarks/_baseline.py +171 -0
- benchmarks/_bitwise.py +231 -0
- benchmarks/_complex.py +176 -0
- benchmarks/_contractions.py +291 -0
- benchmarks/_fft.py +198 -0
- benchmarks/_impl_urls.py +139 -0
- benchmarks/_linalg.py +197 -0
- benchmarks/_linalg_delegates.py +407 -0
- benchmarks/_metadata.py +141 -0
- benchmarks/_misc.py +653 -0
- benchmarks/_perf.py +321 -0
- benchmarks/_perm_group_calibration.py +175 -0
- benchmarks/_pointwise.py +372 -0
- benchmarks/_polynomial.py +193 -0
- benchmarks/_random.py +209 -0
- benchmarks/_reductions.py +136 -0
- benchmarks/_sorting.py +289 -0
- benchmarks/_stats.py +137 -0
- benchmarks/_window.py +92 -0
- benchmarks/accumulation/__init__.py +0 -0
- benchmarks/accumulation/bench_cost_compute.py +138 -0
- benchmarks/dashboard.py +312 -0
- benchmarks/runner.py +636 -0
- flopscope/__init__.py +273 -0
- flopscope/_accumulation/__init__.py +13 -0
- flopscope/_accumulation/_bipartite.py +121 -0
- flopscope/_accumulation/_burnside.py +51 -0
- flopscope/_accumulation/_cache.py +146 -0
- flopscope/_accumulation/_components.py +153 -0
- flopscope/_accumulation/_cost.py +1414 -0
- flopscope/_accumulation/_cost_descriptions.py +63 -0
- flopscope/_accumulation/_detection.py +318 -0
- flopscope/_accumulation/_ladder.py +191 -0
- flopscope/_accumulation/_output_orbit.py +104 -0
- flopscope/_accumulation/_partition.py +290 -0
- flopscope/_accumulation/_path_info.py +211 -0
- flopscope/_accumulation/_public.py +169 -0
- flopscope/_accumulation/_reduction.py +310 -0
- flopscope/_accumulation/_regimes.py +303 -0
- flopscope/_accumulation/_shape.py +33 -0
- flopscope/_accumulation/_wreath.py +209 -0
- flopscope/_budget.py +1027 -0
- flopscope/_config.py +118 -0
- flopscope/_counting_ops.py +451 -0
- flopscope/_display.py +478 -0
- flopscope/_docstrings.py +59 -0
- flopscope/_dtypes.py +20 -0
- flopscope/_einsum.py +717 -0
- flopscope/_errstate.py +25 -0
- flopscope/_flops.py +282 -0
- flopscope/_free_ops.py +2654 -0
- flopscope/_ndarray.py +1126 -0
- flopscope/_opt_einsum/LICENSE +21 -0
- flopscope/_opt_einsum/NOTICE +59 -0
- flopscope/_opt_einsum/__init__.py +209 -0
- flopscope/_opt_einsum/_contract.py +1478 -0
- flopscope/_opt_einsum/_helpers.py +164 -0
- flopscope/_opt_einsum/_hsluv.py +273 -0
- flopscope/_opt_einsum/_path_random.py +462 -0
- flopscope/_opt_einsum/_paths.py +1653 -0
- flopscope/_opt_einsum/_subgraph_symmetry.py +544 -0
- flopscope/_opt_einsum/_symmetry.py +140 -0
- flopscope/_opt_einsum/_typing.py +37 -0
- flopscope/_perm_group.py +717 -0
- flopscope/_pointwise.py +2522 -0
- flopscope/_polynomial.py +278 -0
- flopscope/_registry.py +3216 -0
- flopscope/_sorting_ops.py +571 -0
- flopscope/_symmetric.py +812 -0
- flopscope/_symmetry_transport.py +510 -0
- flopscope/_symmetry_utils.py +669 -0
- flopscope/_type_info.py +12 -0
- flopscope/_unwrap.py +70 -0
- flopscope/_validation.py +83 -0
- flopscope/_version_check.py +46 -0
- flopscope/_weights.py +195 -0
- flopscope/_window.py +177 -0
- flopscope/accounting.py +565 -0
- flopscope/data/default_weights.json +462 -0
- flopscope/data/weights.csv +509 -0
- flopscope/errors.py +197 -0
- flopscope/numpy/__init__.py +878 -0
- flopscope/numpy/fft/__init__.py +55 -0
- flopscope/numpy/fft/_free.py +51 -0
- flopscope/numpy/fft/_transforms.py +695 -0
- flopscope/numpy/linalg/__init__.py +105 -0
- flopscope/numpy/linalg/_aliases.py +126 -0
- flopscope/numpy/linalg/_compound.py +161 -0
- flopscope/numpy/linalg/_decompositions.py +353 -0
- flopscope/numpy/linalg/_properties.py +533 -0
- flopscope/numpy/linalg/_solvers.py +444 -0
- flopscope/numpy/linalg/_svd.py +122 -0
- flopscope/numpy/random/__init__.py +684 -0
- flopscope/numpy/random/_cost_formulas.py +115 -0
- flopscope/numpy/random/_counted_classes.py +241 -0
- flopscope/numpy/testing/__init__.py +13 -0
- flopscope/numpy/typing/__init__.py +30 -0
- flopscope/py.typed +0 -0
- flopscope/stats/__init__.py +84 -0
- flopscope/stats/_base.py +77 -0
- flopscope/stats/_cauchy.py +146 -0
- flopscope/stats/_erf.py +190 -0
- flopscope/stats/_expon.py +146 -0
- flopscope/stats/_laplace.py +150 -0
- flopscope/stats/_logistic.py +148 -0
- flopscope/stats/_lognorm.py +160 -0
- flopscope/stats/_ndtri.py +133 -0
- flopscope/stats/_norm.py +149 -0
- flopscope/stats/_truncnorm.py +186 -0
- flopscope/stats/_uniform.py +141 -0
- flopscope-0.2.0.dist-info/METADATA +23 -0
- flopscope-0.2.0.dist-info/RECORD +115 -0
- flopscope-0.2.0.dist-info/WHEEL +4 -0
flopscope/_errstate.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""NumPy runtime configuration and iteration utilities re-exported as free.
|
|
2
|
+
|
|
3
|
+
These are all pure numpy state management and iteration helpers.
|
|
4
|
+
None perform FLOP-costing operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as _np
|
|
10
|
+
|
|
11
|
+
# Error state (context manager + getters/setters for numpy warnings)
|
|
12
|
+
errstate = _np.errstate
|
|
13
|
+
seterr = _np.seterr
|
|
14
|
+
geterr = _np.geterr
|
|
15
|
+
|
|
16
|
+
# Iteration utilities
|
|
17
|
+
ndindex = _np.ndindex
|
|
18
|
+
ndenumerate = _np.ndenumerate
|
|
19
|
+
broadcast = _np.broadcast
|
|
20
|
+
nditer = _np.nditer
|
|
21
|
+
|
|
22
|
+
# Print formatting
|
|
23
|
+
set_printoptions = _np.set_printoptions
|
|
24
|
+
get_printoptions = _np.get_printoptions
|
|
25
|
+
printoptions = _np.printoptions
|
flopscope/_flops.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""FLOP cost calculators for flopscope operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections import Counter
|
|
7
|
+
|
|
8
|
+
from flopscope._perm_group import SymmetryGroup
|
|
9
|
+
from flopscope._symmetry_utils import unique_elements_for_shape
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_einsum_subscripts(subscripts: str) -> tuple[list[list[str]], list[str]]:
|
|
13
|
+
"""Parse an einsum subscript string into input and output index lists.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
subscripts : str
|
|
18
|
+
Einsum subscript string (e.g., ``'ij,jk->ik'``).
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
inputs : list of list of str
|
|
23
|
+
Index labels for each input operand.
|
|
24
|
+
output : list of str
|
|
25
|
+
Index labels for the output.
|
|
26
|
+
"""
|
|
27
|
+
subscripts = subscripts.replace(" ", "")
|
|
28
|
+
if "->" in subscripts:
|
|
29
|
+
input_part, output_part = subscripts.split("->")
|
|
30
|
+
output = list(output_part)
|
|
31
|
+
else:
|
|
32
|
+
input_part = subscripts
|
|
33
|
+
all_labels: list[str] = []
|
|
34
|
+
for part in input_part.split(","):
|
|
35
|
+
all_labels.extend(list(part))
|
|
36
|
+
counts = Counter(all_labels)
|
|
37
|
+
output = sorted(lbl for lbl, c in counts.items() if c == 1)
|
|
38
|
+
inputs = [list(part) for part in input_part.split(",")]
|
|
39
|
+
return inputs, output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def einsum_cost(
|
|
43
|
+
subscripts: str,
|
|
44
|
+
shapes: list[tuple[int, ...]],
|
|
45
|
+
operand_symmetries: list[SymmetryGroup | None] | None = None,
|
|
46
|
+
identity_pattern: tuple[tuple[int, ...], ...] | None = None,
|
|
47
|
+
) -> int:
|
|
48
|
+
"""FLOP cost of an einsum operation.
|
|
49
|
+
|
|
50
|
+
Uses the whole-expression direct-event accumulation model:
|
|
51
|
+
total = (k-1) * prod(M) + prod(alpha), where M is the number of
|
|
52
|
+
unique output elements and alpha is the number of unique output+
|
|
53
|
+
reduction-axis combinations. FMA = 2 (textbook): the α/M formula counts
|
|
54
|
+
multiplies and adds separately by construction.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
subscripts : str
|
|
59
|
+
Einsum subscript string.
|
|
60
|
+
shapes : list of tuple of int
|
|
61
|
+
Shapes of the input operands.
|
|
62
|
+
operand_symmetries : list of SymmetryGroup or None, optional
|
|
63
|
+
Exact symmetry group for each input operand.
|
|
64
|
+
identity_pattern : tuple of tuple of int, optional
|
|
65
|
+
Groups of operand positions that alias to the same underlying
|
|
66
|
+
array. Each inner tuple lists ≥2 positions sharing identity. When
|
|
67
|
+
provided, the cost path applies repeated-operand savings (e.g.,
|
|
68
|
+
the joint symmetry of ``A @ A``).
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
int
|
|
73
|
+
Estimated FLOP count.
|
|
74
|
+
"""
|
|
75
|
+
import numpy as _np
|
|
76
|
+
|
|
77
|
+
from flopscope._accumulation._cost import compute_accumulation_cost
|
|
78
|
+
from flopscope._opt_einsum import parse_einsum_input
|
|
79
|
+
|
|
80
|
+
dummy_operands = [_np.empty(s) for s in shapes]
|
|
81
|
+
input_subscripts, output_subscript, _ = parse_einsum_input(
|
|
82
|
+
(subscripts, *dummy_operands)
|
|
83
|
+
)
|
|
84
|
+
canonical_subscripts = f"{input_subscripts}->{output_subscript}"
|
|
85
|
+
input_parts = tuple(input_subscripts.split(","))
|
|
86
|
+
|
|
87
|
+
per_op_syms: tuple[SymmetryGroup | None, ...] = (
|
|
88
|
+
tuple(operand_symmetries)
|
|
89
|
+
if operand_symmetries is not None
|
|
90
|
+
else (None,) * len(shapes)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cost = compute_accumulation_cost(
|
|
94
|
+
canonical_subscripts=canonical_subscripts,
|
|
95
|
+
input_parts=input_parts,
|
|
96
|
+
output_subscript=output_subscript,
|
|
97
|
+
shapes=shapes,
|
|
98
|
+
per_op_symmetries=per_op_syms,
|
|
99
|
+
identity_pattern=identity_pattern,
|
|
100
|
+
)
|
|
101
|
+
return cost.total
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def analytical_pointwise_cost(
|
|
105
|
+
shape: tuple[int, ...], symmetry: SymmetryGroup | None = None
|
|
106
|
+
) -> int:
|
|
107
|
+
"""FLOP cost of a pointwise (element-wise) operation.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
shape : tuple of int
|
|
112
|
+
Shape of the array.
|
|
113
|
+
symmetry : SymmetryGroup or None, optional
|
|
114
|
+
If provided, only unique elements are counted.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
int
|
|
119
|
+
Estimated FLOP count (one per element, or one per unique element).
|
|
120
|
+
"""
|
|
121
|
+
if symmetry is not None:
|
|
122
|
+
return max(unique_elements_for_shape(symmetry, shape), 1)
|
|
123
|
+
result = 1
|
|
124
|
+
for dim in shape:
|
|
125
|
+
result *= dim
|
|
126
|
+
return max(result, 1)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def analytical_reduction_cost(
|
|
130
|
+
input_shape: tuple[int, ...],
|
|
131
|
+
axis: int | tuple[int, ...] | None = None,
|
|
132
|
+
symmetry: SymmetryGroup | None = None,
|
|
133
|
+
) -> int:
|
|
134
|
+
"""FLOP cost of a reduction operation (orbit-mapping model).
|
|
135
|
+
|
|
136
|
+
Delegates to compute_reduction_accumulation_cost from the
|
|
137
|
+
_accumulation subpackage. The legacy `unique_elements_for_shape`-based
|
|
138
|
+
formula is removed in favor of the path-independent orbit-mapping count
|
|
139
|
+
(with #56's off-by-one corrected).
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
input_shape : tuple of int
|
|
144
|
+
Shape of the input array.
|
|
145
|
+
axis : int, tuple of int, or None, optional
|
|
146
|
+
Axis or axes to reduce. None means full reduction.
|
|
147
|
+
symmetry : SymmetryGroup, optional
|
|
148
|
+
Pointwise symmetry of the input.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
int
|
|
153
|
+
FLOP count via the Tier-1 orbit-mapping model.
|
|
154
|
+
"""
|
|
155
|
+
from flopscope._accumulation._reduction import (
|
|
156
|
+
_normalize_axis,
|
|
157
|
+
compute_reduction_accumulation_cost,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
ndim = len(input_shape)
|
|
161
|
+
axes_summed = _normalize_axis(axis, ndim)
|
|
162
|
+
cost = compute_reduction_accumulation_cost(
|
|
163
|
+
input_shape=tuple(input_shape),
|
|
164
|
+
axes_summed=axes_summed,
|
|
165
|
+
symmetry=symmetry,
|
|
166
|
+
op_factor=1,
|
|
167
|
+
extra_ops=0,
|
|
168
|
+
)
|
|
169
|
+
return max(cost.total, 1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# Backward-compatible internal aliases. The public weighted API lives in
|
|
173
|
+
# ``flopscope.flops`` and wraps these analytical formulas.
|
|
174
|
+
pointwise_cost = analytical_pointwise_cost
|
|
175
|
+
reduction_cost = analytical_reduction_cost
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def svd_cost(m: int, n: int, k: int | None = None) -> int:
|
|
179
|
+
"""FLOP cost of a (truncated) SVD.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
m : int
|
|
184
|
+
Number of rows.
|
|
185
|
+
n : int
|
|
186
|
+
Number of columns.
|
|
187
|
+
k : int or None, optional
|
|
188
|
+
Number of singular values/vectors to compute. Defaults to min(m, n).
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
int
|
|
193
|
+
Estimated FLOP count: m * n * k.
|
|
194
|
+
|
|
195
|
+
Notes
|
|
196
|
+
-----
|
|
197
|
+
Based on Golub-Reinsch bidiagonalization.
|
|
198
|
+
"""
|
|
199
|
+
if k is None:
|
|
200
|
+
k = min(m, n)
|
|
201
|
+
return m * n * k
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def matmul_cost(m: int, k: int, n: int) -> int:
|
|
205
|
+
"""FLOP cost of a single 2D matmul ``(m, k) @ (k, n) -> (m, n)``.
|
|
206
|
+
|
|
207
|
+
Formula: ``2 * m * k * n - m * n`` (FMA=1 convention with accumulator
|
|
208
|
+
off-by-one), matching what ``fnp.matmul`` charges per 2D call via the
|
|
209
|
+
einsum machinery (``_resolve_cost_and_output_symmetry`` on the
|
|
210
|
+
canonical subscripts ``ij,jk->ik``).
|
|
211
|
+
|
|
212
|
+
Used by ``pinv_cost``, ``lstsq_cost``, and ``matrix_power_cost``
|
|
213
|
+
so those compound formulas track ``fnp.matmul`` automatically if the
|
|
214
|
+
matmul accounting convention ever changes again.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
m, k, n : int
|
|
219
|
+
Matmul dimensions: ``(m, k) @ (k, n)``.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
int
|
|
224
|
+
Estimated FLOP count, clamped to at least 1.
|
|
225
|
+
"""
|
|
226
|
+
return max(2 * m * k * n - m * n, 1)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _ceil_log2(n: int) -> int:
|
|
230
|
+
"""Return ceil(log2(n)), minimum 1.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
n : int
|
|
235
|
+
Input value.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
int
|
|
240
|
+
ceil(log2(n)), with a floor of 1.
|
|
241
|
+
"""
|
|
242
|
+
if n <= 1:
|
|
243
|
+
return 1
|
|
244
|
+
return max(math.ceil(math.log2(n)), 1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def sort_cost(n: int) -> int:
|
|
248
|
+
"""FLOP cost of comparison-based sort.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
n : int
|
|
253
|
+
Number of elements to sort.
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
int
|
|
258
|
+
Estimated FLOP count: n * ceil(log2(n)).
|
|
259
|
+
"""
|
|
260
|
+
if n <= 0:
|
|
261
|
+
return 1
|
|
262
|
+
return max(n * _ceil_log2(n), 1)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def search_cost(queries: int, sorted_size: int) -> int:
|
|
266
|
+
"""FLOP cost of binary search.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
queries : int
|
|
271
|
+
Number of search queries.
|
|
272
|
+
sorted_size : int
|
|
273
|
+
Size of the sorted array being searched.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
int
|
|
278
|
+
Estimated FLOP count: queries * ceil(log2(sorted_size)).
|
|
279
|
+
"""
|
|
280
|
+
if queries <= 0:
|
|
281
|
+
return 1
|
|
282
|
+
return max(queries * _ceil_log2(sorted_size), 1)
|