halide 19.0.0__cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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.
- halide/__init__.py +39 -0
- halide/_generator_helpers.py +835 -0
- halide/bin/adams2019_retrain_cost_model +0 -0
- halide/bin/adams2019_weightsdir_to_weightsfile +0 -0
- halide/bin/anderson2021_retrain_cost_model +0 -0
- halide/bin/anderson2021_weightsdir_to_weightsfile +0 -0
- halide/bin/featurization_to_sample +0 -0
- halide/bin/gengen +0 -0
- halide/bin/get_host_target +0 -0
- halide/halide_.cpython-39-x86_64-linux-gnu.so +0 -0
- halide/imageio.py +60 -0
- halide/include/Halide.h +35293 -0
- halide/include/HalideBuffer.h +2618 -0
- halide/include/HalidePyTorchCudaHelpers.h +64 -0
- halide/include/HalidePyTorchHelpers.h +120 -0
- halide/include/HalideRuntime.h +2221 -0
- halide/include/HalideRuntimeCuda.h +89 -0
- halide/include/HalideRuntimeD3D12Compute.h +91 -0
- halide/include/HalideRuntimeHexagonDma.h +104 -0
- halide/include/HalideRuntimeHexagonHost.h +157 -0
- halide/include/HalideRuntimeMetal.h +112 -0
- halide/include/HalideRuntimeOpenCL.h +119 -0
- halide/include/HalideRuntimeQurt.h +32 -0
- halide/include/HalideRuntimeVulkan.h +137 -0
- halide/include/HalideRuntimeWebGPU.h +44 -0
- halide/lib64/cmake/Halide/FindHalide_LLVM.cmake +152 -0
- halide/lib64/cmake/Halide/FindV8.cmake +33 -0
- halide/lib64/cmake/Halide/Halide-shared-deps.cmake +0 -0
- halide/lib64/cmake/Halide/Halide-shared-targets-release.cmake +29 -0
- halide/lib64/cmake/Halide/Halide-shared-targets.cmake +154 -0
- halide/lib64/cmake/Halide/HalideConfig.cmake +162 -0
- halide/lib64/cmake/Halide/HalideConfigVersion.cmake +65 -0
- halide/lib64/cmake/HalideHelpers/FindHalide_WebGPU.cmake +27 -0
- halide/lib64/cmake/HalideHelpers/Halide-Interfaces-release.cmake +116 -0
- halide/lib64/cmake/HalideHelpers/Halide-Interfaces.cmake +236 -0
- halide/lib64/cmake/HalideHelpers/HalideGeneratorHelpers.cmake +1056 -0
- halide/lib64/cmake/HalideHelpers/HalideHelpersConfig.cmake +28 -0
- halide/lib64/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
- halide/lib64/cmake/HalideHelpers/HalideTargetHelpers.cmake +99 -0
- halide/lib64/cmake/HalideHelpers/MutexCopy.ps1 +31 -0
- halide/lib64/cmake/HalideHelpers/TargetExportScript.cmake +55 -0
- halide/lib64/cmake/Halide_Python/Halide_Python-targets-release.cmake +30 -0
- halide/lib64/cmake/Halide_Python/Halide_Python-targets.cmake +125 -0
- halide/lib64/cmake/Halide_Python/Halide_PythonConfig.cmake +26 -0
- halide/lib64/cmake/Halide_Python/Halide_PythonConfigVersion.cmake +65 -0
- halide/lib64/libHalide.so +0 -0
- halide/lib64/libHalidePyStubs.a +0 -0
- halide/lib64/libHalide_GenGen.a +0 -0
- halide/lib64/libautoschedule_adams2019.so +0 -0
- halide/lib64/libautoschedule_anderson2021.so +0 -0
- halide/lib64/libautoschedule_li2018.so +0 -0
- halide/lib64/libautoschedule_mullapudi2016.so +0 -0
- halide/share/doc/Halide/LICENSE.txt +233 -0
- halide/share/doc/Halide/README.md +439 -0
- halide/share/doc/Halide/doc/BuildingHalideWithCMake.md +626 -0
- halide/share/doc/Halide/doc/CodeStyleCMake.md +393 -0
- halide/share/doc/Halide/doc/FuzzTesting.md +104 -0
- halide/share/doc/Halide/doc/HalideCMakePackage.md +812 -0
- halide/share/doc/Halide/doc/Hexagon.md +73 -0
- halide/share/doc/Halide/doc/Python.md +844 -0
- halide/share/doc/Halide/doc/RunGen.md +283 -0
- halide/share/doc/Halide/doc/Testing.md +125 -0
- halide/share/doc/Halide/doc/Vulkan.md +287 -0
- halide/share/doc/Halide/doc/WebAssembly.md +228 -0
- halide/share/doc/Halide/doc/WebGPU.md +128 -0
- halide/share/tools/RunGen.h +1470 -0
- halide/share/tools/RunGenMain.cpp +642 -0
- halide/share/tools/adams2019_autotune_loop.sh +227 -0
- halide/share/tools/anderson2021_autotune_loop.sh +591 -0
- halide/share/tools/halide_benchmark.h +240 -0
- halide/share/tools/halide_image.h +31 -0
- halide/share/tools/halide_image_info.h +318 -0
- halide/share/tools/halide_image_io.h +2794 -0
- halide/share/tools/halide_malloc_trace.h +102 -0
- halide/share/tools/halide_thread_pool.h +161 -0
- halide/share/tools/halide_trace_config.h +559 -0
- halide-19.0.0.data/data/share/cmake/Halide/HalideConfig.cmake +6 -0
- halide-19.0.0.data/data/share/cmake/Halide/HalideConfigVersion.cmake +65 -0
- halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfig.cmake +6 -0
- halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
- halide-19.0.0.dist-info/METADATA +301 -0
- halide-19.0.0.dist-info/RECORD +84 -0
- halide-19.0.0.dist-info/WHEEL +6 -0
- halide-19.0.0.dist-info/licenses/LICENSE.txt +233 -0
@@ -0,0 +1,835 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from contextvars import ContextVar
|
4
|
+
from enum import Enum
|
5
|
+
from functools import total_ordering
|
6
|
+
from .halide_ import *
|
7
|
+
from .halide_ import _unique_name, _UnspecifiedType
|
8
|
+
from inspect import isclass
|
9
|
+
from typing import Any, Optional
|
10
|
+
import builtins
|
11
|
+
import re
|
12
|
+
import sys
|
13
|
+
import warnings
|
14
|
+
|
15
|
+
def _fail(msg: str):
|
16
|
+
raise HalideError(msg)
|
17
|
+
|
18
|
+
|
19
|
+
def _check(cond: bool, msg: str):
|
20
|
+
if not cond:
|
21
|
+
_fail(msg)
|
22
|
+
|
23
|
+
# Basically, a valid C identifier, except:
|
24
|
+
#
|
25
|
+
# -- initial _ is forbidden (rather than merely "reserved")
|
26
|
+
# -- two underscores in a row is also forbidden
|
27
|
+
_NAME_RE = re.compile(r"^(?!.*__)[A-Za-z0-9][A-Za-z0-9_]*$")
|
28
|
+
|
29
|
+
def _is_valid_name(name: str) -> bool:
|
30
|
+
if not name:
|
31
|
+
return False
|
32
|
+
# We forbid this to avoid ambiguity in arguments to call()
|
33
|
+
if name == "generator_params":
|
34
|
+
return False
|
35
|
+
|
36
|
+
return _NAME_RE.search(name)
|
37
|
+
|
38
|
+
|
39
|
+
def _check_valid_name(name: str) -> str:
|
40
|
+
_check(
|
41
|
+
_is_valid_name(name),
|
42
|
+
"The name '%s' is not valid for a GeneratorParam, Input, or Output." % name,
|
43
|
+
)
|
44
|
+
return name
|
45
|
+
|
46
|
+
|
47
|
+
# Transmute 'None' into our internal "UnspecifiedType" as a placeholder;
|
48
|
+
# also add some syntactic sugar, to allow users to alias
|
49
|
+
# bool -> UInt(1), int -> Int(32), float -> Float(32)
|
50
|
+
def _sanitize_type(t: object) -> Type:
|
51
|
+
if t is None or t is type(None):
|
52
|
+
return _UnspecifiedType()
|
53
|
+
elif t is bool:
|
54
|
+
return UInt(1)
|
55
|
+
elif t is int:
|
56
|
+
return Int(32)
|
57
|
+
elif t is float:
|
58
|
+
return Float(32)
|
59
|
+
else:
|
60
|
+
_check(isinstance(t, Type), "Expected a Halide Type, but saw: %s" % t)
|
61
|
+
return t
|
62
|
+
|
63
|
+
def _normalize_type_list(types: object) -> list[Type]:
|
64
|
+
# Always treat _UnspecifiedType as a non-type
|
65
|
+
if types is None:
|
66
|
+
types = []
|
67
|
+
elif isinstance(types, Type) and types == _UnspecifiedType():
|
68
|
+
types = []
|
69
|
+
if type(types) is not list:
|
70
|
+
types = [types];
|
71
|
+
types = [_sanitize_type(t) for t in types]
|
72
|
+
return types
|
73
|
+
|
74
|
+
|
75
|
+
_type_string_map = {
|
76
|
+
# "bfloat16": BFloat(16),
|
77
|
+
"bool": Bool(),
|
78
|
+
"float16": Float(16),
|
79
|
+
"float32": Float(32),
|
80
|
+
"float64": Float(64),
|
81
|
+
"int16": Int(16),
|
82
|
+
"int32": Int(32),
|
83
|
+
"int8": Int(8),
|
84
|
+
"uint16": UInt(16),
|
85
|
+
"uint32": UInt(32),
|
86
|
+
"uint8": UInt(8),
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
def _parse_halide_type(s: str) -> Type:
|
91
|
+
_check(s in _type_string_map, "The value %s cannot be parsed as a Type." % s)
|
92
|
+
return _type_string_map[s]
|
93
|
+
|
94
|
+
|
95
|
+
def _parse_halide_type_list(s: str) -> list[Type]:
|
96
|
+
return [_parse_halide_type(t) for t in s.split(",")]
|
97
|
+
|
98
|
+
|
99
|
+
class Requirement:
|
100
|
+
# Name
|
101
|
+
_name: str = ""
|
102
|
+
|
103
|
+
# List of the required types, if any. An empty list means
|
104
|
+
# no constraints. The list will usually be a single type,
|
105
|
+
# except for Outputs that have Tuple-valued results, which
|
106
|
+
# can have multiple types.
|
107
|
+
_types: list[Type] = []
|
108
|
+
|
109
|
+
# Required dimensions. 0 = scalar. -1 = unconstrained.
|
110
|
+
_dimensions: int = -1
|
111
|
+
|
112
|
+
def __init__(self, name: str, types: object, dimensions: int):
|
113
|
+
self._name = _check_valid_name(name)
|
114
|
+
self._types = _normalize_type_list(types)
|
115
|
+
self._dimensions = dimensions
|
116
|
+
_check(
|
117
|
+
len(self._types) > 0,
|
118
|
+
"No type has been specified for %s. Try specifying one in code, or by setting '%s.type' as a GeneratorParam."
|
119
|
+
% (self._name, self._name),
|
120
|
+
)
|
121
|
+
_check(
|
122
|
+
self._dimensions >= 0,
|
123
|
+
"No dimensionality has been specified for %s. Try specifying one in code, or by setting '%s.dim' as a GeneratorParam."
|
124
|
+
% (self._name, self._name),
|
125
|
+
)
|
126
|
+
self._check_types_and_dimensions(types, dimensions)
|
127
|
+
|
128
|
+
def _check_types_and_dimensions(self, types: list[Type], dimensions: int):
|
129
|
+
types = _normalize_type_list(types)
|
130
|
+
assert len(types) > 0
|
131
|
+
assert dimensions >= 0
|
132
|
+
|
133
|
+
_check(
|
134
|
+
len(types) == len(self._types),
|
135
|
+
"Type mismatch for %s: expected %d types but saw %d" % (self._name, len(self._types), len(types)),
|
136
|
+
)
|
137
|
+
for i in range(0, len(types)):
|
138
|
+
_check(
|
139
|
+
self._types[i] == types[i],
|
140
|
+
"Type mismatch for %s:%d: expected %s saw %s" % (self._name, i, self._types[i], types[i]),
|
141
|
+
)
|
142
|
+
_check(
|
143
|
+
dimensions == self._dimensions,
|
144
|
+
"Dimensions mismatch for %s: expected %d saw %d" % (self._name, self._dimensions, dimensions),
|
145
|
+
)
|
146
|
+
|
147
|
+
|
148
|
+
# GeneratorParam is a string, int, float, bool, Type
|
149
|
+
class GeneratorParam:
|
150
|
+
# This tells PyType that we dynamically set attributes
|
151
|
+
# on this object. (We don't, actually, but it defeats
|
152
|
+
# PyType's complaints about using len(), arithmetic operators, etc.
|
153
|
+
# on GeneratorParam fields, since they are replaced at runtime.)
|
154
|
+
_HAS_DYNAMIC_ATTRIBUTES:bool = True
|
155
|
+
|
156
|
+
_name: str = ""
|
157
|
+
_value: Any = None
|
158
|
+
|
159
|
+
def __init__(self, value: Any):
|
160
|
+
# Don't parse it here, since we don't have our name yet
|
161
|
+
self._value = value
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def _parse_value(name: str, gp_type: type, value: Any) -> object:
|
165
|
+
if gp_type is str:
|
166
|
+
return str(value)
|
167
|
+
elif gp_type is bool:
|
168
|
+
return bool(value)
|
169
|
+
elif gp_type is int:
|
170
|
+
return int(value)
|
171
|
+
elif gp_type is float:
|
172
|
+
return float(value)
|
173
|
+
elif gp_type is Type:
|
174
|
+
if isinstance(value, (list, Type)):
|
175
|
+
value = _normalize_type_list(value)
|
176
|
+
else:
|
177
|
+
value = _parse_halide_type_list(str(value))
|
178
|
+
return value
|
179
|
+
else:
|
180
|
+
_fail("GeneratorParam %s has unsupported type %s" % (name, gp_type))
|
181
|
+
return None
|
182
|
+
|
183
|
+
def _make_replacement(self, value: Any, r: Requirement) -> Any:
|
184
|
+
assert _is_valid_name(r._name)
|
185
|
+
|
186
|
+
# Check the default value for validity
|
187
|
+
GeneratorParam._parse_value(r._name, type(self._value), self._value)
|
188
|
+
|
189
|
+
if value is not None:
|
190
|
+
# parse replacement value for validity, and also for type match with default.
|
191
|
+
return GeneratorParam._parse_value(r._name, type(self._value), value)
|
192
|
+
else:
|
193
|
+
return self._value
|
194
|
+
|
195
|
+
def _get_types_and_dimensions(self) -> (list[Type], int):
|
196
|
+
# Use dummy type-and-dimensions here so that the ctor won't fail due to undefined status
|
197
|
+
return [Int(32)], 0
|
198
|
+
|
199
|
+
|
200
|
+
class InputBuffer(ImageParam):
|
201
|
+
|
202
|
+
def __init__(self, type: Optional[Type], dimensions: Optional[int]):
|
203
|
+
type = _sanitize_type(type)
|
204
|
+
if dimensions is None:
|
205
|
+
dimensions = -1
|
206
|
+
super().__init__(type, dimensions, _unique_name())
|
207
|
+
|
208
|
+
def _get_types_and_dimensions(self) -> (list[Type], int):
|
209
|
+
return _normalize_type_list(self.type()), self.dimensions()
|
210
|
+
|
211
|
+
def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
|
212
|
+
return ArgInfoDirection.Input, ArgInfoKind.Buffer
|
213
|
+
|
214
|
+
def _make_replacement(self, value: Any, r: Requirement) -> ImageParam:
|
215
|
+
assert _is_valid_name(r._name) and len(r._types) == 1 and r._dimensions >= 0
|
216
|
+
if value is None:
|
217
|
+
return ImageParam(r._types[0], r._dimensions, r._name)
|
218
|
+
|
219
|
+
_check(
|
220
|
+
isinstance(value, (Buffer, ImageParam)),
|
221
|
+
"Input %s requires an ImageParam or Buffer argument when using call(), but saw %s" % (r._name, str(value)),
|
222
|
+
)
|
223
|
+
_check(
|
224
|
+
value.defined(),
|
225
|
+
"Cannot set the value for %s to an undefined value." % r._name,
|
226
|
+
)
|
227
|
+
if isinstance(value, Buffer):
|
228
|
+
im = ImageParam(value.type(), value.dimensions(), r._name)
|
229
|
+
im.set(value)
|
230
|
+
value = im
|
231
|
+
r._check_types_and_dimensions([value.type()], value.dimensions())
|
232
|
+
return value
|
233
|
+
|
234
|
+
|
235
|
+
class InputScalar(Param):
|
236
|
+
|
237
|
+
def __init__(self, type: Optional[Type]):
|
238
|
+
type = _sanitize_type(type)
|
239
|
+
super().__init__(type, _unique_name())
|
240
|
+
|
241
|
+
def _get_types_and_dimensions(self) -> (list[Type], int):
|
242
|
+
return _normalize_type_list(self.type()), 0
|
243
|
+
|
244
|
+
def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
|
245
|
+
return ArgInfoDirection.Input, ArgInfoKind.Scalar
|
246
|
+
|
247
|
+
def _make_replacement(self, value: Any, r: Requirement) -> Param:
|
248
|
+
assert _is_valid_name(r._name) and len(r._types) == 1 and r._dimensions == 0
|
249
|
+
if value is None:
|
250
|
+
return Param(r._types[0], r._name)
|
251
|
+
|
252
|
+
if isinstance(value, (int, float, bool)):
|
253
|
+
p = Param(r._types[0], r._name)
|
254
|
+
p.set(value)
|
255
|
+
return p
|
256
|
+
else:
|
257
|
+
_check(
|
258
|
+
isinstance(value, Param),
|
259
|
+
"Input %s requires a Param (or scalar literal) argument when using call(), but saw %s." %
|
260
|
+
(r._name, str(value)),
|
261
|
+
)
|
262
|
+
_check(
|
263
|
+
value.defined(),
|
264
|
+
"Cannot set the value for %s to an undefined value." % r._name,
|
265
|
+
)
|
266
|
+
r._check_types_and_dimensions([value.type()], 0)
|
267
|
+
return value
|
268
|
+
|
269
|
+
|
270
|
+
class OutputBuffer(Func):
|
271
|
+
|
272
|
+
def __init__(self, types: Optional[Type], dimensions: Optional[int]):
|
273
|
+
types = _normalize_type_list(types)
|
274
|
+
if dimensions is None:
|
275
|
+
dimensions = -1
|
276
|
+
super().__init__(types, dimensions, _unique_name())
|
277
|
+
self._types = types
|
278
|
+
self._dimensions = dimensions
|
279
|
+
|
280
|
+
def _get_types_and_dimensions(self) -> (list[Type], int):
|
281
|
+
return self._types, self._dimensions
|
282
|
+
|
283
|
+
def _get_direction_and_kind(self) -> (ArgInfoDirection, ArgInfoKind):
|
284
|
+
return ArgInfoDirection.Output, ArgInfoKind.Buffer
|
285
|
+
|
286
|
+
def _make_replacement(self, value: Any, r: Requirement) -> Func:
|
287
|
+
assert _is_valid_name(r._name) and len(r._types) > 0 and r._dimensions >= 0
|
288
|
+
if value is None:
|
289
|
+
return Func(r._types, r._dimensions, r._name)
|
290
|
+
|
291
|
+
_check(
|
292
|
+
isinstance(value, (Buffer, Func)),
|
293
|
+
"Output %s requires a Func or Buffer argument when using call(), but saw %s" % (r._name, str(value)),
|
294
|
+
)
|
295
|
+
_check(
|
296
|
+
value.defined(),
|
297
|
+
"Cannot set the value for %s to an undefined value." % r._name,
|
298
|
+
)
|
299
|
+
|
300
|
+
value_types = [value.type()] if isinstance(value, Buffer) else value.output_types
|
301
|
+
r._check_types_and_dimensions(value_types, value.dimensions())
|
302
|
+
|
303
|
+
if isinstance(value, Buffer):
|
304
|
+
# Allow assignment from a Buffer<> to an Output<Buffer<>>;
|
305
|
+
# this allows us to use a statically-compiled buffer inside a Generator
|
306
|
+
# to assign to an output.
|
307
|
+
f = Func(r._types, r._dimensions, r._name)
|
308
|
+
f[_] = value[_]
|
309
|
+
value = f
|
310
|
+
|
311
|
+
return value
|
312
|
+
|
313
|
+
|
314
|
+
# OutputScalar is just like OutputBuffer, except it is always dimensions = 0
|
315
|
+
class OutputScalar(OutputBuffer):
|
316
|
+
|
317
|
+
def __init__(self, types: Optional[Type]):
|
318
|
+
super().__init__(types, 0)
|
319
|
+
|
320
|
+
def _make_replacement(self, value: Any, r: Requirement) -> Func:
|
321
|
+
assert _is_valid_name(r._name) and len(r._types) > 0 and r._dimensions == 0
|
322
|
+
if value is None:
|
323
|
+
return Func(r._types, 0, r._name)
|
324
|
+
|
325
|
+
_check(
|
326
|
+
isinstance(value, Expr),
|
327
|
+
"Output %s requires an Expr argument when using call(), but saw %s" % (r._name, str(value)),
|
328
|
+
)
|
329
|
+
_check(
|
330
|
+
value.defined(),
|
331
|
+
"Cannot set the value for %s to an undefined value." % r._name,
|
332
|
+
)
|
333
|
+
r._check_types_and_dimensions(value.type(), 0)
|
334
|
+
f = Func(value.type(), 0, r._name)
|
335
|
+
f[_] = value
|
336
|
+
return f
|
337
|
+
|
338
|
+
|
339
|
+
@total_ordering
|
340
|
+
class _Stage(Enum):
|
341
|
+
generator_created = 0
|
342
|
+
gp_created = 1
|
343
|
+
gp_replaced = 2
|
344
|
+
io_created = 3
|
345
|
+
configure_called = 4
|
346
|
+
io_replaced = 5
|
347
|
+
pipeline_built = 6
|
348
|
+
|
349
|
+
def __lt__(self, other):
|
350
|
+
if self.__class__ is other.__class__:
|
351
|
+
return self.value < other.value
|
352
|
+
return NotImplemented
|
353
|
+
|
354
|
+
|
355
|
+
def _unsorted_cls_dir(cls):
|
356
|
+
for base in cls.__bases__:
|
357
|
+
yield from _unsorted_cls_dir(base)
|
358
|
+
|
359
|
+
# Don't use dir(): it will alphabetize the result. It's critical that
|
360
|
+
# we produce this list in order of declaration, which __dict__ should guarantee
|
361
|
+
# in Python 3.7+.
|
362
|
+
for k, v in cls.__dict__.items():
|
363
|
+
yield (k, v)
|
364
|
+
|
365
|
+
|
366
|
+
_halide_generator_context = ContextVar('halide_generator_context', default=None)
|
367
|
+
|
368
|
+
|
369
|
+
def _generatorcontext_enter(self: GeneratorContext) -> GeneratorContext:
|
370
|
+
if not hasattr(self, "_tokens"):
|
371
|
+
self._tokens = []
|
372
|
+
self._tokens.append(_halide_generator_context.set(self))
|
373
|
+
return self
|
374
|
+
|
375
|
+
|
376
|
+
def _generatorcontext_exit(self: GeneratorContext) -> None:
|
377
|
+
_halide_generator_context.reset(self._tokens.pop())
|
378
|
+
|
379
|
+
|
380
|
+
class Generator(ABC):
|
381
|
+
"""Base class for Halide Generators in Python"""
|
382
|
+
|
383
|
+
def context(self):
|
384
|
+
return GeneratorContext(self._target, self._autoscheduler)
|
385
|
+
|
386
|
+
def target(self):
|
387
|
+
return self._target
|
388
|
+
|
389
|
+
def autoscheduler(self):
|
390
|
+
return self._autoscheduler
|
391
|
+
|
392
|
+
def using_autoscheduler(self):
|
393
|
+
return bool(self._autoscheduler.name)
|
394
|
+
|
395
|
+
def natural_vector_size(self, type: Type) -> int:
|
396
|
+
return self.target().natural_vector_size(type)
|
397
|
+
|
398
|
+
def add_requirement(self, condition: Expr, *args) -> None:
|
399
|
+
assert self._stage < _Stage.pipeline_built
|
400
|
+
self._pipeline_requirements.append((condition, [*args]))
|
401
|
+
|
402
|
+
@classmethod
|
403
|
+
def call(cls, *args, **kwargs):
|
404
|
+
generator = cls()
|
405
|
+
|
406
|
+
# First, fill in all the GeneratorParams
|
407
|
+
# (in case some are tied to Inputs).
|
408
|
+
gp = kwargs.pop("generator_params", {})
|
409
|
+
_check(isinstance(gp, dict), "generator_params must be a dict")
|
410
|
+
for k, v in gp.items():
|
411
|
+
generator._set_generatorparam_value(k, v)
|
412
|
+
|
413
|
+
generator._advance_to_stage(_Stage.configure_called)
|
414
|
+
|
415
|
+
_check(
|
416
|
+
len(args) <= len(generator._arginfos_in),
|
417
|
+
"Generator '%s' allows at most %d positional args, but %d were specified." %
|
418
|
+
(generator._get_name(), len(generator._arginfos_in), len(args)))
|
419
|
+
|
420
|
+
inputs_set = []
|
421
|
+
for i in range(0, len(args)):
|
422
|
+
a = generator._arginfos_in[i]
|
423
|
+
k = a.name
|
424
|
+
v = args[i]
|
425
|
+
_check(k not in inputs_set, "Input %s was specified multiple times." % k)
|
426
|
+
inputs_set.append(k)
|
427
|
+
generator._bind_input(k, [v])
|
428
|
+
|
429
|
+
input_names = [a.name for a in generator._arginfos_in]
|
430
|
+
for k, v in kwargs.items():
|
431
|
+
_check(k in input_names, "Unknown input '%s' specified via keyword argument." % k)
|
432
|
+
_check(k not in inputs_set, "Input %s specified multiple times." % k)
|
433
|
+
inputs_set.append(k)
|
434
|
+
generator._bind_input(k, [v])
|
435
|
+
|
436
|
+
_check(
|
437
|
+
len(inputs_set) == len(generator._arginfos_in), "Generator '%s' requires %d args, but %d were specified." %
|
438
|
+
(generator._get_name(), len(generator._arginfos_in), len(inputs_set)))
|
439
|
+
|
440
|
+
generator._build_pipeline()
|
441
|
+
|
442
|
+
outputs = []
|
443
|
+
for o in generator._arginfos_out:
|
444
|
+
outputs.append(generator._get_output_func(o.name))
|
445
|
+
|
446
|
+
return outputs[0] if len(outputs) == 1 else tuple(outputs)
|
447
|
+
|
448
|
+
def compile_to_callable(self):
|
449
|
+
pipeline = self._build_pipeline()
|
450
|
+
arguments = [
|
451
|
+
self._get_input_parameter(a.name)._to_argument()
|
452
|
+
for a in self._get_arginfos()
|
453
|
+
if a.dir == ArgInfoDirection.Input
|
454
|
+
]
|
455
|
+
return pipeline.compile_to_callable(arguments, self._target)
|
456
|
+
|
457
|
+
# Make it hard for the user to overwrite any members that are GeneratorParams, Inputs, or Outputs
|
458
|
+
def __setattr__(self, name, value):
|
459
|
+
r = getattr(self, "_requirements", None)
|
460
|
+
s = getattr(self, "_stage", None)
|
461
|
+
if r and s and (name in r) and (s != _Stage.configure_called) and (s != _Stage.gp_created):
|
462
|
+
raise AttributeError("Invalid write to field '%s'" % name)
|
463
|
+
super().__setattr__(name, value)
|
464
|
+
|
465
|
+
def __init__(self, generator_params: dict = {}):
|
466
|
+
context = active_generator_context()
|
467
|
+
|
468
|
+
self._target = context.target()
|
469
|
+
self._autoscheduler = context.autoscheduler_params()
|
470
|
+
self._stage = _Stage.generator_created
|
471
|
+
self._gp_dict = None
|
472
|
+
self._inputs_dict = None
|
473
|
+
self._outputs_dict = None
|
474
|
+
self._arginfos_in = None
|
475
|
+
self._arginfos_out = None
|
476
|
+
self._pipeline = None
|
477
|
+
self._input_parameters = None
|
478
|
+
self._output_funcs = None
|
479
|
+
self._unhandled_generator_params = {}
|
480
|
+
self._requirements = {}
|
481
|
+
self._replacements = {}
|
482
|
+
self._in_configure = 0
|
483
|
+
self._pipeline_requirements = []
|
484
|
+
|
485
|
+
self._advance_to_gp_created()
|
486
|
+
if generator_params:
|
487
|
+
_check(isinstance(generator_params, dict), "generator_params must be a dict")
|
488
|
+
for k, v in generator_params.items():
|
489
|
+
self._set_generatorparam_value(k, v)
|
490
|
+
|
491
|
+
def allow_out_of_order_inputs_and_outputs(self):
|
492
|
+
return False
|
493
|
+
|
494
|
+
def configure(self):
|
495
|
+
pass
|
496
|
+
|
497
|
+
@abstractmethod
|
498
|
+
def generate(self):
|
499
|
+
pass
|
500
|
+
|
501
|
+
def _add_gpio(self, name: str, io, io_dict: dict, arginfos: Optional[list]) -> None:
|
502
|
+
_check_valid_name(name)
|
503
|
+
_check(
|
504
|
+
name not in self._requirements,
|
505
|
+
"The name '%s' is used more than once in Generator %s." % (name, self._get_name()),
|
506
|
+
)
|
507
|
+
types, dimensions = io._get_types_and_dimensions()
|
508
|
+
types, dimensions = self._set_io_types_and_dimensions_from_gp(name, types, dimensions)
|
509
|
+
r = Requirement(name, types, dimensions)
|
510
|
+
self._requirements[name] = r
|
511
|
+
io_dict[name] = io
|
512
|
+
if arginfos is not None:
|
513
|
+
arginfos.append(ArgInfo(name, *io._get_direction_and_kind(), types, dimensions))
|
514
|
+
|
515
|
+
def add_input(self, name: str, io) -> None:
|
516
|
+
_check(self._in_configure > 0, "Can only call add_input() from the configure() method.")
|
517
|
+
_check(not hasattr(self, name),
|
518
|
+
"Cannot call add_input('%s') because the class already has a member of that name." % name)
|
519
|
+
_check(isinstance(io, (InputBuffer, InputScalar)),
|
520
|
+
"Cannot call add_input() with an object of type '%s'." % type(io))
|
521
|
+
self._add_gpio(name, io, self._inputs_dict, self._arginfos_in)
|
522
|
+
|
523
|
+
def add_output(self, name: str, io) -> None:
|
524
|
+
_check(self._in_configure > 0, "Can only call add_output() from the configure() method.")
|
525
|
+
_check(not hasattr(self, name),
|
526
|
+
"Cannot call add_output('%s') because the class already has a member of that name." % name)
|
527
|
+
_check(isinstance(io, (OutputBuffer, OutputScalar)),
|
528
|
+
"Cannot call add_output() with an object of type '%s'." % type(io))
|
529
|
+
self._add_gpio(name, io, self._outputs_dict, self._arginfos_out)
|
530
|
+
|
531
|
+
def _advance_to_stage(self, new_stage: _Stage):
|
532
|
+
_stage_advancers = {
|
533
|
+
_Stage.gp_created: self._advance_to_gp_created,
|
534
|
+
_Stage.gp_replaced: self._advance_to_gp_replaced,
|
535
|
+
_Stage.io_created: self._advance_to_io_created,
|
536
|
+
_Stage.configure_called: self._advance_to_configure_called,
|
537
|
+
_Stage.io_replaced: self._advance_to_io_replaced,
|
538
|
+
}
|
539
|
+
assert new_stage in _stage_advancers
|
540
|
+
a = _stage_advancers[new_stage]
|
541
|
+
old_stage = self._stage
|
542
|
+
if self._stage < new_stage:
|
543
|
+
a()
|
544
|
+
assert self._stage >= new_stage
|
545
|
+
|
546
|
+
def _advance_to_gp_created(self):
|
547
|
+
assert self._stage < _Stage.gp_created
|
548
|
+
assert not self._gp_dict
|
549
|
+
assert not self._inputs_dict
|
550
|
+
assert not self._outputs_dict
|
551
|
+
|
552
|
+
self._gp_dict = {}
|
553
|
+
for name, gp in _unsorted_cls_dir(self.__class__):
|
554
|
+
if not isinstance(gp, GeneratorParam):
|
555
|
+
continue
|
556
|
+
self._add_gpio(name, gp, self._gp_dict, None)
|
557
|
+
|
558
|
+
self._stage = _Stage.gp_created
|
559
|
+
|
560
|
+
if hasattr(self, "_halide_alias_generator_params"):
|
561
|
+
for k, v in self._halide_alias_generator_params.items():
|
562
|
+
self._set_generatorparam_value(k, v)
|
563
|
+
|
564
|
+
def _advance_to_io_created(self):
|
565
|
+
self._advance_to_stage(_Stage.gp_replaced)
|
566
|
+
assert self._gp_dict is not None
|
567
|
+
assert not self._inputs_dict
|
568
|
+
assert not self._outputs_dict
|
569
|
+
|
570
|
+
self._inputs_dict = {}
|
571
|
+
self._outputs_dict = {}
|
572
|
+
self._arginfos_in = []
|
573
|
+
self._arginfos_out = []
|
574
|
+
outputs_seen = False
|
575
|
+
for name, io in _unsorted_cls_dir(self.__class__):
|
576
|
+
is_input = isinstance(io, (InputBuffer, InputScalar))
|
577
|
+
is_output = isinstance(io, (OutputBuffer, OutputScalar))
|
578
|
+
if not (is_input or is_output):
|
579
|
+
continue
|
580
|
+
|
581
|
+
if is_input and outputs_seen and not self.allow_out_of_order_inputs_and_outputs():
|
582
|
+
io_order_warning = ("Generators will always produce code that orders all Inputs before all Outputs; "
|
583
|
+
"this Generator declares the Inputs and Outputs in a different order, so the calling convention may not be as expected. "
|
584
|
+
"A future version of Halide will make this illegal, and require all Inputs to be declared before all Outputs. "
|
585
|
+
"(You can avoid this requirement by overriding Generator::allow_out_of_order_inputs_and_outputs().)")
|
586
|
+
warnings.warn(io_order_warning)
|
587
|
+
|
588
|
+
if is_output:
|
589
|
+
outputs_seen = True
|
590
|
+
d = self._inputs_dict if is_input else self._outputs_dict
|
591
|
+
a = self._arginfos_in if is_input else self._arginfos_out
|
592
|
+
self._add_gpio(name, io, d, a)
|
593
|
+
|
594
|
+
_check(
|
595
|
+
len(self._unhandled_generator_params) == 0,
|
596
|
+
"Generator %s has no GeneratorParam(s) named: %s" %
|
597
|
+
(self._get_name(), list(self._unhandled_generator_params.keys())),
|
598
|
+
)
|
599
|
+
|
600
|
+
self._stage = _Stage.io_created
|
601
|
+
|
602
|
+
def _set_io_types_and_dimensions_from_gp(self, name: str, current_types: list[Type],
|
603
|
+
current_dimensions: int) -> (list[Type], int):
|
604
|
+
new_types = current_types
|
605
|
+
new_dimensions = current_dimensions
|
606
|
+
|
607
|
+
type_name = "%s.type" % name
|
608
|
+
if type_name in self._unhandled_generator_params:
|
609
|
+
value = self._unhandled_generator_params.pop(type_name)
|
610
|
+
new_types = GeneratorParam._parse_value(type_name, Type, value)
|
611
|
+
_check(
|
612
|
+
len(current_types) == 0,
|
613
|
+
"Cannot set the GeneratorParam %s for %s because the value is explicitly specified in the Python source."
|
614
|
+
% (type_name, self._get_name()),
|
615
|
+
)
|
616
|
+
|
617
|
+
dimensions_name = "%s.dim" % name
|
618
|
+
if dimensions_name in self._unhandled_generator_params:
|
619
|
+
value = self._unhandled_generator_params.pop(dimensions_name)
|
620
|
+
new_dimensions = GeneratorParam._parse_value(dimensions_name, int, value)
|
621
|
+
_check(
|
622
|
+
current_dimensions == -1,
|
623
|
+
"Cannot set the GeneratorParam %s for %s because the value is explicitly specified in the Python source."
|
624
|
+
% (dimensions_name, self._get_name()),
|
625
|
+
)
|
626
|
+
|
627
|
+
return new_types, new_dimensions
|
628
|
+
|
629
|
+
def _advance_to_configure_called(self):
|
630
|
+
self._advance_to_stage(_Stage.io_created)
|
631
|
+
|
632
|
+
self._in_configure += 1
|
633
|
+
self.configure()
|
634
|
+
self._in_configure -= 1
|
635
|
+
|
636
|
+
_check(
|
637
|
+
len(self._outputs_dict) > 0,
|
638
|
+
"Generator '%s' must declare at least one output." % self._get_name(),
|
639
|
+
)
|
640
|
+
|
641
|
+
self._stage = _Stage.configure_called
|
642
|
+
|
643
|
+
def _replace_one(self, gpio_dict):
|
644
|
+
for name, gpio in gpio_dict.items():
|
645
|
+
# Note that new_io will be a different type (e.g. InputBuffer -> ImageParam)
|
646
|
+
new_gpio = self._replacements.pop(name, None)
|
647
|
+
if not new_gpio:
|
648
|
+
new_gpio = gpio._make_replacement(None, self._requirements[name])
|
649
|
+
setattr(self, name, new_gpio)
|
650
|
+
|
651
|
+
def _advance_to_gp_replaced(self):
|
652
|
+
self._advance_to_stage(_Stage.gp_created)
|
653
|
+
self._replace_one(self._gp_dict)
|
654
|
+
self._stage = _Stage.gp_replaced
|
655
|
+
|
656
|
+
def _advance_to_io_replaced(self):
|
657
|
+
self._advance_to_stage(_Stage.configure_called)
|
658
|
+
self._replace_one(self._inputs_dict)
|
659
|
+
self._replace_one(self._outputs_dict)
|
660
|
+
self._stage = _Stage.io_replaced
|
661
|
+
|
662
|
+
# --------------- AbstractGenerator methods (partial)
|
663
|
+
def _get_name(self) -> str:
|
664
|
+
return self._halide_registered_name
|
665
|
+
|
666
|
+
def _get_arginfos(self) -> list[ArgInfo]:
|
667
|
+
self._advance_to_stage(_Stage.configure_called)
|
668
|
+
return self._arginfos_in + self._arginfos_out
|
669
|
+
|
670
|
+
def _set_generatorparam_value(self, name: str, value: Any):
|
671
|
+
_check(
|
672
|
+
name != "target",
|
673
|
+
"The GeneratorParam named %s cannot be set by set_generatorparam_value." % name,
|
674
|
+
)
|
675
|
+
assert self._stage == _Stage.gp_created
|
676
|
+
assert not self._pipeline
|
677
|
+
if name in self._gp_dict:
|
678
|
+
gp = self._gp_dict[name]
|
679
|
+
assert isinstance(gp, GeneratorParam)
|
680
|
+
old_value = gp._value
|
681
|
+
new_value = GeneratorParam._parse_value(name, type(old_value), value)
|
682
|
+
# Do not mutate the existing GP in place; it could be shared across multiple Generators.
|
683
|
+
self._gp_dict[name] = GeneratorParam(new_value)
|
684
|
+
elif name == "autoscheduler":
|
685
|
+
_check(not self.autoscheduler().name, "The GeneratorParam %s cannot be set more than once" % name)
|
686
|
+
self.autoscheduler().name = value
|
687
|
+
elif name.startswith("autoscheduler."):
|
688
|
+
sub_key = name[len("autoscheduler."):]
|
689
|
+
_check(sub_key not in self.autoscheduler().extra,
|
690
|
+
"The GeneratorParam %s cannot be set more than once" % name)
|
691
|
+
self.autoscheduler().extra[sub_key] = value
|
692
|
+
else:
|
693
|
+
self._unhandled_generator_params[name] = value
|
694
|
+
|
695
|
+
def _build_pipeline(self) -> Pipeline:
|
696
|
+
self._advance_to_stage(_Stage.io_replaced)
|
697
|
+
|
698
|
+
assert not self._pipeline
|
699
|
+
assert not self._input_parameters
|
700
|
+
assert not self._output_funcs
|
701
|
+
|
702
|
+
# Ensure that the current context is the one in self.
|
703
|
+
# For most Generators this won't matter, but if the Generator
|
704
|
+
# invokes SomeOtherGenerator.call(), it would be nice to have this
|
705
|
+
# be the default, so that the end user doesn't have to mess with it.
|
706
|
+
with self.context():
|
707
|
+
self.generate()
|
708
|
+
|
709
|
+
self._input_parameters = {n: getattr(self, n).parameter() for n in self._inputs_dict}
|
710
|
+
self._output_funcs = {n: getattr(self, n) for n in self._outputs_dict}
|
711
|
+
_check(
|
712
|
+
len(self._output_funcs) > 0,
|
713
|
+
"Generator %s must declare at least one Output in Arguments." % self._get_name(),
|
714
|
+
)
|
715
|
+
|
716
|
+
funcs = []
|
717
|
+
for name, f in self._output_funcs.items():
|
718
|
+
_check(f.defined(), "Output '%s' was not defined." % name)
|
719
|
+
self._requirements[name]._check_types_and_dimensions(f.types(), f.dimensions())
|
720
|
+
funcs.append(f)
|
721
|
+
|
722
|
+
self._pipeline = Pipeline(funcs)
|
723
|
+
for condition, error_args in self._pipeline_requirements:
|
724
|
+
self._pipeline.add_requirement(condition, *error_args)
|
725
|
+
self._stage = _Stage.pipeline_built
|
726
|
+
return self._pipeline
|
727
|
+
|
728
|
+
def _get_input_parameter(self, name: str) -> InternalParameter:
|
729
|
+
assert self._stage == _Stage.pipeline_built
|
730
|
+
_check(name in self._input_parameters, "Unknown input: %s" % name)
|
731
|
+
return self._input_parameters[name]
|
732
|
+
|
733
|
+
def _get_output_func(self, name: str) -> Func:
|
734
|
+
assert self._stage == _Stage.pipeline_built
|
735
|
+
_check(name in self._output_funcs, "Unknown output: %s" % name)
|
736
|
+
return self._output_funcs[name]
|
737
|
+
|
738
|
+
def _bind_input(self, name: str, values: list[object]):
|
739
|
+
assert self._stage < _Stage.io_replaced
|
740
|
+
self._advance_to_stage(_Stage.configure_called)
|
741
|
+
_check(len(values) == 1, "Too many values specified for input: %s" % name)
|
742
|
+
_check(name in self._inputs_dict, "There is no input with the name: %s" % name)
|
743
|
+
assert name not in self._replacements
|
744
|
+
self._replacements[name] = self._inputs_dict[name]._make_replacement(values[0], self._requirements[name])
|
745
|
+
|
746
|
+
|
747
|
+
_python_generators: dict = {}
|
748
|
+
|
749
|
+
|
750
|
+
def _get_python_generator_names() -> list[str]:
|
751
|
+
return _python_generators.keys()
|
752
|
+
|
753
|
+
|
754
|
+
def _create_python_generator(name: str, context: GeneratorContext):
|
755
|
+
cls = _python_generators.get(name, None)
|
756
|
+
if not isclass(cls):
|
757
|
+
return None
|
758
|
+
with context:
|
759
|
+
return cls()
|
760
|
+
|
761
|
+
|
762
|
+
def _fqname(o):
|
763
|
+
k = o
|
764
|
+
m = k.__module__
|
765
|
+
q = k.__qualname__
|
766
|
+
if m == "__main__" or k == "builtins":
|
767
|
+
return q
|
768
|
+
return m + "." + q
|
769
|
+
|
770
|
+
|
771
|
+
def active_generator_context() -> GeneratorContext:
|
772
|
+
context = _halide_generator_context.get()
|
773
|
+
_check(isinstance(context, GeneratorContext), "There is no active GeneratorContext")
|
774
|
+
return context
|
775
|
+
|
776
|
+
|
777
|
+
def _is_interactive_mode() -> bool:
|
778
|
+
return hasattr(sys, 'ps1')
|
779
|
+
|
780
|
+
|
781
|
+
def _check_generator_name_in_use(n:str):
|
782
|
+
if _is_interactive_mode():
|
783
|
+
# In interactive mode, it's OK to redefine generators... in fact, it's really
|
784
|
+
# annoying not to allow this (e.g. in Colab)
|
785
|
+
#
|
786
|
+
# (Want to debug? Include this:)
|
787
|
+
# if n in _python_generators:
|
788
|
+
# builtins.print("REDEFINING ", n)
|
789
|
+
return
|
790
|
+
|
791
|
+
_check(n not in _python_generators, "The Generator name %s is already in use." % n)
|
792
|
+
|
793
|
+
|
794
|
+
def alias(**kwargs):
|
795
|
+
|
796
|
+
def alias_impl(cls):
|
797
|
+
for k, v in kwargs.items():
|
798
|
+
_check_valid_name(k)
|
799
|
+
_check(hasattr(cls, "_halide_registered_name"), "@alias can only be used in conjunction with @generator.")
|
800
|
+
_check_generator_name_in_use(k)
|
801
|
+
_check(type(v) is dict, "The Generator alias %s specifies something other than a dict." % k)
|
802
|
+
new_cls = type(k, (cls,), {"_halide_registered_name": k, "_halide_alias_generator_params": v})
|
803
|
+
_python_generators[k] = new_cls
|
804
|
+
return cls
|
805
|
+
|
806
|
+
return alias_impl
|
807
|
+
|
808
|
+
def generator(name:str=""):
|
809
|
+
# This code relies on dicts preserving key-insertion order, which is only
|
810
|
+
# guaranteed for all Python implementations as of v3.7.
|
811
|
+
_check(sys.version_info >= (3, 7), "Halide Generators require Python 3.7 or later.")
|
812
|
+
def generator_impl(cls):
|
813
|
+
n = name if name else _fqname(cls)
|
814
|
+
_check_generator_name_in_use(n)
|
815
|
+
_check(isclass(cls), "@generator can only be used on classes.")
|
816
|
+
# Allow (but don't require) explicit inheritance from hl.Generator;
|
817
|
+
# static type checkers (e.g. pytype) can complain that the decorated class
|
818
|
+
# uses inherited methods since it can't correctly infer the inheritance.
|
819
|
+
if issubclass(cls, Generator):
|
820
|
+
new_cls = type(cls.__name__, (cls,), {"_halide_registered_name": n})
|
821
|
+
else:
|
822
|
+
new_cls = type(cls.__name__, (cls, Generator), {"_halide_registered_name": n})
|
823
|
+
_python_generators[n] = new_cls
|
824
|
+
return new_cls
|
825
|
+
|
826
|
+
return generator_impl
|
827
|
+
|
828
|
+
def funcs(names:str) -> tuple(Func):
|
829
|
+
"""Given a space-delimited string, create a Func for each substring and return as a tuple."""
|
830
|
+
return (Func(n) for n in names.split(' '))
|
831
|
+
|
832
|
+
|
833
|
+
def vars(names:str) -> tuple(Var):
|
834
|
+
"""Given a space-delimited string, create a Var for each substring and return as a tuple."""
|
835
|
+
return (Var(n) for n in names.split(' '))
|