halide 19.0.0__cp313-cp313-win_amd64.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.
Files changed (85) hide show
  1. halide/__init__.py +39 -0
  2. halide/_generator_helpers.py +835 -0
  3. halide/bin/Halide.dll +0 -0
  4. halide/bin/adams2019_retrain_cost_model.exe +0 -0
  5. halide/bin/adams2019_weightsdir_to_weightsfile.exe +0 -0
  6. halide/bin/anderson2021_retrain_cost_model.exe +0 -0
  7. halide/bin/anderson2021_weightsdir_to_weightsfile.exe +0 -0
  8. halide/bin/featurization_to_sample.exe +0 -0
  9. halide/bin/gengen.exe +0 -0
  10. halide/bin/get_host_target.exe +0 -0
  11. halide/halide_.cp313-win_amd64.pyd +0 -0
  12. halide/imageio.py +60 -0
  13. halide/include/Halide.h +35293 -0
  14. halide/include/HalideBuffer.h +2618 -0
  15. halide/include/HalidePyTorchCudaHelpers.h +64 -0
  16. halide/include/HalidePyTorchHelpers.h +120 -0
  17. halide/include/HalideRuntime.h +2221 -0
  18. halide/include/HalideRuntimeCuda.h +89 -0
  19. halide/include/HalideRuntimeD3D12Compute.h +91 -0
  20. halide/include/HalideRuntimeHexagonDma.h +104 -0
  21. halide/include/HalideRuntimeHexagonHost.h +157 -0
  22. halide/include/HalideRuntimeMetal.h +112 -0
  23. halide/include/HalideRuntimeOpenCL.h +119 -0
  24. halide/include/HalideRuntimeQurt.h +32 -0
  25. halide/include/HalideRuntimeVulkan.h +137 -0
  26. halide/include/HalideRuntimeWebGPU.h +44 -0
  27. halide/lib/Halide.lib +0 -0
  28. halide/lib/HalidePyStubs.lib +0 -0
  29. halide/lib/Halide_GenGen.lib +0 -0
  30. halide/lib/autoschedule_adams2019.dll +0 -0
  31. halide/lib/autoschedule_anderson2021.dll +0 -0
  32. halide/lib/autoschedule_li2018.dll +0 -0
  33. halide/lib/autoschedule_mullapudi2016.dll +0 -0
  34. halide/lib/cmake/Halide/FindHalide_LLVM.cmake +152 -0
  35. halide/lib/cmake/Halide/FindV8.cmake +33 -0
  36. halide/lib/cmake/Halide/Halide-shared-deps.cmake +0 -0
  37. halide/lib/cmake/Halide/Halide-shared-targets-release.cmake +29 -0
  38. halide/lib/cmake/Halide/Halide-shared-targets.cmake +154 -0
  39. halide/lib/cmake/Halide/HalideConfig.cmake +162 -0
  40. halide/lib/cmake/Halide/HalideConfigVersion.cmake +65 -0
  41. halide/lib/cmake/HalideHelpers/FindHalide_WebGPU.cmake +27 -0
  42. halide/lib/cmake/HalideHelpers/Halide-Interfaces-release.cmake +112 -0
  43. halide/lib/cmake/HalideHelpers/Halide-Interfaces.cmake +236 -0
  44. halide/lib/cmake/HalideHelpers/HalideGeneratorHelpers.cmake +1056 -0
  45. halide/lib/cmake/HalideHelpers/HalideHelpersConfig.cmake +28 -0
  46. halide/lib/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
  47. halide/lib/cmake/HalideHelpers/HalideTargetHelpers.cmake +99 -0
  48. halide/lib/cmake/HalideHelpers/MutexCopy.ps1 +31 -0
  49. halide/lib/cmake/HalideHelpers/TargetExportScript.cmake +55 -0
  50. halide/lib/cmake/Halide_Python/Halide_Python-targets-release.cmake +29 -0
  51. halide/lib/cmake/Halide_Python/Halide_Python-targets.cmake +125 -0
  52. halide/lib/cmake/Halide_Python/Halide_PythonConfig.cmake +26 -0
  53. halide/lib/cmake/Halide_Python/Halide_PythonConfigVersion.cmake +65 -0
  54. halide/share/doc/Halide/LICENSE.txt +233 -0
  55. halide/share/doc/Halide/README.md +439 -0
  56. halide/share/doc/Halide/doc/BuildingHalideWithCMake.md +626 -0
  57. halide/share/doc/Halide/doc/CodeStyleCMake.md +393 -0
  58. halide/share/doc/Halide/doc/FuzzTesting.md +104 -0
  59. halide/share/doc/Halide/doc/HalideCMakePackage.md +812 -0
  60. halide/share/doc/Halide/doc/Hexagon.md +73 -0
  61. halide/share/doc/Halide/doc/Python.md +844 -0
  62. halide/share/doc/Halide/doc/RunGen.md +283 -0
  63. halide/share/doc/Halide/doc/Testing.md +125 -0
  64. halide/share/doc/Halide/doc/Vulkan.md +287 -0
  65. halide/share/doc/Halide/doc/WebAssembly.md +228 -0
  66. halide/share/doc/Halide/doc/WebGPU.md +128 -0
  67. halide/share/tools/RunGen.h +1470 -0
  68. halide/share/tools/RunGenMain.cpp +642 -0
  69. halide/share/tools/adams2019_autotune_loop.sh +227 -0
  70. halide/share/tools/anderson2021_autotune_loop.sh +591 -0
  71. halide/share/tools/halide_benchmark.h +240 -0
  72. halide/share/tools/halide_image.h +31 -0
  73. halide/share/tools/halide_image_info.h +318 -0
  74. halide/share/tools/halide_image_io.h +2794 -0
  75. halide/share/tools/halide_malloc_trace.h +102 -0
  76. halide/share/tools/halide_thread_pool.h +161 -0
  77. halide/share/tools/halide_trace_config.h +559 -0
  78. halide-19.0.0.data/data/share/cmake/Halide/HalideConfig.cmake +6 -0
  79. halide-19.0.0.data/data/share/cmake/Halide/HalideConfigVersion.cmake +65 -0
  80. halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfig.cmake +6 -0
  81. halide-19.0.0.data/data/share/cmake/HalideHelpers/HalideHelpersConfigVersion.cmake +54 -0
  82. halide-19.0.0.dist-info/METADATA +301 -0
  83. halide-19.0.0.dist-info/RECORD +85 -0
  84. halide-19.0.0.dist-info/WHEEL +5 -0
  85. halide-19.0.0.dist-info/licenses/LICENSE.txt +233 -0
