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/errors.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Exception and warning classes for flopscope."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys as _sys
|
|
7
|
+
import warnings as _warnings
|
|
8
|
+
|
|
9
|
+
_DEFAULT_DOCS_ROOT = "https://aicrowd.github.io/flopscope/docs"
|
|
10
|
+
_BUDGET_DOCS_PATH = "/guides/budget-planning"
|
|
11
|
+
_COMPETITION_DOCS_PATH = "/getting-started/competition"
|
|
12
|
+
_SYMMETRY_DOCS_PATH = "/guides/symmetry"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _docs_url(path: str) -> str:
|
|
16
|
+
root = os.environ.get("FLOPSCOPE_DOCS_ROOT", "").strip() or _DEFAULT_DOCS_ROOT
|
|
17
|
+
return f"{root.rstrip('/')}{path}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FlopscopeError(Exception):
|
|
21
|
+
"""Base exception for all flopscope errors."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BudgetExhaustedError(FlopscopeError):
|
|
25
|
+
"""Raised when an operation would exceed the FLOP budget."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, op_name: str, *, flop_cost: int, flops_remaining: int):
|
|
28
|
+
self.op_name = op_name
|
|
29
|
+
self.flop_cost = flop_cost
|
|
30
|
+
self.flops_remaining = flops_remaining
|
|
31
|
+
super().__init__(
|
|
32
|
+
f"{op_name} would cost {flop_cost:,} FLOPs but only "
|
|
33
|
+
f"{flops_remaining:,} remain. "
|
|
34
|
+
f"See: {_docs_url(_BUDGET_DOCS_PATH)}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TimeExhaustedError(FlopscopeError):
|
|
39
|
+
"""Raised when an operation exceeds the wall-clock time limit."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, op_name: str, *, elapsed_s: float, limit_s: float):
|
|
42
|
+
self.op_name = op_name
|
|
43
|
+
self.elapsed_s = elapsed_s
|
|
44
|
+
self.limit_s = limit_s
|
|
45
|
+
super().__init__(
|
|
46
|
+
f"{op_name}: wall-clock time {elapsed_s:.3f}s exceeds "
|
|
47
|
+
f"limit {limit_s:.3f}s. "
|
|
48
|
+
f"See: {_docs_url(_BUDGET_DOCS_PATH)}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NoBudgetContextError(FlopscopeError):
|
|
53
|
+
"""Raised when a counted operation is called outside a BudgetContext."""
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
super().__init__(
|
|
57
|
+
"No active BudgetContext. "
|
|
58
|
+
"Wrap your code in `with flopscope.BudgetContext(...):` "
|
|
59
|
+
f"See: {_docs_url(_COMPETITION_DOCS_PATH)}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SymmetryError(FlopscopeError):
|
|
64
|
+
"""Raised when a claimed tensor symmetry does not hold."""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
axes: tuple[int, ...],
|
|
69
|
+
max_deviation: float,
|
|
70
|
+
atol: float = 1e-6,
|
|
71
|
+
rtol: float = 1e-5,
|
|
72
|
+
):
|
|
73
|
+
self.axes = axes
|
|
74
|
+
self.max_deviation = max_deviation
|
|
75
|
+
self.atol = atol
|
|
76
|
+
self.rtol = rtol
|
|
77
|
+
super().__init__(
|
|
78
|
+
f"Tensor not symmetric along axes ({', '.join(str(d) for d in axes)}): "
|
|
79
|
+
f"max deviation = {max_deviation} "
|
|
80
|
+
f"(tolerance: atol={atol}, rtol={rtol}). "
|
|
81
|
+
f"See: {_docs_url(_SYMMETRY_DOCS_PATH)}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class UnsupportedFunctionError(FlopscopeError):
|
|
86
|
+
"""Raised when calling a function not available in the installed NumPy.
|
|
87
|
+
|
|
88
|
+
Use ``min_version`` for functions that require a newer numpy than installed
|
|
89
|
+
(e.g. ``bitwise_count`` requires numpy >= 2.1). Use ``max_version`` for
|
|
90
|
+
functions that have been removed in a newer numpy than installed
|
|
91
|
+
(e.g. ``in1d`` was removed in numpy 2.4). ``replacement`` optionally names
|
|
92
|
+
the function users should call instead.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
func_name: str,
|
|
98
|
+
*,
|
|
99
|
+
min_version: str | None = None,
|
|
100
|
+
max_version: str | None = None,
|
|
101
|
+
replacement: str | None = None,
|
|
102
|
+
):
|
|
103
|
+
import numpy as _np
|
|
104
|
+
|
|
105
|
+
self.func_name = func_name
|
|
106
|
+
self.min_version = min_version
|
|
107
|
+
self.max_version = max_version
|
|
108
|
+
self.replacement = replacement
|
|
109
|
+
|
|
110
|
+
if max_version is not None:
|
|
111
|
+
if replacement is not None:
|
|
112
|
+
msg = (
|
|
113
|
+
f"numpy.{func_name} was removed in numpy {max_version}; "
|
|
114
|
+
f"use `{replacement}` instead "
|
|
115
|
+
f"(you have numpy {_np.__version__})."
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
msg = (
|
|
119
|
+
f"numpy.{func_name} was removed in numpy {max_version} "
|
|
120
|
+
f"(you have numpy {_np.__version__})."
|
|
121
|
+
)
|
|
122
|
+
elif min_version is not None:
|
|
123
|
+
msg = (
|
|
124
|
+
f"numpy.{func_name} requires numpy >= {min_version} "
|
|
125
|
+
f"(you have numpy {_np.__version__}). "
|
|
126
|
+
f"To use it: uv pip install 'numpy>={min_version}'"
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
msg = f"numpy.{func_name} is not supported by flopscope."
|
|
130
|
+
|
|
131
|
+
super().__init__(msg)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class FlopscopeWarning(UserWarning):
|
|
135
|
+
"""Warning issued when flopscope detects potential numerical issues."""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SymmetryLossWarning(FlopscopeWarning):
|
|
139
|
+
"""Warning issued when an operation causes loss of symmetry metadata."""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CostFallbackWarning(FlopscopeWarning):
|
|
143
|
+
"""Warning issued when flopscope skips its symmetry-aware cost adjustment.
|
|
144
|
+
|
|
145
|
+
The output's symmetry group is too large for the placeholder cost
|
|
146
|
+
model's Burnside enumeration — its order ``|G|`` exceeds the configured
|
|
147
|
+
``dimino_budget``. The op runs correctly with the dense cost charged
|
|
148
|
+
instead — only the FLOP accounting is conservative; the data is
|
|
149
|
+
unaffected. Common trigger: ``np.ones((1,)*n)`` for large ``n`` or
|
|
150
|
+
other auto-inferred ``S_n`` symmetries on degenerate shapes.
|
|
151
|
+
|
|
152
|
+
Tune the threshold with ``flops.configure(dimino_budget=...)``. Suppress
|
|
153
|
+
the warning with ``flops.configure(symmetry_warnings=False)`` (shares
|
|
154
|
+
the flag with :class:`SymmetryLossWarning` since both are
|
|
155
|
+
symmetry-related diagnostics).
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Used by _user_stacklevel() to skip frames inside the flopscope package.
|
|
160
|
+
_FLOPSCOPE_PKG_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _user_stacklevel() -> int:
|
|
164
|
+
"""Stacklevel for :func:`warnings.warn` that points to the first frame
|
|
165
|
+
outside the ``flopscope`` package — i.e. the user's call site.
|
|
166
|
+
|
|
167
|
+
Walks the active call stack starting from the caller of
|
|
168
|
+
:func:`_warn_symmetry_loss`. Robust to changes in the number of
|
|
169
|
+
decorator/wrapper layers between user code and the warn site.
|
|
170
|
+
"""
|
|
171
|
+
frame = _sys._getframe(2)
|
|
172
|
+
level = 2
|
|
173
|
+
while frame is not None:
|
|
174
|
+
if not frame.f_code.co_filename.startswith(_FLOPSCOPE_PKG_DIR):
|
|
175
|
+
return level
|
|
176
|
+
frame = frame.f_back
|
|
177
|
+
level += 1
|
|
178
|
+
return 3
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _warn_symmetry_loss(
|
|
182
|
+
lost_dims: list[tuple[int, ...]],
|
|
183
|
+
reason: str,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Emit a :class:`SymmetryLossWarning` if warnings are enabled."""
|
|
186
|
+
from flopscope._config import get_setting
|
|
187
|
+
|
|
188
|
+
if not get_setting("symmetry_warnings"):
|
|
189
|
+
return
|
|
190
|
+
dim_str = ", ".join(str(g) for g in lost_dims)
|
|
191
|
+
_warnings.warn(
|
|
192
|
+
f"Symmetry lost along dims {dim_str}: {reason}. "
|
|
193
|
+
"Use as_symmetric() to re-tag if you know the result is symmetric. "
|
|
194
|
+
"Suppress with flops.configure(symmetry_warnings=False).",
|
|
195
|
+
SymmetryLossWarning,
|
|
196
|
+
stacklevel=_user_stacklevel(),
|
|
197
|
+
)
|