numba-vulkan 0.1.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.
- numba_vulkan/__init__.py +79 -0
- numba_vulkan/_emit.py +98 -0
- numba_vulkan/arrayfuncs.py +467 -0
- numba_vulkan/autotune.py +173 -0
- numba_vulkan/buffers.py +819 -0
- numba_vulkan/codegen.py +2224 -0
- numba_vulkan/compiler.py +635 -0
- numba_vulkan/data/LICENSE-libclc.txt +299 -0
- numba_vulkan/data/clspv--.bc +0 -0
- numba_vulkan/data/libclc-version.txt +1 -0
- numba_vulkan/dispatcher.py +1178 -0
- numba_vulkan/errors.py +56 -0
- numba_vulkan/kernelcache.py +315 -0
- numba_vulkan/legalize.py +754 -0
- numba_vulkan/libclc.py +281 -0
- numba_vulkan/mathfuncs.py +697 -0
- numba_vulkan/mathimpl.py +869 -0
- numba_vulkan/models.py +112 -0
- numba_vulkan/narrowing.py +429 -0
- numba_vulkan/probes.py +230 -0
- numba_vulkan/runtime.py +3644 -0
- numba_vulkan/structurize.py +912 -0
- numba_vulkan/stubs.py +451 -0
- numba_vulkan/target.py +501 -0
- numba_vulkan/ufuncs.py +143 -0
- numba_vulkan/vectorizers.py +889 -0
- numba_vulkan/vkdecl.py +693 -0
- numba_vulkan/vkimpl.py +1915 -0
- numba_vulkan/vktypes.py +251 -0
- numba_vulkan-0.1.0.dist-info/METADATA +553 -0
- numba_vulkan-0.1.0.dist-info/RECORD +34 -0
- numba_vulkan-0.1.0.dist-info/WHEEL +4 -0
- numba_vulkan-0.1.0.dist-info/licenses/LICENSE +24 -0
- numba_vulkan-0.1.0.dist-info/licenses/src/numba_vulkan/data/LICENSE-libclc.txt +299 -0
numba_vulkan/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Proof of concept: a Vulkan compute target for Numba."""
|
|
2
|
+
|
|
3
|
+
from numba_vulkan.autotune import autotune
|
|
4
|
+
from numba_vulkan.codegen import CompiledKernel
|
|
5
|
+
from numba_vulkan.dispatcher import VulkanDispatcher, jit
|
|
6
|
+
from numba_vulkan.errors import (
|
|
7
|
+
SpirvCodegenError,
|
|
8
|
+
VulkanPerformanceWarning,
|
|
9
|
+
VulkanPrecisionWarning,
|
|
10
|
+
VulkanSupportError,
|
|
11
|
+
VulkanUnsupportedError,
|
|
12
|
+
VulkanValidationWarning,
|
|
13
|
+
)
|
|
14
|
+
from numba_vulkan.runtime import (
|
|
15
|
+
DeviceArray,
|
|
16
|
+
device_array,
|
|
17
|
+
device_array_like,
|
|
18
|
+
event,
|
|
19
|
+
event_elapsed_time,
|
|
20
|
+
get_device,
|
|
21
|
+
list_devices,
|
|
22
|
+
pinned_array,
|
|
23
|
+
select_device,
|
|
24
|
+
stream,
|
|
25
|
+
synchronize,
|
|
26
|
+
to_device,
|
|
27
|
+
)
|
|
28
|
+
from numba_vulkan.stubs import (
|
|
29
|
+
atomic,
|
|
30
|
+
barrier,
|
|
31
|
+
global_id,
|
|
32
|
+
group_id,
|
|
33
|
+
local,
|
|
34
|
+
local_id,
|
|
35
|
+
local_size,
|
|
36
|
+
num_groups,
|
|
37
|
+
shared,
|
|
38
|
+
subgroup,
|
|
39
|
+
syncthreads,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"CompiledKernel",
|
|
44
|
+
"DeviceArray",
|
|
45
|
+
"SpirvCodegenError",
|
|
46
|
+
"VulkanDispatcher",
|
|
47
|
+
"VulkanPerformanceWarning",
|
|
48
|
+
"VulkanPrecisionWarning",
|
|
49
|
+
"VulkanSupportError",
|
|
50
|
+
"VulkanUnsupportedError",
|
|
51
|
+
"VulkanValidationWarning",
|
|
52
|
+
"atomic",
|
|
53
|
+
"autotune",
|
|
54
|
+
"barrier",
|
|
55
|
+
"device_array",
|
|
56
|
+
"device_array_like",
|
|
57
|
+
"event",
|
|
58
|
+
"event_elapsed_time",
|
|
59
|
+
"get_device",
|
|
60
|
+
"global_id",
|
|
61
|
+
"group_id",
|
|
62
|
+
"jit",
|
|
63
|
+
"list_devices",
|
|
64
|
+
"local",
|
|
65
|
+
"local_id",
|
|
66
|
+
"local_size",
|
|
67
|
+
"num_groups",
|
|
68
|
+
"pinned_array",
|
|
69
|
+
"select_device",
|
|
70
|
+
"shared",
|
|
71
|
+
"stream",
|
|
72
|
+
"subgroup",
|
|
73
|
+
"synchronize",
|
|
74
|
+
"syncthreads",
|
|
75
|
+
"to_device",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Registers target="vulkan" with numba.vectorize and numba.guvectorize.
|
|
79
|
+
from numba_vulkan import vectorizers # noqa: F401
|
numba_vulkan/_emit.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Child process that turns LLVM IR into SPIR-V.
|
|
2
|
+
|
|
3
|
+
LLVM's SPIR-V backend aborts the whole process on input it cannot handle,
|
|
4
|
+
and it keeps state between modules that corrupts the second module
|
|
5
|
+
translated by one process. It therefore runs outside the user's
|
|
6
|
+
interpreter, and in a process of its own for every module.
|
|
7
|
+
|
|
8
|
+
Starting Python and importing llvmlite takes much longer than the
|
|
9
|
+
translation, so this process does that once and then forks for every
|
|
10
|
+
request, where the platform allows it. It serves requests until its
|
|
11
|
+
standard input is closed. A request is the length of the IR as an unsigned
|
|
12
|
+
64-bit little-endian integer followed by the IR; the reply is framed in the
|
|
13
|
+
same way, with the length `FAILED` and no data if the backend failed. Its
|
|
14
|
+
message is then on standard error. See `numba_vulkan.codegen.Emitter`.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import signal
|
|
19
|
+
import struct
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import llvmlite.binding as llvm
|
|
23
|
+
|
|
24
|
+
HEADER = struct.Struct("<Q")
|
|
25
|
+
FAILED = 2**64 - 1
|
|
26
|
+
CAN_FORK = hasattr(os, "fork")
|
|
27
|
+
# Lets the backend use float atomics; they are emitted only for devices that
|
|
28
|
+
# support them (see numba_vulkan.narrowing.Mode).
|
|
29
|
+
EXTENSIONS = "+SPV_EXT_shader_atomic_float_add"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read(stream, count):
|
|
33
|
+
"""Read exactly `count` bytes, or return ``None`` at end of input."""
|
|
34
|
+
data = b""
|
|
35
|
+
while len(data) < count:
|
|
36
|
+
chunk = stream.read(count - len(data))
|
|
37
|
+
if not chunk:
|
|
38
|
+
return None
|
|
39
|
+
data += chunk
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _translate(target, text, stdout):
|
|
44
|
+
"""Translate one module and write the reply."""
|
|
45
|
+
module = llvm.parse_assembly(text.decode())
|
|
46
|
+
# codemodel="default": with llvmlite's "jitdefault", Windows gets "-elf"
|
|
47
|
+
# appended to the triple, and ELF output makes the backend abort.
|
|
48
|
+
machine = target.create_target_machine(opt=0, codemodel="default")
|
|
49
|
+
spirv = machine.emit_object(module)
|
|
50
|
+
stdout.write(HEADER.pack(len(spirv)) + spirv)
|
|
51
|
+
stdout.flush()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
"""Serve translation requests on standard input and output.
|
|
56
|
+
|
|
57
|
+
The target triple is taken from the first command line argument.
|
|
58
|
+
Without ``fork``, the process translates a single module itself and
|
|
59
|
+
exits.
|
|
60
|
+
"""
|
|
61
|
+
triple = sys.argv[1]
|
|
62
|
+
llvm.set_option("", f"--spirv-ext={EXTENSIONS}")
|
|
63
|
+
llvm.initialize_all_targets()
|
|
64
|
+
llvm.initialize_all_asmprinters()
|
|
65
|
+
target = llvm.Target.from_triple(triple)
|
|
66
|
+
stdin, stdout = sys.stdin.buffer, sys.stdout.buffer
|
|
67
|
+
while True:
|
|
68
|
+
header = _read(stdin, HEADER.size)
|
|
69
|
+
if header is None:
|
|
70
|
+
return
|
|
71
|
+
text = _read(stdin, HEADER.unpack(header)[0])
|
|
72
|
+
if text is None:
|
|
73
|
+
return
|
|
74
|
+
if not CAN_FORK:
|
|
75
|
+
_translate(target, text, stdout)
|
|
76
|
+
return
|
|
77
|
+
pid = os.fork()
|
|
78
|
+
if pid == 0:
|
|
79
|
+
status = 1
|
|
80
|
+
try:
|
|
81
|
+
_translate(target, text, stdout)
|
|
82
|
+
status = 0
|
|
83
|
+
except BaseException as exc: # noqa: BLE001 - reported to the parent
|
|
84
|
+
print(f"{type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
|
|
85
|
+
finally:
|
|
86
|
+
os._exit(status)
|
|
87
|
+
status = os.waitpid(pid, 0)[1]
|
|
88
|
+
if os.WIFSIGNALED(status):
|
|
89
|
+
# A crash leaves no message of its own.
|
|
90
|
+
name = signal.Signals(os.WTERMSIG(status)).name
|
|
91
|
+
print(f"the backend was killed by {name}", file=sys.stderr, flush=True)
|
|
92
|
+
if status != 0:
|
|
93
|
+
stdout.write(HEADER.pack(FAILED))
|
|
94
|
+
stdout.flush()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""Reductions over arrays, written in Python on top of element access.
|
|
2
|
+
|
|
3
|
+
Numba's own ``sum``, ``min``... walk the array through its data pointer,
|
|
4
|
+
which arrays on this target do not have. The versions here are overloads
|
|
5
|
+
for the ``vulkan`` target that read the elements by position instead (see
|
|
6
|
+
`numba_vulkan.vkimpl.flat_item`), so they work for arrays and views of any
|
|
7
|
+
dimensionality. They reduce over all elements, or, with a constant
|
|
8
|
+
``axis``, along one axis: kernels cannot allocate the resulting array, so
|
|
9
|
+
``a.sum(axis=0)`` is an array expression (see `AxisReduction`), whose
|
|
10
|
+
elements are computed where they are read.
|
|
11
|
+
|
|
12
|
+
Each is available as a method (``a.sum()``) and as the NumPy function
|
|
13
|
+
(``np.sum(a)``).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import functools
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from numba.core import cgutils, errors, types
|
|
21
|
+
from numba.core.extending import intrinsic, overload, overload_method
|
|
22
|
+
|
|
23
|
+
from numba_vulkan.vkimpl import flat_item
|
|
24
|
+
from numba_vulkan.vktypes import VulkanArray, VulkanExpr
|
|
25
|
+
|
|
26
|
+
TARGET = "vulkan"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _accumulator(dtype):
|
|
30
|
+
"""Type in which sums and products of ``dtype`` elements are formed.
|
|
31
|
+
|
|
32
|
+
As in NumPy, integers and booleans widen to the platform integer.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
dtype : numba.types.Type
|
|
37
|
+
Element type.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
numba.types.Type
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(dtype, types.Boolean):
|
|
44
|
+
return types.intp
|
|
45
|
+
if isinstance(dtype, types.Integer):
|
|
46
|
+
return types.intp if dtype.signed else types.uintp
|
|
47
|
+
return dtype
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _check_whole_axis(a, axis):
|
|
51
|
+
"""Raise unless `axis` is None, or a valid axis of a 1-d array."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@overload(_check_whole_axis, target=TARGET)
|
|
55
|
+
def _check_whole_axis_impl(a, axis):
|
|
56
|
+
"""Build `_check_whole_axis`: reductions over all elements take it."""
|
|
57
|
+
if axis is None or isinstance(axis, (types.NoneType, types.Omitted)):
|
|
58
|
+
return lambda a, axis: None
|
|
59
|
+
|
|
60
|
+
def impl(a, axis):
|
|
61
|
+
if axis != 0 and axis != -1:
|
|
62
|
+
raise ValueError("axis is out of bounds for an array of dimension 1")
|
|
63
|
+
|
|
64
|
+
return impl
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _sum(a):
|
|
68
|
+
"""Build ``sum``: the sum of all elements."""
|
|
69
|
+
acc = _accumulator(a.dtype)
|
|
70
|
+
|
|
71
|
+
def impl(a, axis=None):
|
|
72
|
+
_check_whole_axis(a, axis)
|
|
73
|
+
total = acc(0)
|
|
74
|
+
for k in range(a.size):
|
|
75
|
+
total += flat_item(a, k)
|
|
76
|
+
return total
|
|
77
|
+
|
|
78
|
+
return impl
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _prod(a):
|
|
82
|
+
"""Build ``prod``: the product of all elements."""
|
|
83
|
+
acc = _accumulator(a.dtype)
|
|
84
|
+
|
|
85
|
+
def impl(a, axis=None):
|
|
86
|
+
_check_whole_axis(a, axis)
|
|
87
|
+
total = acc(1)
|
|
88
|
+
for k in range(a.size):
|
|
89
|
+
total *= flat_item(a, k)
|
|
90
|
+
return total
|
|
91
|
+
|
|
92
|
+
return impl
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _mean(a):
|
|
96
|
+
"""Build ``mean``: float64 for integers, the element type otherwise."""
|
|
97
|
+
acc = a.dtype if isinstance(a.dtype, types.Float) else types.float64
|
|
98
|
+
|
|
99
|
+
def impl(a, axis=None):
|
|
100
|
+
_check_whole_axis(a, axis)
|
|
101
|
+
total = acc(0)
|
|
102
|
+
for k in range(a.size):
|
|
103
|
+
total += flat_item(a, k)
|
|
104
|
+
return total / acc(a.size)
|
|
105
|
+
|
|
106
|
+
return impl
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _min(a):
|
|
110
|
+
"""Build ``min``; raises ``ValueError`` for an empty array."""
|
|
111
|
+
|
|
112
|
+
def impl(a, axis=None):
|
|
113
|
+
_check_whole_axis(a, axis)
|
|
114
|
+
if a.size == 0:
|
|
115
|
+
raise ValueError("zero-size array has no minimum")
|
|
116
|
+
best = flat_item(a, 0)
|
|
117
|
+
for k in range(1, a.size):
|
|
118
|
+
value = flat_item(a, k)
|
|
119
|
+
# written so that a NaN, once found, is kept, as in NumPy
|
|
120
|
+
if value < best or value != value: # noqa: PLR0124 - NaN, for any type
|
|
121
|
+
best = value
|
|
122
|
+
return best
|
|
123
|
+
|
|
124
|
+
return impl
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _max(a):
|
|
128
|
+
"""Build ``max``; raises ``ValueError`` for an empty array."""
|
|
129
|
+
|
|
130
|
+
def impl(a, axis=None):
|
|
131
|
+
_check_whole_axis(a, axis)
|
|
132
|
+
if a.size == 0:
|
|
133
|
+
raise ValueError("zero-size array has no maximum")
|
|
134
|
+
best = flat_item(a, 0)
|
|
135
|
+
for k in range(1, a.size):
|
|
136
|
+
value = flat_item(a, k)
|
|
137
|
+
if value > best or value != value: # noqa: PLR0124 - NaN, for any type
|
|
138
|
+
best = value
|
|
139
|
+
return best
|
|
140
|
+
|
|
141
|
+
return impl
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _argmin(a):
|
|
145
|
+
"""Build ``argmin``: row-major position of the first minimum."""
|
|
146
|
+
|
|
147
|
+
def impl(a, axis=None):
|
|
148
|
+
_check_whole_axis(a, axis)
|
|
149
|
+
if a.size == 0:
|
|
150
|
+
raise ValueError("attempt to get argmin of an empty sequence")
|
|
151
|
+
best, where = flat_item(a, 0), 0
|
|
152
|
+
for k in range(1, a.size):
|
|
153
|
+
value = flat_item(a, k)
|
|
154
|
+
if value < best:
|
|
155
|
+
best, where = value, k
|
|
156
|
+
return where
|
|
157
|
+
|
|
158
|
+
return impl
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _argmax(a):
|
|
162
|
+
"""Build ``argmax``: row-major position of the first maximum."""
|
|
163
|
+
|
|
164
|
+
def impl(a, axis=None):
|
|
165
|
+
_check_whole_axis(a, axis)
|
|
166
|
+
if a.size == 0:
|
|
167
|
+
raise ValueError("attempt to get argmax of an empty sequence")
|
|
168
|
+
best, where = flat_item(a, 0), 0
|
|
169
|
+
for k in range(1, a.size):
|
|
170
|
+
value = flat_item(a, k)
|
|
171
|
+
if value > best:
|
|
172
|
+
best, where = value, k
|
|
173
|
+
return where
|
|
174
|
+
|
|
175
|
+
return impl
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _any(a):
|
|
179
|
+
"""Build ``any``: whether some element is non-zero."""
|
|
180
|
+
|
|
181
|
+
def impl(a, axis=None):
|
|
182
|
+
_check_whole_axis(a, axis)
|
|
183
|
+
for k in range(a.size):
|
|
184
|
+
if flat_item(a, k):
|
|
185
|
+
return True
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
return impl
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _all(a):
|
|
192
|
+
"""Build ``all``: whether every element is non-zero."""
|
|
193
|
+
|
|
194
|
+
def impl(a, axis=None):
|
|
195
|
+
_check_whole_axis(a, axis)
|
|
196
|
+
for k in range(a.size):
|
|
197
|
+
if not flat_item(a, k):
|
|
198
|
+
return False
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
return impl
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass(frozen=True)
|
|
205
|
+
class AxisReduction:
|
|
206
|
+
"""The operation of an array expression that reduces along an axis.
|
|
207
|
+
|
|
208
|
+
``a.sum(axis=k)`` is a `numba_vulkan.vktypes.VulkanExpr` with this as
|
|
209
|
+
its operation, and ``a`` and the axis as its operands; the axis is a
|
|
210
|
+
run-time value, as in Numba's ``sum(axis=...)`` on the CPU, since the
|
|
211
|
+
number of dimensions of the result does not depend on it. Its shape is
|
|
212
|
+
that of ``a`` without the axis; each element is a loop along the axis,
|
|
213
|
+
written in Python and compiled where the element is read (see
|
|
214
|
+
`numba_vulkan.vkimpl.expression_element`).
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
name : str
|
|
219
|
+
The reduction: a key of ``_REDUCTIONS``.
|
|
220
|
+
acc : numba.types.Type
|
|
221
|
+
Type in which sums, products and means are formed.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
name: str
|
|
225
|
+
acc: types.Type
|
|
226
|
+
# Tells numba_vulkan.vkimpl how to compute the shape and the elements.
|
|
227
|
+
reduces_axis = True
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def __name__(self):
|
|
231
|
+
"""Name used in the expression's type name."""
|
|
232
|
+
return f"{self.name}(axis)"
|
|
233
|
+
|
|
234
|
+
def element_function(self, ndim):
|
|
235
|
+
"""The function that computes one element.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
ndim : int
|
|
240
|
+
Dimensions of the operand.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
function
|
|
245
|
+
Takes the operand, the axis (made non-negative, and checked
|
|
246
|
+
when the shape was computed) and one index per dimension of
|
|
247
|
+
the result.
|
|
248
|
+
"""
|
|
249
|
+
return _element_function(self, ndim)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# Loops that compute one element of a reduction along an axis; ``{item}``
|
|
253
|
+
# reads the element at position ``k`` along it, ``ACC`` is the accumulator
|
|
254
|
+
# type.
|
|
255
|
+
_ELEMENT_BODIES = {
|
|
256
|
+
"sum": """
|
|
257
|
+
total = ACC(0)
|
|
258
|
+
for k in range(n):
|
|
259
|
+
total += {item}
|
|
260
|
+
return total""",
|
|
261
|
+
"prod": """
|
|
262
|
+
total = ACC(1)
|
|
263
|
+
for k in range(n):
|
|
264
|
+
total *= {item}
|
|
265
|
+
return total""",
|
|
266
|
+
"mean": """
|
|
267
|
+
total = ACC(0)
|
|
268
|
+
for k in range(n):
|
|
269
|
+
total += {item}
|
|
270
|
+
return total / ACC(n)""",
|
|
271
|
+
"min": """
|
|
272
|
+
if n == 0:
|
|
273
|
+
raise ValueError("zero-size array has no minimum")
|
|
274
|
+
k = 0
|
|
275
|
+
best = {item}
|
|
276
|
+
for k in range(1, n):
|
|
277
|
+
value = {item}
|
|
278
|
+
if value < best or value != value: # noqa: PLR0124 - NaN
|
|
279
|
+
best = value
|
|
280
|
+
return best""",
|
|
281
|
+
"max": """
|
|
282
|
+
if n == 0:
|
|
283
|
+
raise ValueError("zero-size array has no maximum")
|
|
284
|
+
k = 0
|
|
285
|
+
best = {item}
|
|
286
|
+
for k in range(1, n):
|
|
287
|
+
value = {item}
|
|
288
|
+
if value > best or value != value: # noqa: PLR0124 - NaN
|
|
289
|
+
best = value
|
|
290
|
+
return best""",
|
|
291
|
+
"argmin": """
|
|
292
|
+
if n == 0:
|
|
293
|
+
raise ValueError("attempt to get argmin of an empty sequence")
|
|
294
|
+
k = 0
|
|
295
|
+
best, where = {item}, 0
|
|
296
|
+
for k in range(1, n):
|
|
297
|
+
value = {item}
|
|
298
|
+
if value < best:
|
|
299
|
+
best, where = value, k
|
|
300
|
+
return where""",
|
|
301
|
+
"argmax": """
|
|
302
|
+
if n == 0:
|
|
303
|
+
raise ValueError("attempt to get argmax of an empty sequence")
|
|
304
|
+
k = 0
|
|
305
|
+
best, where = {item}, 0
|
|
306
|
+
for k in range(1, n):
|
|
307
|
+
value = {item}
|
|
308
|
+
if value > best:
|
|
309
|
+
best, where = value, k
|
|
310
|
+
return where""",
|
|
311
|
+
"any": """
|
|
312
|
+
for k in range(n):
|
|
313
|
+
if {item}:
|
|
314
|
+
return True
|
|
315
|
+
return False""",
|
|
316
|
+
"all": """
|
|
317
|
+
for k in range(n):
|
|
318
|
+
if not {item}:
|
|
319
|
+
return False
|
|
320
|
+
return True""",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@functools.cache
|
|
325
|
+
def _element_function(op, ndim):
|
|
326
|
+
"""Write and define the function of `AxisReduction.element_function`."""
|
|
327
|
+
outer = [f"i{d}" for d in range(ndim - 1)]
|
|
328
|
+
position = []
|
|
329
|
+
for d in range(ndim):
|
|
330
|
+
# Index along operand axis d: an index of the result before the
|
|
331
|
+
# reduced axis, k on it, and the next index of the result after it.
|
|
332
|
+
if d == 0:
|
|
333
|
+
position.append("(k if axis == 0 else i0)")
|
|
334
|
+
elif d == ndim - 1:
|
|
335
|
+
position.append(f"(k if axis == {d} else i{d - 1})")
|
|
336
|
+
else:
|
|
337
|
+
position.append(
|
|
338
|
+
f"(i{d} if axis > {d} else (k if axis == {d} else i{d - 1}))"
|
|
339
|
+
)
|
|
340
|
+
body = _ELEMENT_BODIES[op.name].format(item=f"a[{', '.join(position)}]")
|
|
341
|
+
source = f"def element(a, axis, {', '.join(outer)}):\n n = a.shape[axis]{body}\n"
|
|
342
|
+
scope = {"ACC": op.acc}
|
|
343
|
+
exec(source, scope) # noqa: S102 - generated from the table above
|
|
344
|
+
return scope["element"]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _result_type(name, dtype):
|
|
348
|
+
"""Element type of a reduction of `dtype` elements."""
|
|
349
|
+
if name in ("sum", "prod"):
|
|
350
|
+
return _accumulator(dtype)
|
|
351
|
+
if name == "mean":
|
|
352
|
+
return dtype if isinstance(dtype, types.Float) else types.float64
|
|
353
|
+
if name in ("argmin", "argmax"):
|
|
354
|
+
return types.intp
|
|
355
|
+
if name in ("any", "all"):
|
|
356
|
+
return types.boolean
|
|
357
|
+
return dtype
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@functools.cache
|
|
361
|
+
def _axis_reducer(name):
|
|
362
|
+
"""An intrinsic that makes the expression of a reduction along an axis."""
|
|
363
|
+
|
|
364
|
+
@intrinsic(target=TARGET)
|
|
365
|
+
def reduce_along(typingctx, a, axis):
|
|
366
|
+
acc = _result_type("mean" if name == "mean" else "sum", a.dtype)
|
|
367
|
+
op = AxisReduction(name, acc)
|
|
368
|
+
dtype = _result_type(name, a.dtype)
|
|
369
|
+
exprty = VulkanExpr(op, (a, types.intp), dtype, a.ndim - 1)
|
|
370
|
+
|
|
371
|
+
def codegen(context, builder, sig, args):
|
|
372
|
+
proxy = cgutils.create_struct_proxy(sig.return_type)(context, builder)
|
|
373
|
+
proxy.operand0 = args[0]
|
|
374
|
+
proxy.operand1 = context.cast(builder, args[1], sig.args[1], types.intp)
|
|
375
|
+
return proxy._getvalue()
|
|
376
|
+
|
|
377
|
+
return exprty(a, axis), codegen
|
|
378
|
+
|
|
379
|
+
return reduce_along
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
_REDUCTIONS = {
|
|
383
|
+
"sum": (_sum, [np.sum]),
|
|
384
|
+
"prod": (_prod, [np.prod]),
|
|
385
|
+
"mean": (_mean, [np.mean]),
|
|
386
|
+
"min": (_min, [np.min, min]),
|
|
387
|
+
"max": (_max, [np.max, max]),
|
|
388
|
+
"argmin": (_argmin, [np.argmin]),
|
|
389
|
+
"argmax": (_argmax, [np.argmax]),
|
|
390
|
+
"any": (_any, [np.any]),
|
|
391
|
+
"all": (_all, [np.all]),
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _register(name, factory, functions):
|
|
396
|
+
"""Register a reduction as an array method and for its functions.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
name : str
|
|
401
|
+
Name of the array method.
|
|
402
|
+
factory : callable
|
|
403
|
+
Builds the implementation for an array type.
|
|
404
|
+
functions : list of callable
|
|
405
|
+
Functions that mean the same when called with one array.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
def typer(a, axis=None):
|
|
409
|
+
"""Select the implementation for arrays and expressions of this target."""
|
|
410
|
+
if not isinstance(a, (VulkanArray, VulkanExpr)):
|
|
411
|
+
return None
|
|
412
|
+
if axis is None or isinstance(axis, (types.NoneType, types.Omitted)):
|
|
413
|
+
return factory(a)
|
|
414
|
+
if not isinstance(axis, types.Integer):
|
|
415
|
+
raise errors.TypingError(f"{name}(axis=...) takes an integer axis")
|
|
416
|
+
ndim = a.ndim
|
|
417
|
+
if ndim == 1:
|
|
418
|
+
return factory(a) # a scalar, as in NumPy; it checks the axis
|
|
419
|
+
reduce_along = _axis_reducer(name)
|
|
420
|
+
|
|
421
|
+
def impl(a, axis=None):
|
|
422
|
+
# Checked where the shape is computed: raising here would make
|
|
423
|
+
# the array of the expression depend on the branch taken.
|
|
424
|
+
return reduce_along(a, axis)
|
|
425
|
+
|
|
426
|
+
return impl
|
|
427
|
+
|
|
428
|
+
def builtin_typer(a):
|
|
429
|
+
"""``min(a)`` and ``max(a)`` on an array: over all elements."""
|
|
430
|
+
if isinstance(a, (VulkanArray, VulkanExpr)):
|
|
431
|
+
return factory(a) # its axis keeps its default
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
overload_method(VulkanArray, name, target=TARGET)(typer)
|
|
435
|
+
overload_method(VulkanExpr, name, target=TARGET)(typer)
|
|
436
|
+
for function in functions:
|
|
437
|
+
if function in (min, max):
|
|
438
|
+
overload(function, target=TARGET, strict=False)(builtin_typer)
|
|
439
|
+
else:
|
|
440
|
+
overload(function, target=TARGET)(typer)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
for _name, (_factory, _functions) in _REDUCTIONS.items():
|
|
444
|
+
_register(_name, _factory, _functions)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@overload(np.dot, target=TARGET)
|
|
448
|
+
def _dot(a, b):
|
|
449
|
+
"""Build ``np.dot`` for two 1-d arrays."""
|
|
450
|
+
if not (
|
|
451
|
+
isinstance(a, (VulkanArray, VulkanExpr))
|
|
452
|
+
and isinstance(b, (VulkanArray, VulkanExpr))
|
|
453
|
+
):
|
|
454
|
+
return None
|
|
455
|
+
if a.ndim != 1 or b.ndim != 1:
|
|
456
|
+
return None
|
|
457
|
+
acc = np.result_type(str(a.dtype), str(b.dtype)).type
|
|
458
|
+
|
|
459
|
+
def impl(a, b):
|
|
460
|
+
if a.shape[0] != b.shape[0]:
|
|
461
|
+
raise ValueError("incompatible array sizes for np.dot(a, b)")
|
|
462
|
+
total = acc(0)
|
|
463
|
+
for k in range(a.shape[0]):
|
|
464
|
+
total += flat_item(a, k) * flat_item(b, k)
|
|
465
|
+
return total
|
|
466
|
+
|
|
467
|
+
return impl
|