@@ -0,0 +1,844 @@
1
+ # Halide Bindings for Python
2
+
3
+ <!-- TOC -->
4
+ * [Halide Bindings for Python](#halide-bindings-for-python)
5
+ * [Acquiring the Python bindings](#acquiring-the-python-bindings)
6
+ * [Building the Python bindings](#building-the-python-bindings)
7
+ * [Using CMake directly](#using-cmake-directly)
8
+ * [Using wheel infrastructure](#using-wheel-infrastructure)
9
+ * [Documentation and Examples](#documentation-and-examples)
10
+ * [Differences from C++ API](#differences-from-c-api)
11
+ * [Example of Simple Usage](#example-of-simple-usage)
12
+ * [Halide Generators In Python](#halide-generators-in-python)
13
+ * [Writing a Generator in Python](#writing-a-generator-in-python)
14
+ * [`hl.generator("name")`](#hlgeneratorname)
15
+ * [hl.GeneratorParam](#hlgeneratorparam)
16
+ * [hl.InputBuffer, hl.InputScalar](#hlinputbuffer-hlinputscalar)
17
+ * [hl.OutputBuffer, hl.OutputScalar](#hloutputbuffer-hloutputscalar)
18
+ * [Names](#names)
19
+ * [generate() method](#generate-method)
20
+ * [Types for Inputs and Outputs](#types-for-inputs-and-outputs)
21
+ * [Using a Generator for JIT compilation](#using-a-generator-for-jit-compilation)
22
+ * [Using a Generator for AOT compilation](#using-a-generator-for-aot-compilation)
23
+ * [Calling Generator-Produced code from Python](#calling-generator-produced-code-from-python)
24
+ * [Advanced Generator-Related Topics](#advanced-generator-related-topics)
25
+ * [Generator Aliases](#generator-aliases)
26
+ * [Dynamic Inputs and Outputs](#dynamic-inputs-and-outputs)
27
+ * [Calling a Generator Directly](#calling-a-generator-directly)
28
+ * [The Lifecycle Of A Generator](#the-lifecycle-of-a-generator)
29
+ * [Notable Differences Between C++ and Python Generators](#notable-differences-between-c-and-python-generators)
30
+ * [Keeping Up To Date](#keeping-up-to-date)
31
+ * [License](#license)
32
+ <!-- TOC -->
33
+
34
+ Halide provides Python bindings for most of its public API. Python 3.8 (or
35
+ higher) is required. The Python bindings are supported on 64-bit Linux, OSX, and
36
+ Windows systems.
37
+
38
+ In addition to the ability to write just-in-time Halide code using Python, you
39
+ can write [Generators](#halide-generators-in-python) using the Python bindings,
40
+ which can simplify build-system integration (since no C++ metacompilation step
41
+ is required).
42
+
43
+ You can also use existing Halide Generators (written in either C++ or Python)
44
+ to produce Python extensions that can be used within Python code.
45
+
46
+ ## Acquiring the Python bindings
47
+
48
+ As of Halide 19.0.0, we provide binary wheels on PyPI which include the Python
49
+ bindings and the C++/CMake package for native development. Full releases may be
50
+ installed with `pip` like so:
51
+
52
+ ```shell
53
+ $ pip install halide
54
+ ```
55
+
56
+ Every commit to `main` is published to Test PyPI as a development version and
57
+ these may be installed with a few extra flags:
58
+
59
+ ```shell
60
+ $ pip install halide --pre --extra-index-url https://test.pypi.org/simple
61
+ ```
62
+
63
+ Currently, we provide wheels for: Windows x86-64, macOS x86-64, macOS arm64, and
64
+ Linux x86-64. The Linux wheels are built for manylinux_2_28, which makes them
65
+ broadly compatible (Debian 10, Ubuntu 18.10, Fedora 29).
66
+
67
+ ## Building the Python bindings
68
+
69
+ If `pip` isn't enough for your purposes, or you are developing Halide directly,
70
+ you have two options for building and using the Python bindings. Note that the
71
+ bindings require Halide to be built with RTTI and exceptions **enabled**, which
72
+ in turn requires LLVM to be built with RTTI, but this is not the default for
73
+ LLVM.
74
+
75
+ ### Using CMake directly
76
+
77
+ Before configuring with CMake, you should ensure you have prerequisite packages
78
+ installed in your local Python environment. The best way to get set up is to use
79
+ a virtual environment:
80
+
81
+ ```shell
82
+ $ python3 -m venv venv
83
+ $ . venv/bin/activate
84
+ $ python3 -m pip install -U pip "setuptools[core]" wheel
85
+ $ python3 -m pip install -r requirements.txt
86
+ ```
87
+
88
+ Then build and install Halide:
89
+
90
+ ```shell
91
+ $ cmake -G Ninja -S . -B build \
92
+ -DCMAKE_BUILD_TYPE=Release \
93
+ -DWITH_PYTHON_BINDINGS=ON
94
+ $ cmake --build build
95
+ $ cmake --install build --prefix .local
96
+ ```
97
+
98
+ Now you can set the `PYTHONPATH` variable to point to the freshly built Python
99
+ package:
100
+
101
+ ```shell
102
+ $ export PYTHONPATH="$PWD/.local/lib/python3/site-packages"
103
+ ```
104
+
105
+ ### Using wheel infrastructure
106
+
107
+ You can also follow the same procedure that we use to build the published
108
+ wheels. First, create a virtual environment as before, but omit
109
+ `requirements.txt`
110
+
111
+ ```shell
112
+ $ python3 -m venv venv
113
+ $ . venv/bin/activate
114
+ $ python3 -m pip install -U pip "setuptools[core]" wheel
115
+ ```
116
+
117
+ Next, ensure you have installed Halide's dependencies to locations where CMake
118
+ can find them, given your environment. The variables `Halide_LLVM_ROOT`,
119
+ `flatbuffers_ROOT`, and `wabt_ROOT` specify locations for the relevant packages
120
+ directly. If they are all installed to a common prefix, you can add it to the
121
+ environment variable `CMAKE_PREFIX_PATH`.
122
+
123
+ Then it should be as simple as:
124
+
125
+ ```shell
126
+ $ pip install .
127
+ ```
128
+
129
+ ## Documentation and Examples
130
+
131
+ As mentioned elsewhere, the Python API attempts to mimic the
132
+ [C++ Halide API](http://halide-lang.org/docs) as directly as possible; there
133
+ isn't separate Python-specific documentation for the API at this time.
134
+
135
+ For now, examine the code for the example applications in the `test/apps/` and
136
+ `tutorial/` subdirectories.
137
+
138
+ The tests run as part of the standard CTest infrastructure and are labeled with
139
+ the `python` label. You can run the Python tests specifically by running:
140
+
141
+ ```
142
+ $ ctest -L python
143
+ ```
144
+
145
+ from the Halide build directory.
146
+
147
+ ## Differences from C++ API
148
+
149
+ The Python bindings attempt to mimic the Halide C++ API as closely as possible,
150
+ with some differences where the C++ idiom is either inappropriate or impossible:
151
+
152
+ - Most APIs that take a variadic argument list of ints in C++ take an explicit
153
+ list in Python. For instance, the usual version of the `Buffer` ctor in C++
154
+ offers both variadic and list versions:
155
+
156
+ ```cpp
157
+ Buffer<>(Type t, int extent_dim_0, int extent_dim_1, ...., extent_dim_N, string name = "");
158
+ Buffer<>(Type t, vector<int> extents, string name = "");
159
+ ```
160
+
161
+ In Python, only the second variant is provided.
162
+
163
+ - `Func` and `Buffer` access is done using `[]` rather than `()`
164
+
165
+ - For zero-dimensional `Func` and `Buffer`, you must explicitly specify
166
+ `[()]` -- that is, use an empty tuple as the index -- because `[]` is not
167
+ syntactically acceptable in Python.
168
+
169
+ - Some classes in the Halide API aren't provided because standard Python idioms
170
+ are a better fit:
171
+
172
+ - `Halide::Tuple` doesn't exist in the Python bindings; an ordinary Python
173
+ tuple of `Halide::Expr` is used instead.
174
+ - `Halide::Realization` doesn't exist in the Python bindings; an ordinary
175
+ Python tuple of `Halide::Buffer` is used instead.
176
+
177
+ - static and instance method overloads with the same name in the same class
178
+ aren't allowed, so some convenience methods are missing from `Halide::Var`
179
+
180
+ - Templated types (notably `Halide::Buffer<>` and `Halide::Param<>`) aren't
181
+ provided, for obvious reasons; only the equivalents of
182
+ `Halide::Buffer<void>` and `Halide::Param<void>` are supported.
183
+
184
+ - The functions in `Halide::ConciseCasts` are present in the toplevel Halide
185
+ module in Python, rather than a submodule: e.g., use `halide.i8_sat()`, not
186
+ `halide.ConciseCasts.i8_sat()`.
187
+
188
+ - Only things in the `Halide` namespace are supported; classes and methods that
189
+ involve using the `Halide::Internal` namespace are not provided.
190
+
191
+ - No mechanism is provided for overriding any runtime functions from Python for
192
+ JIT-compiled code. (Runtime functions for AOT-compiled code can be overridden
193
+ by building and linking a custom runtime, but not currently via any runtime
194
+ API, e.g. halide_set_custom_print() does not exist.)
195
+
196
+ - No mechanism is provided for supporting `Func::define_extern`.
197
+
198
+ - `Buffer::for_each_value()` isn't supported yet.
199
+
200
+ - `Func::in` becomes `Func.in_` because `in` is a Python keyword.
201
+
202
+ - `Func::async` becomes `Func.async_` because `async` is a Python keyword.
203
+
204
+ - The `not` keyword cannot be used to negate boolean Halide expressions.
205
+ Instead, the `logical_not` function can be used and is equivalent to using
206
+ `operator!` in C++.
207
+
208
+ - There is no way to override the logical `and`/`or` operators in Python to work
209
+ with `Expr`: you must use the bitwise `|` and `&` instead. (Note that
210
+ incorrectly using `and`/`or` just short-circuits weirdly, rather than failing
211
+ with some helpful error; this is an issue that we have not yet found any way
212
+ to improve, unfortunately.)
213
+
214
+ - Some error messages need to be made more informative.
215
+
216
+ - Some exceptions are the "incorrect" type (compared to C++ expectations).
217
+
218
+ - Many hooks to override runtime functions (e.g. Func::set_error_handler)
219
+ aren't yet implemented.
220
+
221
+ - The following parts of the Halide public API are currently missing entirely
222
+ from the Python bindings (but are all likely to be supported at some point in
223
+ the future):
224
+
225
+ - `DeviceInterface`
226
+ - `evaluate()`
227
+
228
+ ## Example of Simple Usage
229
+
230
+ Here is a basic example of using Halide to produce a procedural image.
231
+
232
+ ```python
233
+ # By convention, we import halide as 'hl' for terseness
234
+ import halide as hl
235
+
236
+ # Some constants
237
+ edge = 512
238
+ k = 20.0 / float(edge)
239
+
240
+ # Simple formula
241
+ x, y, c = hl.Var('x'), hl.Var('y'), hl.Var('c')
242
+ f = hl.Func('f')
243
+ e = hl.sin(x * ((c + 1) / 3.0) * k) * hl.cos(y * ((c + 1) / 3.0) * k)
244
+ f[x, y, c] = hl.cast(hl.UInt(8), e * 255.0)
245
+ f.vectorize(x, 8).parallel(y)
246
+
247
+ # Realize into a Buffer.
248
+ buf = f.realize([edge, edge, 3])
249
+
250
+ # Do something with the image. We'll just save it to a PNG.
251
+ from halide import imageio
252
+
253
+ imageio.imwrite("/tmp/example.png", buf)
254
+ ```
255
+
256
+ It's worth noting in the example above that the Halide `Buffer` object supports
257
+ the Python Buffer Protocol (https://www.python.org/dev/peps/pep-3118) and thus
258
+ is converted to and from other compatible objects (e.g., NumPy's `ndarray`), at
259
+ essentially zero cost, with storage being shared. Thus, we can usually pass it
260
+ directly to existing Python APIs (like `imsave()`) that expect 'image-like'
261
+ objects without any explicit conversion necessary.
262
+
263
+ ## Halide Generators In Python
264
+
265
+ In Halide, a "Generator" is a unit of encapsulation for Halide code. It is a
266
+ self-contained piece of code that can:
267
+
268
+ - Produce a chunk of Halide IR (in the form of an `hl.Pipeline`) that is
269
+ appropriate for compilation (via either JIT or AOT)
270
+ - Expose itself to the build system in a discoverable way
271
+ - Fully describe itself for the build system with metadata for (at least) the
272
+ type and number of inputs and outputs expected
273
+ - Allow for build-time customization of coder-specified parameters in a way that
274
+ doesn't require editing of source code
275
+
276
+ Originally, Halide only supported writing Generators in C++. In this document,
277
+ we'll use the term "C++ Generator" to mean "Generator written in C++ using the
278
+ classic API", the term "Python Generator" to mean "Generator written in Halide's
279
+ Python bindings", and just plain "Generator" when the discussion is relatively
280
+ neutral with respect to the implementation language/API.
281
+
282
+ ### Writing a Generator in Python
283
+
284
+ A Python Generator is a class that:
285
+
286
+ - has the `@hl.generator` decorator applied to it
287
+ - declares zero or more member fields that are initialized with values of
288
+ `hl.InputBuffer` or `hl.InputScalar`, which specify the expected input(s) of
289
+ the resulting `Pipeline`.
290
+ - declares one or more member fields that are initialized with values of
291
+ `hl.OutputBuffer` or `hl.OutputScalar`, which specify the expected output(s)
292
+ of the resulting `Pipeline`.
293
+ - declares zero or more member fields that are initialized with values of
294
+ `hl.GeneratorParam`, which can be used to pass arbitrary information from the
295
+ build system to the Generator. A GeneratorParam can carry a value of type
296
+ `bool`, `int`, `float`, `str`, or `hl.Type`.
297
+ - declares a `generate()` method that fill in the Halide IR needed to define all
298
+ the Outputs
299
+ - optionally declares a `configure()` method to dynamically add Inputs or
300
+ Outputs to the pipeline, based on (e.g.) the values of `GeneratorParam`
301
+ values or other external inputs
302
+
303
+ Let's look at a fairly simple example:
304
+
305
+ > **TODO:** this example is pretty contrived; is there an equally simple
306
+ > Generator to use here that would demonstrate the basics?
307
+
308
+ ```python
309
+ import halide as hl
310
+
311
+ x = hl.Var('x')
312
+ y = hl.Var('y')
313
+
314
+ _operators = {
315
+ 'xor': lambda a, b: a ^ b,
316
+ 'and': lambda a, b: a & b,
317
+ 'or': lambda a, b: a | b
318
+ }
319
+
320
+
321
+ # Apply a mask value to a 2D image using a logical operator that is selected at compile-time.
322
+ @hl.generator(name="logical_op_generator")
323
+ class LogicalOpGenerator:
324
+ op = hl.GeneratorParam("xor")
325
+
326
+ input = hl.InputBuffer(hl.UInt(8), 2)
327
+ mask = hl.InputScalar(hl.UInt(8))
328
+
329
+ output = hl.OutputBuffer(hl.UInt(8), 2)
330
+
331
+ def generate(g):
332
+ # Algorithm
333
+ operator = _operators[g.op]
334
+ g.output[x, y] = operator(g.input[x, y], g.mask)
335
+
336
+ # Schedule
337
+ v = g.natural_vector_size(hl.UInt(8))
338
+ g.output.vectorize(x, v)
339
+
340
+
341
+ if __name__ == "__main__":
342
+ hl.main()
343
+ ```
344
+
345
+ If you've worked with Halide Generators written in C++, the "shape" of this will
346
+ likely look familiar. (If not, no worries; you shouldn't need any knowledge of
347
+ C++ Generators for the following to make sense.)
348
+
349
+ Let's take the details here one at a time.
350
+
351
+ #### `hl.generator("name")`
352
+
353
+ This decorator adds appropriate "glue" machinery to the class to enforce various
354
+ invariants. It also serves as the declares a "registered name" for the
355
+ Generator, which is a unique name that the build system will use to identify the
356
+ Generator. If you omit the name, it defaults to `module.classname`; if module is
357
+ `__main__` then we omit it and just use the plain classname. Note that the
358
+ registered name need not match the classname. (Inside Halide, we use the
359
+ convention of `CamelCase` for class names and `snake_case` for registered names,
360
+ but you can use whatever convention you like.)
361
+
362
+ #### hl.GeneratorParam
363
+
364
+ Each `GeneratorParam` is an arbitrary key-value pair that can be used to provide
365
+ configurable options at compile time. You provide the name and a default value.
366
+ The default value can be overridden by the build machinery, which will replace
367
+ the value (based on user specified text).
368
+
369
+ Note that the type of the default value *is* used to define the expected type of
370
+ the `GeneratorParam`, and trying to set it to an incompatible value will throw
371
+ an exception. The types that are acceptable to use in a `GeneratorParam` are:
372
+
373
+ - Python's `bool`, `int`, `float`, or `str`
374
+ - Halide's `hl.Type`
375
+ - ...that's all
376
+
377
+ Note that the value of a `GeneratorParam` is read-only from the point of view of
378
+ the Generator; they are set at Generator construction time and attempting to
379
+ change their value will throw an exception.
380
+
381
+ #### hl.InputBuffer, hl.InputScalar
382
+
383
+ These declare the inputs to the `hl.Pipeline` that the Generator will produce.
384
+ An `hl.InputScalar` is, essentially, a "factory" that produces an `hl.Param` in
385
+ the existing Python API, while an `hl.InputBuffer` is a factory for
386
+ `hl.ImageParam`.
387
+
388
+ From the Generator author's perspective, a field initialized with `InputScalar`
389
+ **is** a `Param` – not kinda-like-one, not a magic wrapper that forwards
390
+ everything; it is literally just `hl.Param`. Similarly, an `InputBuffer`
391
+ produces `ImageParam`, and an `InputFunc` is a wrapper around `Func`. You won't
392
+ be able to assign a new value to the member field for Inputs – as with
393
+ GeneratorParams, they are "read-only" to the Generator – but you will be able to
394
+ set constraints on them.
395
+
396
+ Note that in addition to specifying a concrete type and dimensionality for the
397
+ inputs, these factory classes support the ability to specify either (or both)
398
+ `None`, which means the type/dimensionality will be provided by GeneratorParams
399
+ in the build system.
400
+
401
+ #### hl.OutputBuffer, hl.OutputScalar
402
+
403
+ These declare the output(s) of the Pipeline that the Generator will produce. An
404
+ `hl.OutputBuffer` is, essentially, a "factory" that produces an `hl.Func` in the
405
+ existing Python API. (`hl.OutputScalar` is just an `hl.OutputBuffer` that always
406
+ has zero dimensions.)
407
+
408
+ From the Generator author's perspective, a field declared with `OutputBuffer`
409
+ **is** a `Func` – not kinda-like-one, not a magic wrapper that forwards
410
+ everything; it is literally just `hl.Func` (with type-and-dimensionality set to
411
+ match, see recent PR https://github.com/halide/Halide/pull/6734) . You won't be
412
+ able to assign a new value to the member field for Inputs – as with
413
+ GeneratorParams, they are "read-only" to the Generator – but you will be able to
414
+ set constraints on them.
415
+
416
+ Note that in addition to specifying a concrete type and dimensionality for the
417
+ inputs, these factory classes support the ability to specify either (or both) as
418
+ `None`, which means the type/dimensionality will be provided by GeneratorParams
419
+ in the build system.
420
+
421
+ #### Names
422
+
423
+ Note that all the GeneratorParams, Inputs, and Outputs have names that are
424
+ implicitly filled in based on the field name of their initial assignment; unlike
425
+ in C++ Generators, there isn't a way to "override" this name (i.e., the name in
426
+ the IR will always exactly match the Python field name). Names have the same
427
+ constraints as for C++ Generators (essentially, a C identifier, but without an
428
+ initial underscore, and without any double underscore anywhere).
429
+
430
+ #### generate() method
431
+
432
+ This will be called by the Generator machinery to build the Pipeline. As with
433
+ C++ Generators, the only required task is to ensure that all Output fields are
434
+ fully defined, in a way that matches the type-and-dimension constraints
435
+ specified.
436
+
437
+ It is required that the `generate()` method be defined by the Generator.
438
+
439
+ (Note that, by convention, Halide Generators use `g` instead of `self` in their
440
+ `generate()` method to make the expression language terser; this is not in any
441
+ way required, but is recommended to improve readability.)
442
+
443
+ #### Types for Inputs and Outputs
444
+
445
+ For all the Input and Output fields of Generators, you can specify native Python
446
+ types (instead of `hl.Type`) for certain cases that are unambiguous. At present,
447
+ we allow `bool` as an alias for `hl.Bool()`, `int` as an alias for
448
+ `hl.Int(32)`, and `float` as an alias for `hl.Float(32)`.
449
+
450
+ ### Using a Generator for JIT compilation
451
+
452
+ You can use the `compile_to_callable()` method to JIT-compile a Generator into a
453
+ `hl.Callable`, which is (essentially) just a dynamically-created function.
454
+
455
+ ```python
456
+ import LogicalOpGenerator
457
+ from halide import imageio
458
+ import numpy as np
459
+
460
+ # Instantiate a Generator -- we can only set the GeneratorParams
461
+ # by passing in a dict to the Generator's constructor
462
+ or_op_generator = LogicalOpGenerator({"op": "or"})
463
+
464
+ # Now compile the Generator into a Callable
465
+ or_filter = or_op_generator.compile_to_callable()
466
+
467
+ # Read in some file for input
468
+ input_buf = imageio.imread("/path/to/some/file.png")
469
+ assert input_buf.ndim == 2
470
+ assert input_buf.dtype == np.uint8
471
+
472
+ # create a Buffer-compatible object for the output; we'll use np.array
473
+ output_buf = np.empty(input_buf.shape, dtype=input_buf.dtype)
474
+
475
+ # Note, Python code throws exception for error conditions rather than returning an int
476
+ or_filter(input_buf, 0x7f, output_buf)
477
+
478
+ # Note also that we can use named arguments for any/all, in the Python manner:
479
+ or_filter(mask=0x7f, input=input_buf, output=output_buf)
480
+
481
+ imageio.imwrite("/tmp/or.png", output_buf)
482
+ ```
483
+
484
+ By default, a Generator will produce code targeted at `Target("host")` (or the
485
+ value of the `HL_JIT_TARGET` environment variable, if set); you can override
486
+ this behavior selectively by activating a `GeneratorContext` when the Generator
487
+ is *created*:
488
+
489
+ ```python
490
+ import LogicalOpGenerator
491
+
492
+ # Compile with debugging enabled
493
+ t = hl.Target("host-debug")
494
+ with hl.GeneratorContext(t):
495
+ or_op_generator = LogicalOpGenerator({"op": "or"})
496
+ or_filter = or_op_generator.compile_to_callable()
497
+ ```
498
+
499
+ ### Using a Generator for AOT compilation
500
+
501
+ If you are using CMake, the simplest thing is to use
502
+ `add_halide_library` and `add_halide_python_extension_library()`:
503
+
504
+ ```cmake
505
+ # Build a Halide library as you usually would, but be sure to include `PYTHON_EXTENSION`
506
+ add_halide_library(xor_filter
507
+ FROM logical_op_generator
508
+ PARAMS op=xor
509
+ PYTHON_EXTENSION output_path_var
510
+ [ FEATURES ... ]
511
+ [ PARAMS ... ])
512
+
513
+ # Now wrap the generated code with a Python extension.
514
+ # (Note that module name defaults to match the target name; we only
515
+ # need to specify MODULE_NAME if we need a name that may differ)
516
+ add_halide_python_extension_library(my_extension
517
+ MODULE_NAME my_module
518
+ HALIDE_LIBRARIES xor_filter)
519
+ ```
520
+
521
+ (Note that this rule works for both C++ and Python Generators.)
522
+
523
+ This compiles the Generator code in `logical_op_generator.py` with the
524
+ registered name `logical_op_generator` to produce the target `xor_filter`, and
525
+ then wraps the compiled output with a Python extension. The result will be a
526
+ shared library of the form `<target>.<soabi>.so`, where `<soabi>` describes
527
+ the specific Python version and platform (e.g., `cpython-310-darwin` for
528
+ Python 3.10 on OSX.)
529
+
530
+ Note that you can combine multiple Halide libraries into a single Python module;
531
+ this is convenient for packaging, but also because all the libraries in a single
532
+ extension module share the same Halide runtime (and thus, the same caches,
533
+ thread pools, etc.).
534
+
535
+ ```cmake
536
+ add_halide_library(xor_filter ...)
537
+ add_halide_library(and_filter ...)
538
+ add_halide_library(or_filter ...)
539
+
540
+ add_halide_python_extension_library(my_extension
541
+ MODULE_NAME my_module
542
+ HALIDE_LIBRARIES xor_filter and_filter or_filter)
543
+ ```
544
+
545
+ Note that you must take care to ensure that all of the `add_halide_library`
546
+ targets specified use the same Halide runtime; it may be necessary to use
547
+ `add_halide_runtime`
548
+ to define an explicit runtime that is shared by all the targets:
549
+
550
+ ```cmake
551
+ add_halide_runtime(my_runtime)
552
+
553
+ add_halide_library(xor_filter USE_RUNTIME my_runtime ...)
554
+ add_halide_library(and_filter USE_RUNTIME my_runtime ...)
555
+ add_halide_library(or_filter USE_RUNTIME my_runtime ...)
556
+
557
+ add_halide_python_extension_library(my_extension
558
+ MODULE_NAME my_module
559
+ HALIDE_LIBRARIES xor_filter and_filter or_filter)
560
+ ```
561
+
562
+ If you're not using CMake, you can "drive" a Generator directly from your build
563
+ system via command-line flags. The most common, minimal set looks something like
564
+ this:
565
+
566
+ ```shell
567
+ python3 /path/to/my/generator.py -g <registered-name> \
568
+ -o <output-dir> \
569
+ target=<halide-target-string> \
570
+ [generator-param=value ...]
571
+ ```
572
+
573
+ The argument to `-g` is the name supplied to the `@hl.generator` decorator. The
574
+ argument to -o is a directory to use for the output files; by default, we'll
575
+ produce a static library containing the object code, and a C++ header file with
576
+ a forward declaration. `target` specifies a Halide `Target` string describing
577
+ the OS, architecture, features, etc. that should be used for compilation. Any
578
+ other arguments to the command line that don't begin with `-` are presumed to
579
+ name
580
+ `GeneratorParam` values to set.
581
+
582
+ There are other flags and options too, of course; use `python3
583
+ /path/to/my/generator.py -help` to see a list with explanations.
584
+
585
+ (Unfortunately, there isn't (yet) a way to produce a Python Extension just by
586
+ running a Generator; the logic for `add_halide_python_extension_library` is
587
+ currently all in the CMake helper files.)
588
+
589
+ ### Calling Generator-Produced code from Python
590
+
591
+ As long as the shared library is in `PYTHONPATH`, it can be imported and used
592
+ directly. For the example above:
593
+
594
+ ```python
595
+ from my_module import xor_filter
596
+ from halide import imageio
597
+ import numpy as np
598
+
599
+ # Read in some file for input
600
+ input_buf = imageio.imread("/path/to/some/file.png")
601
+ assert input_buf.ndim == 2
602
+ assert input_buf.dtype == np.uint8
603
+
604
+ # create a Buffer-compatible object for the output; we'll use np.array
605
+ output_buf = np.empty(input_buf.shape, dtype=input_buf.dtype)
606
+
607
+ # Note, Python code throws exception for error conditions rather than returning an int
608
+ xor_filter(input_buf, 0xff, output_buf)
609
+
610
+ # Note also that we can use named arguments for any/all, in the Python manner:
611
+ # xor_filter(input=input_buf, mask=0xff, output=output_buf)
612
+
613
+ imageio.imwrite("/tmp/xored.png", output_buf)
614
+ ```
615
+
616
+ Above, we're using common Python utilities (`numpy`) to construct the
617
+ input/output buffers we want to pass to Halide.
618
+
619
+ **Note**: Getting the memory order correct can be a little confusing for numpy.
620
+ By default, numpy uses "C-style"
621
+ [row-major](https://docs.scipy.org/doc/numpy-1.13.0/reference/internals.html)
622
+ order, which sounds like the right option for Halide; however, this nomenclature
623
+ assumes the matrix-math convention of ordering axes as `[rows, cols]`, whereas
624
+ Halide (and imaging code in general) generally assumes `[x, y]` (i.e., `[cols,
625
+ rows]`). Thus, what you usually want in Halide is column-major ordering. This
626
+ means numpy arrays, by default, come with the wrong memory layout for Halide.
627
+ But if you construct the numpy arrays yourself (like above), you can pass
628
+ `order='F'` to make numpy use the Halide-compatible memory layout. If you're
629
+ passing in an array constructed somewhere else, the easiest thing to do is to
630
+ `.transpose()` it before passing it to your Halide code.
631
+
632
+ ### Advanced Generator-Related Topics
633
+
634
+ #### Generator Aliases
635
+
636
+ A Generator alias is a way to associate a Generator with one (or more) specific
637
+ sets of GeneratorParams; the 'alias' is just another registered name. This
638
+ offers a convenient alternative to specifying multiple sets of GeneratorParams
639
+ via the build system. To define alias(es) for a Generator, just add the
640
+ `@hl.alias` decorator before `@hl.generator` decorator:
641
+
642
+ ```python
643
+ @hl.alias(
644
+ xor_generator={"op": "xor"},
645
+ and_generator={"op": "and"},
646
+ or_generator={"op": "or"}
647
+ )
648
+ @hl.generator("logical_op_generator")
649
+ class LogicalOpGenerator:
650
+ ...
651
+ ```
652
+
653
+ #### Dynamic Inputs and Outputs
654
+
655
+ If you need to build `Input` and/or `Output` dynamically, you can define a
656
+ `configure()` method. It will always be called after all `GeneratorParam` values
657
+ are valid, but before `generate()` is called. Let's take our example and add an
658
+ option to pass an offset to be added after the logical operator is done:
659
+
660
+ ```python
661
+ import halide as hl
662
+
663
+ x = hl.Var('x')
664
+ y = hl.Var('y')
665
+
666
+ _operators = {
667
+ 'xor': lambda a, b: a ^ b,
668
+ 'and': lambda a, b: a & b,
669
+ 'or': lambda a, b: a | b
670
+ }
671
+
672
+
673
+ # Apply a mask value to a 2D image using a logical operator that is selected at compile-time.
674
+ @hl.generator(name="logical_op_generator")
675
+ class LogicalOpGenerator:
676
+ op = hl.GeneratorParam("xor")
677
+ with_offset = hl.GeneratorParam(False)
678
+
679
+ input = hl.InputBuffer(hl.UInt(8), 2)
680
+ mask = hl.InputScalar(hl.UInt(8))
681
+
682
+ output = hl.OutputBuffer(hl.UInt(8), 2)
683
+
684
+ def configure(g):
685
+ # If with_offset is specified, we
686
+ if g.with_offset:
687
+ g.add_input("offset", hl.InputScalar(hl.Int(32)))
688
+
689
+ # See note the use of 'g' instead of 'self' here
690
+ def generate(g):
691
+ # Algorithm
692
+ operator = _operators[g.op]
693
+ if hasattr(g, "offset"):
694
+ g.output[x, y] = operator(g.input[x, y], g.mask) + g.offset
695
+ else:
696
+ g.output[x, y] = operator(g.input[x, y], g.mask)
697
+
698
+ # Schedule
699
+ v = g.natural_vector_size(hl.UInt(8))
700
+ g.output.vectorize(x, v)
701
+
702
+
703
+ if __name__ == "__main__":
704
+ hl.main()
705
+ ```
706
+
707
+ The only thing you can (usefully) do from `configure()` is to call `add_input()`
708
+ or `add_output()`, which accept only the appropriate `Input` or `Output`
709
+ classes. The resulting value is stored as a member variable with the name
710
+ specified (if there is already a member with the given name, an exception is
711
+ thrown).
712
+
713
+ #### Calling a Generator Directly
714
+
715
+ Each Generator has a class method (injected by `@hl.generator`) that allows you
716
+ to "call" the Generator like an ordinary function; this allows you to directly
717
+ take the Halide IR produced by the Generator and do anything you want to with
718
+ it. This can be especially useful when writing library code, as you can
719
+ 'compose' more complex pipelines this way.
720
+
721
+ This method is named `call()` and looks like this:
722
+
723
+ ```python
724
+ @classmethod
725
+ def call(cls, *args, **kwargs):
726
+ ...
727
+ ```
728
+
729
+ It takes the inputs (specified either by-name or by-position in the usual Python
730
+ way). It also allows for an optional by-name-only argument, `generator_params`,
731
+ which is a simple Python dict that allows for overriding `GeneratorParam`s. It
732
+ returns a tuple of the Output values. For the earlier example, usage might be
733
+ something like:
734
+
735
+ ```python
736
+ import LogicalOpFilter
737
+
738
+ x, y = hl.Var(), hl.Var()
739
+
740
+ input_buf = hl.Buffer(hl.UInt(8), [2, 2])
741
+ mask_value = 0x7f
742
+
743
+ # Inputs by-position
744
+ func_out = LogicalOpFilter.call(input_buf, mask_value)
745
+
746
+ # Inputs by-name
747
+ func_out = LogicalOpFilter.call(mask=mask_value, input=input_buf)
748
+
749
+ # Above again, but with generator_params
750
+ func_out = LogicalOpFilter.call(input_buf, mask_value,
751
+ generator_params={"op": "and"})
752
+ func_out = LogicalOpFilter.call(generator_params={"op": "and"},
753
+ input=input_buf, mask=mask_value)
754
+ ```
755
+
756
+ #### The Lifecycle Of A Generator
757
+
758
+ Whether being driven by a build system (for AOT use) or by another piece of
759
+ Python code (typically for JIT use), the lifecycle of a Generator looks
760
+ something like this:
761
+
762
+ - An instance of the Generator in question is created. It uses the
763
+ currently-active `GeneratorContext` (which contains the `Target` to be used
764
+ for code generation), which is stored in a thread-local stack.
765
+ - Some (or all) of the default values of the `GeneratorParam` members may be
766
+ replaced based on (e.g.) command-line arguments in the build system
767
+ - All `GeneratorParam` members are made immutable.
768
+ - The `configure()` method is called, allowing the Generator to use
769
+ `add_input()` or `add_output()` to dynamically add inputs and/or outputs.
770
+ - If any `Input` or `Output` members were defined with unspecified type or
771
+ dimensions (e.g. `some_input = hl.InputBuffer(None, 3)`), those types and
772
+ dimensions are filled in from `GeneratorParam` values (e.g.
773
+ `some_input.type` in this case). If any types or dimensions are left
774
+ unspecified after this step, an exception will be thrown.
775
+ - If the Generator is being invoked via its `call()` method (see below), the
776
+ default values for `Inputs` will be replaced by the values from the argument
777
+ list.
778
+ - The Generator instance has its `generate()` method called.
779
+ - The calling code will extract the values of all `Output` values and validate
780
+ that they match the type, dimensions, etc. of the declarations.
781
+ - The calling code will then either call `compile_to_file()` and friends (for
782
+ AOT use), or return the output values to the caller (for JIT use).
783
+ - Finally, the Generator instance will be discarded, never to be used again.
784
+
785
+ Note that almost all the code doing the hand-wavy bits above is injected by the
786
+ `@hl.generator` decorator – the Generator author doesn't need to know or care
787
+ about the specific details, only that they happen.
788
+
789
+ All Halide Generators are **single-use** instances – that is, any given
790
+ Generator instance should be used at most once. If a Generator is to be executed
791
+ multiple times (e.g. for different `GeneratorParam` values, or a different
792
+ `Target`), a new one must be constructed each time.
793
+
794
+ #### Notable Differences Between C++ and Python Generators
795
+
796
+ If you have written C++ Generators in Halide in the past, you might notice some
797
+ features are missing and/or different for Python Generators. Among the
798
+ differences are:
799
+
800
+ - In C++, you can create a Generator, then call `set_generatorparam_value()`
801
+ to alter the values of GeneratorParams. In Python, there is no public method
802
+ to alter a GeneratorParam after the Generator is created; instead, you must
803
+ pass a dict of GeneratorParam values to the constructor, after which the
804
+ values are immutable for that Generator instance.
805
+ - Array Inputs/Outputs: in our experience, they are pretty rarely used, it
806
+ complicates the implementation in nontrivial ways, and the majority of use
807
+ cases for them can all be reasonably supported by dynamically adding inputs or
808
+ outputs (and saving the results in a local array).
809
+ - `Input<Func>` and `Output<Func>`: these were deliberately left out in order to
810
+ simplify Python Generators. It's possible that something similar might be
811
+ added in the future.
812
+ - GeneratorParams with LoopLevel types: these aren't useful without
813
+ `Input<Func>`/`Output<Func>`.
814
+ - GeneratorParams with Enum types: using a plain `str` type in Python is
815
+ arguably just as easy, if not easier.
816
+ - `get_externs_map()`: this allows registering ExternalCode objects to be
817
+ appended to the Generator's code. In our experience, this feature is very
818
+ rarely used. We will consider adding this in the future if necessary.
819
+ - Lazy Binding of Unspecified Input/Output Types: for C++ Generators, if you
820
+ left an Output's type (or dimensionality) unspecified, you didn't always have
821
+ to specify a `GeneratorParam` to make it into a concrete type: if the type was
822
+ always fully specified by the contents of the `generate()` method, that was
823
+ good enough. In Python Generators, by contrast, **all** types and dimensions
824
+ must be **explicitly** specified by either code declaration or by
825
+ `GeneratorParam` setting. This simplifies the internal code in nontrivial
826
+ ways, and also allows for (arguably) more readable code, since there are no
827
+ longer cases that require the reader to execute the code in their head in
828
+ order to deduce the output types.
829
+
830
+ ## Keeping Up To Date
831
+
832
+ If you use the Halide Bindings for Python inside Google, you are *strongly*
833
+ encouraged to
834
+ [subscribe to announcements for new releases of Halide](https://github.blog/changelog/2018-11-27-watch-releases/),
835
+ as it is likely that enhancements and tweaks to our Python support will be made
836
+ in future releases.
837
+
838
+ ## License
839
+
840
+ The Python bindings use the same
841
+ [MIT license](https://github.com/halide/Halide/blob/main/LICENSE.txt) as Halide.
842
+
843
+ Python bindings provided by Connelly Barnes (2012-2013), Fred Rotbart (2014),
844
+ Rodrigo Benenson (2015) and the Halide open-source community.