Halide

GitHub

a language for fast, portable data-parallel computation

RAW Doc

Doc/BuildingHalideWithCMake

Building Halide with CMake

This is a detailed guide to building Halide with CMake. If you want to learn how
to use Halide in your own CMake projects, see [HalideCMakePackage.md]. If you
are looking for Halide's CMake coding guidelines, see [CodeStyleCMake.md].

- Building Halide with CMake
- Installing CMake
- Cross-platform
- Windows
- macOS
- Ubuntu Linux
- Optional: Install Ninja
- Dependencies
- Summary
- Installing dependencies
- vcpkg
- Windows
- Homebrew
- Ubuntu / Debian
- Python
- Building Halide
- Basic build
- Windows
- macOS and Linux
- CMake Presets
- Common presets
- Vcpkg presets
- Sanitizer presets
- Build options
- Installing
- Building Halide with pip
- Using ccache with pip builds

Installing CMake

This section covers installing a recent version of CMake and the correct
dependencies for building and using Halide. If you have not used CMake before,
we strongly suggest reading through the [CMake documentation][cmake-docs] first.

Halide requires at least version 3.28. Fortunately, getting a recent version of
CMake couldn't be easier, and there are multiple good options on any system to
do so. Generally, one should always have the most recent version of CMake
installed system-wide. CMake is committed to backwards compatibility and even
the most recent release can build projects over a decade old.

Cross-platform

Kitware provides packages for CMake on [PyPI][pypi-cmake] which can be installed
via pip into a [virtual environment][venv]. There are binary wheels available
for nearly all relevant platforms, including:

| OS | x86-32 | x86-64 | ARM64 |
| ----------------- | ------------------ | ------------------ | -------------------------- |
| Windows | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| macOS | :x: | 10.10+ | 11.0+ (incl. universal2) |
| Linux (musl 1.1+) | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Linux (glibc) | glibc 2.12+ | glibc 2.12+ | glibc 2.17+ |

We recommend installing CMake using [pipx] to avoid package conflicts and
redundant installations. After installing pipx, run:

shell
$ pipx install cmake

Alternatively, you can use a normal virtual environment:

shell
$ python -m pip install cmake

If you don't want Python to manage your CMake installation, you can either
follow the platform-specific instructions below or install CMake from
[Kitware's binary releases][cmake-download]. If all else fails, you might need
to build CMake from source (e.g. on 32-bit ARM). In that case, follow the
directions posted on [Kitware's website][cmake-from-source].

Windows

On Windows, there are two primary methods for installing an up-to-date CMake:

1. You can get CMake through the Visual Studio 2022 installer.
2. You can use Windows's built-in package manager, [winget]:

shell
winget install Kitware.CMake

We prefer the first option for its simplicity. See Microsoft's
[documentation][vs-cmake-docs] for more details.

macOS

[Homebrew] keeps its [CMake package][brew-cmake] up to date. Simply run:

shell
$ brew install cmake

Ubuntu Linux

There are a few good ways to install CMake on Ubuntu:

1. If you're running 24.04 LTS, then simply running sudo apt install cmake
will install CMake 3.28.
2. If you're running an older LTS or would like to use the newest CMake, try
installing via the [snap store]: snap install cmake. Note this will
conflict with an APT-provided CMake.
3. Kitware also provides an [APT repository][cmake-apt] with up-to-date
releases. Compatible with 20.04 LTS+ and is the best option for 32-bit ARM.

For other Linux distributions, check with your distribution's package manager.

Note: On WSL 1, snap is not available; in this case, prefer to use APT. On
WSL 2, all methods are available.

Optional: Install Ninja

We strongly recommend using [Ninja] as your go-to CMake generator for working
with Halide. It has a much richer dependency structure than the alternatives,
and it is the only generator capable of producing accurate incremental builds.

It is available in most package repositories:

- Python: pipx install ninja
- Visual Studio Installer: alongside CMake
- winget: winget install Ninja-build.Ninja
- Homebrew: brew install ninja
- APT: apt install ninja-build

You can also place a [pre-built binary][ninja-download] from their website in
the PATH.

Dependencies

Summary

The following is a complete list of required and optional dependencies for
building the core pieces of Halide.

| Dependency | Version | Required when... | Notes |
| ------------- | ------------------ | -------------------------- | --------------------------------------------------- |
| [LLVM] | _see policy below_ | _always_ | WebAssembly and X86 targets are required. |
| [Clang] | ==LLVM | _always_ | |
| [LLD] | ==LLVM | _always_ | |
| [flatbuffers] | ~=23.5.26 | WITH_SERIALIZATION=ON | |
| [wabt] | ==1.0.39 | Halide_WASM_BACKEND=wabt | Does not have a stable API; exact version required. |
| [V8] | trunk | Halide_WASM_BACKEND=V8 | Difficult to build. See [WebAssembly.md] |
| [Python] | >=3.10 | WITH_PYTHON_BINDINGS=ON | |
| [pybind11] | ~=2.11.1 | WITH_PYTHON_BINDINGS=ON | |

Halide maintains the following compatibility policy with LLVM: Halide version
N supports LLVM versions N, N-1, and N-2. Our binary distributions
always include the latest N patch at time of release. For most users, we
recommend using a pre-packaged binary release of LLVM rather than trying to
build it yourself.

To build the apps, documentation, and tests, an extended set is needed.

| Dependency | Required when... | Notes |
| ------------------------------- | --------------------------------- | --------------------------------------------------------------------------- |
| [CUDA Toolkit][findcudatoolkit] | building apps/cuda_mat_mul | When compiling Halide pipelines that use CUDA, only the drivers are needed. |
| [Doxygen][finddoxygen] | WITH_DOCS=ON | |
| [Eigen3][eigen3cmake] | building apps/linear_algebra | |
| [libjpeg][findjpeg] | WITH_TESTS=ON | Optionally used by halide_image_io.h and Halide::ImageIO in CMake. |
| [libpng][findpng] | WITH_TESTS=ON | (same as libjpeg) |
| [BLAS][findblas] | building apps/linear_algebra | [ATLAS] and [OpenBLAS] are supported implementations |
| [OpenCL][findopencl] | compiling pipelines with opencl | |

It is best practice to configure your environment so that CMake can find
dependencies without package-specific hints. For instance, if you want CMake to
use a particular version of Python, create a virtual environment and activate it
_before_ configuring Halide. Similarly, the CMAKE_PREFIX_PATH variable can be
set to a local directory where from-source dependencies have been installed.
Carefully consult the [find_package] documentation to learn how the search
procedure works.

If the build still fails to find a dependency, each package provides a bespoke
interface for providing hints and overriding incorrect results. Documentation
for these packages is linked in the table above.

Installing dependencies

vcpkg

Halide has first-class support for using [vcpkg] to manage dependencies. The
list of dependencies and features is contained inside vcpkg.json at the root
of the repository. LLVM and Python must be provided by the system; vcpkg handles
the remaining dependencies (flatbuffers, wabt, pybind11, libjpeg, libpng, etc.).

Halide includes a vcpkg-configuration.json file that automatically configures
[overlay ports][vcpkg-overlay] and overlay triplets. The overlay ports redirect
LLVM and Python to system installations, preventing vcpkg from trying to build
them. This configuration is applied automatically when vcpkg is used from the
Halide source tree.

When using the vcpkg toolchain file, you can set
-DVCPKG_MANIFEST_FEATURES=developer to enable building all test dependencies
(except Doxygen, which is not available on vcpkg).

For convenience, we provide CMake presets that set these flags
appropriately per-platform. They are documented further below.

Windows

On Windows, we recommend using vcpkg to install library dependencies.

To build the documentation, you will need to install [Doxygen]. This can be done
either from the [Doxygen website][doxygen-download] or through [winget]:

shell
$ winget install DimitriVanHeesch.Doxygen

To build the Python bindings, you will need to install Python 3. This should be
done by running the official installer from the [Python website][python]. Be
sure to download the debugging symbols through the installer. This will require
using the "Advanced Installation" workflow. Although it is not strictly
necessary, it is convenient to install Python system-wide on Windows (i.e.
C:\Program Files) because CMake looks at standard paths and registry keys.
This removes the need to manually set the PATH.

Once Python is installed, you can install the Python module dependencies in a
[virtual environment][venv] by running

shell
$ uv sync

from the root of the repository.

Homebrew

On macOS, it is possible to install all dependencies via [Homebrew]:

shell
$ brew install llvm flatbuffers wabt python pybind11 doxygen eigen libpng libjpeg-turbo openblas

The llvm package includes clang, clang-format, and lld, too. To ensure
CMake can find LLVM, set the following cache variable:

shell
$ cmake ... -DHalide_ROOT=/opt/homebrew/opt/llvm

Or use the macOS CMake preset, which does this for you.

Ubuntu / Debian

On Ubuntu you should install the following packages (this includes the Python
module dependencies):

text
$ sudo apt install clang-tools lld llvm-dev libclang-dev liblld-dev \
libpng-dev libjpeg-dev libgl-dev python3-dev python3-numpy \
python3-imageio python3-pybind11 libopenblas-dev libeigen3-dev \
libatlas-base-dev doxygen

Python

When running the Python package, you will need to install additional
dependencies. These are tabulated as constraints in pyproject.toml and
resolved to specific versions in uv.lock. They may be installed by running:

shell
$ uv sync --no-install-project

Building Halide

Basic build

These instructions assume that your working directory is the Halide repository
root.

Windows

If you plan to use the Ninja generator, be sure to launch the developer command
prompt corresponding to your intended environment. Note that whatever your
intended target system (x86, x64, or ARM), you must use the 64-bit _host tools_
because the 32-bit tools run out of memory during the linking step with LLVM.
More information is available from [Microsoft's documentation][msvc-cmd].

You should either open the correct Developer Command Prompt directly or run the
[vcvarsall.bat][vcvarsall] script with the correct argument, i.e. one of the
following:

shell
$ "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
$ "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64_x86
$ "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64_arm

Then, assuming that vcpkg is installed to D:\vcpkg, simply run:

shell
$ cmake -G Ninja -S . -B build --toolchain D:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release
$ cmake --build .\build

Valid values of [CMAKE_BUILD_TYPE][cmake_build_type] are Debug,
RelWithDebInfo, MinSizeRel, and Release. When using a single-configuration
generator (like Ninja) you must specify a build type in the configuration step.

Otherwise, if you wish to create a Visual Studio based build system, you can
configure with:

shell
$ cmake -G "Visual Studio 17 2022" -Thost=x64 -A x64 -S . -B build ^
--toolchain D:\vcpkg\scripts\buildsystems\vcpkg.cmake
$ cmake --build .\build --config Release -j %NUMBER_OF_PROCESSORS%

Because the Visual Studio generator is a _multi-config generator_, you don't set
CMAKE_BUILD_TYPE at configure-time, but instead pass the configuration to the
build (and test/install) commands with the --config flag. More documentation
is available in the [CMake User Interaction Guide][cmake-user-interaction].

The process is similar for 32-bit:

text
cmake -G "Visual Studio 17 2022" -Thost=x64 -A Win32 -S . -B build ^

--toolchain D:\vcpkg\scripts\buildsystems\vcpkg.cmake
cmake --build .\build --config Release -j %NUMBER_OF_PROCESSORS%

In both cases, the -Thost=x64 flag ensures that the correct host tools are
used.

Note: due to limitations in MSBuild, incremental builds using the VS
generators will miss dependencies (including changes to headers in the
src/runtime folder). We recommend using Ninja for day-to-day development and
use Visual Studio only if you need it for packaging.

macOS and Linux

The instructions here are straightforward. Assuming your environment is set up
correctly, just run:

shell
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build build

If you omit -G Ninja, a Makefile-based generator will likely be used instead.
In either case, [CMAKE_BUILD_TYPE][cmake_build_type] must be set to one of the
standard types: Debug, RelWithDebInfo, MinSizeRel, or Release.

CMake Presets

Halide provides several [presets][cmake_presets] to make the above commands more
convenient.

Common presets

These presets do not use vcpkg. They assume that all dependencies are available
via the system (e.g. Homebrew on macOS, APT on Linux).

shell
$ cmake --preset=macOS    # Ninja generator, macOS host build, Homebrew deps
$ cmake --preset=debug # Debug mode, any single-config generator / compiler
$ cmake --preset=release # Release mode, any single-config generator / compiler

Vcpkg presets

The following presets use vcpkg to manage non-LLVM dependencies. LLVM and Python
must be provided by the system.

| Preset | Description |
| --------------- | --------------------------------------------- |
| win32 | Visual Studio 2022 generator, 32-bit build |
| win64 | Visual Studio 2022 generator, 64-bit build |
| macOS-vcpkg | macOS build with vcpkg + Homebrew LLVM |
| debug-vcpkg | Debug build for any single-config generator |
| release-vcpkg | Release build for any single-config generator |

Sanitizer presets

There are also presets to use some Clang sanitizers with the CMake build; at
present, only Fuzzer and ASAN (Address Sanitizer) are supported, and only on
linux-x86-64.

- linux-x64-asan: Use the Address Sanitizer
- linux-x64-fuzzer: Use the Clang fuzzer plugin

To use these, you must build LLVM with additional options:

text
-DLLVM_ENABLE_PROJECTS="clang;lld;clang-tools-extra"
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind"

Valgrind and Intel SDE presets

These presets rerun the ordinary test suite under an external tool, using
CTest's native mechanisms.

The valgrind preset reruns every registered test under [valgrind] via CTest's
built-in [MemCheck][ctest_memcheck] action. It configures a Debug build with
Halide_ENABLE_MEMCHECK=ON, which pulls in the [CTest module][ctest_module] so
that ctest -T memcheck works directly in the build tree (no CDash/dashboard
submission required):

shell
$ cmake --preset valgrind
$ cmake --build --preset valgrind
$ ctest --preset valgrind -T memcheck

The valgrind test preset excludes the no_memcheck label. Valgrind (and its
options and suppressions file) is configured via the standard
MEMORYCHECK_COMMAND, MEMORYCHECK_COMMAND_OPTIONS, and
MEMORYCHECK_SUPPRESSIONS_FILE (test/valgrind.supp) cache variables.

The avx512-cannonlake and avx512-knights-landing presets rerun every test
under [Intel SDE][intel_sde] to emulate an AVX-512-capable CPU on a host that
lacks one. They set
[CMAKE_CROSSCOMPILING_EMULATOR][cmake_crosscompiling_emulator] to
sde -cnl -- and sde -knl -- respectively, so every add_test-registered
executable runs under the emulator automatically. sde must be on your PATH:

shell
$ cmake --preset avx512-cannonlake
$ cmake --build --preset avx512-cannonlake
$ ctest --preset avx512-cannonlake

Build options

Halide reads and understands several options that can configure the build. The
following are the most consequential and control how Halide is actually
compiled.

| Option | Default | Description |
| ---------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| [BUILD_SHARED_LIBS][build_shared_libs] | ON | Standard CMake variable that chooses whether to build as a static or shared library. |
| Halide_LLVM_SHARED_LIBS | OFF | Link to the shared version of LLVM. Not available on Windows. |
| Halide_ENABLE_RTTI | _inherited from LLVM_ | Enable RTTI when building Halide. Recommended to be set to ON |
| Halide_ENABLE_EXCEPTIONS | ON | Enable exceptions when building Halide |
| Halide_TARGET | _empty_ | The default target triple to use for add_halide_library (and the generator tests, by extension) |
| WITH_AUTOSCHEDULERS | ON | Enable building the autoschedulers. Requires BUILD_SHARED_LIBS. |
| WITH_SERIALIZATION | ON | Include experimental Serialization/Deserialization features; also required for the generator compile cache |

The following options are disabled by default when building Halide through the
[ add_subdirectory][add_subdirectory] or [FetchContent][fetchcontent]
mechanisms. They control whether non-essential targets (like tests and
documentation) are built.

| Option | Default | Description |
| ---------------------- | ------- | ---------------------------------------------------------------- |
| WITH_DOCS | OFF | Enable building the documentation via Doxygen |
| WITH_PACKAGING | ON | Include the install() rules for Halide. |
| WITH_PYTHON_BINDINGS | ON | Enable building Python 3 bindings |
| WITH_TESTS | ON | Enable building unit and integration tests |
| WITH_TUTORIALS | ON | Enable building the tutorials |
| WITH_UTILS | ON | Enable building various utilities including the trace visualizer |

The following options are _advanced_ and should not be required in typical
workflows. Generally, these are used by Halide's own CI infrastructure, or as
escape hatches for third-party packagers.

| Option | Default | Description |
| --------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Halide_CCACHE_BUILD | OFF | Use ccache with Halide-recommended settings to accelerate rebuilds. |
| Halide_ENABLE_MEMCHECK | OFF | Pull in the CTest module so ctest -T memcheck reruns the tests under valgrind. See the valgrind preset. |
| Halide_CCACHE_PARAMS | CCACHE_CPP2=yes CCACHE_HASHDIR=yes CCACHE_SLOPPINESS=pch_defines | Options to pass to ccache when using Halide_CCACHE_BUILD. |
| Halide_VERSION_OVERRIDE | ${Halide_VERSION} | Override the VERSION for libHalide. |
| Halide_SOVERSION_OVERRIDE | ${Halide_VERSION_MAJOR} | Override the SOVERSION for libHalide. Expects a positive integer (i.e. not a version). |

The following options control whether to build certain test subsets. They only
apply when WITH_TESTS=ON:

| Option | Default | Description |
| ------------------------- | ---------- | ------------------------------------- |
| WITH_TEST_AUTO_SCHEDULE | ON | enable the auto-scheduling tests |
| WITH_TEST_CORRECTNESS | ON | enable the correctness tests |
| WITH_TEST_ERROR | ON | enable the expected-error tests |
| WITH_TEST_FUZZ | _detected_ | enable the libfuzzer-based fuzz tests |
| WITH_TEST_GENERATOR | ON | enable the AOT generator tests |
| WITH_TEST_PERFORMANCE | ON | enable performance testing |
| WITH_TEST_RUNTIME | ON | enable testing the runtime modules |
| WITH_TEST_WARNING | ON | enable the expected-warning tests |

The following option selects the execution engine for in-process WASM testing:

| Option | Default | Description |
| --------------------- | ------- | ---------------------------------------------------------------------------------------- |
| Halide_WASM_BACKEND | wabt | Select the backend for WASM testing. Can be wabt, V8 or a false value such as OFF. |

Installing

Once built, Halide will need to be installed somewhere before using it in a
separate project. On any platform, this means running the
[cmake --install][cmake-install] command in one of two ways. For a
single-configuration generator (like Ninja), run either:

shell
$ cmake --install ./build --prefix /path/to/Halide-install
$ cmake --install .\build --prefix X:\path\to\Halide-install

For a multi-configuration generator (like Visual Studio) run:

shell
$ cmake --install ./build --prefix /path/to/Halide-install --config Release
$ cmake --install .\build --prefix X:\path\to\Halide-install --config Release

Of course, make sure that you build the corresponding config before attempting
to install it.

Building Halide with pip

Halide also supports installation via the standard Python packaging workflow.
Running pip install . at the root of the repository will build a wheel and
install it into the currently active Python environment.

However, this comes with a few caveats:

1. The environment must be prepared for CMake to find its dependencies. This is
easiest to do by setting either CMAKE_PREFIX_PATH to pre-built dependencies
or by setting CMAKE_TOOLCHAIN_FILE to vcpkg.
2. The build settings are fixed, meaning that wabt is required on non-Windows
systems, flatbuffers is always required, and the Python bindings must be
built.
3. The generated wheel will likely only work on your system. In particular, it
will not be repaired with auditwheel or delocate.

Even so, this is a very good method of installing Halide. It supports both
Python and C++ find_package workflows.

Using ccache with pip builds

Because Python's build infrastructure creates temporary CMake build directories,
simply setting CMAKE_CXX_COMPILER_LAUNCHER to ccache is insufficient to
produce a well-cached build. The following settings should serve as a starting
point to configure your environment (assuming $PWD is the repository root) for
using ccache with pip install ..

shell

Point CMake to ccache


export CMAKE_C_COMPILER_LAUNCHER=ccache
export CMAKE_CXX_COMPILER_LAUNCHER=ccache

Settings to make ccache try to ignore the build directory


export CCACHE_BASEDIR=$PWD
export CCACHE_NOHASHDIR=true

Enable caching of pre-compiled headers and rewrite debug paths


-Xclang -fno-pch-timestamp is only necessary when using Clang (not GCC)


export CCACHE_SLOPPINESS=include_file_ctime,include_file_mtime,pch_defines,time_macros
export CFLAGS="-Xclang -fno-pch-timestamp -fdebug-prefix-map=$PWD=."
export CXXFLAGS="$CFLAGS"

Locate the temporary build beneath $PWD so that CCACHE_BASEDIR works


export TMPDIR=$PWD/build/tmp

If using uv, don't create a temporary venv


export UV_NO_BUILD_ISOLATION=1

See the CCache documentation on [compiling in different directories] and on
using [precompiled headers] for more information about these settings. To check
that ccache is working, run,

shell
$ uv pip install .  # first run, populate cache
Resolved 4 packages in 397ms
Built halide @ file:///Users/areinking/dev/Halide
Prepared 1 package in 29.17s
Installed 1 package in 8ms
+ halide==20.0.0.dev87+gf6c939fd3.d20250724 (from file:///Users/areinking/dev/Halide)
$ ccache -z
Statistics zeroed
$ uv pip install . # second run, reload from cache
Resolved 4 packages in 338ms
Built halide @ file:///Users/areinking/dev/Halide
Prepared 1 package in 10.82s
Uninstalled 1 package in 7ms
Installed 1 package in 6ms
~ halide==20.0.0.dev87+gf6c939fd3.d20250724 (from file:///Users/areinking/dev/Halide)
$ ccache -s
Cacheable calls: 1079 / 1080 (99.91%)
Hits: 1079 / 1079 (100.0%)
Direct: 1079 / 1079 (100.0%)
Preprocessed: 0 / 1079 ( 0.00%)
Misses: 0 / 1079 ( 0.00%)
Uncacheable calls: 1 / 1080 ( 0.09%)
Local storage:
Cache size (GB): 2.2 / 30.0 ( 7.24%)
Hits: 1079 / 1079 (100.0%)
Misses: 0 / 1079 ( 0.00%)

On this test system (an M3 MacBook Pro), the build is three times faster, with a
100% cache hit rate!

[add_subdirectory]: https://cmake.org/cmake/help/latest/command/add_subdirectory.html
[atlas]: http://math-atlas.sourceforge.net/
[brew-cmake]: https://formulae.brew.sh/cask/cmake#default
[build_shared_libs]: https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html
[clang]: https://clang.llvm.org
[cmake-apt]: https://apt.kitware.com/
[cmake-docs]: https://cmake.org/cmake/help/latest/
[cmake-download]: https://cmake.org/download/
[cmake-from-source]: https://cmake.org/install/
[cmake-install]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project
[cmake-user-interaction]: https://cmake.org/cmake/help/latest/guide/user-interaction/index.html#setting-build-variables
[cmake_build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
[cmake_crosscompiling_emulator]: https://cmake.org/cmake/help/latest/variable/CMAKE_CROSSCOMPILING_EMULATOR.html
[cmake_presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[codestylecmake.md]: ./CodeStyleCMake.md
[compiling in different directories]: https://ccache.dev/manual/4.11.3.html#_compiling_in_different_directories
[ctest_memcheck]: https://cmake.org/cmake/help/latest/manual/ctest.1.html#dashboard-client-steps
[ctest_module]: https://cmake.org/cmake/help/latest/module/CTest.html
[doxygen]: https://www.doxygen.nl/index.html
[doxygen-download]: https://www.doxygen.nl/download.html
[eigen3cmake]: https://eigen.tuxfamily.org/dox/TopicCMakeGuide.html
[fetchcontent]: https://cmake.org/cmake/help/latest/module/FetchContent.html
[findblas]: https://cmake.org/cmake/help/latest/module/FindBLAS.html
[findcudatoolkit]: https://cmake.org/cmake/help/latest/module/FindCUDAToolkit.html
[finddoxygen]: https://cmake.org/cmake/help/latest/module/FindDoxygen.html
[findjpeg]: https://cmake.org/cmake/help/latest/module/FindJPEG.html
[findopencl]: https://cmake.org/cmake/help/latest/module/FindOpenCL.html
[findpng]: https://cmake.org/cmake/help/latest/module/FindPNG.html
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
[flatbuffers]: https://github.com/google/flatbuffers
[halidecmakepackage.md]: ./HalideCMakePackage.md
[homebrew]: https://brew.sh
[intel_sde]: https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html
[lld]: https://lld.llvm.org/
[llvm]: https://github.com/llvm/llvm-project
[msvc-cmd]: https://learn.microsoft.com/en-us/cpp/build/building-on-the-command-line
[ninja]: https://ninja-build.org/
[ninja-download]: https://github.com/ninja-build/ninja/releases
[openblas]: https://www.openblas.net/
[pipx]: https://pipx.pypa.io/stable/
[precompiled headers]: https://ccache.dev/manual/4.11.3.html#_precompiled_headers
[pybind11]: https://github.com/pybind/pybind11
[pypi-cmake]: https://pypi.org/project/cmake/
[python]: https://www.python.org/downloads/
[snap store]: https://snapcraft.io/cmake
[v8]: https://v8.dev
[valgrind]: https://valgrind.org/
[vcpkg]: https://github.com/Microsoft/vcpkg
[vcpkg-overlay]: https://learn.microsoft.com/en-us/vcpkg/concepts/overlay-ports
[vcvarsall]: https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line#developer_command_file_locations
[venv]: https://docs.python.org/3/tutorial/venv.html
[vs-cmake-docs]: https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio
[wabt]: https://github.com/WebAssembly/wabt
[webassembly.md]: ./WebAssembly.md
[winget]: https://learn.microsoft.com/en-us/windows/package-manager/winget/

---

Doc/CMakeLists

find_package(Doxygen REQUIRED doxygen)

Halide-specific Doxygen options


set(DOXYGEN_ALPHABETICAL_INDEX NO)
set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
set(DOXYGEN_CASE_SENSE_NAMES NO)
set(DOXYGEN_CLASS_DIAGRAMS NO)
set(DOXYGEN_DISTRIBUTE_GROUP_DOC YES)
set(DOXYGEN_EXAMPLE_PATH "${Halide_SOURCE_DIR}/tutorial")
set(DOXYGEN_EXCLUDE bin)
set(DOXYGEN_EXCLUDE_PATTERNS README.md)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_LOCAL_CLASSES NO)
set(DOXYGEN_FILE_PATTERNS .h .md)
set(DOXYGEN_GENERATE_TREEVIEW YES)
set(DOXYGEN_HIDE_FRIEND_COMPOUNDS YES)
set(DOXYGEN_HIDE_IN_BODY_DOCS YES)
set(DOXYGEN_HIDE_UNDOC_CLASSES YES)
set(DOXYGEN_HIDE_UNDOC_MEMBERS YES)
set(DOXYGEN_JAVADOC_AUTOBRIEF YES)
set(DOXYGEN_MARKDOWN_ID_STYLE GITHUB)
set(DOXYGEN_QT_AUTOBRIEF YES)
set(DOXYGEN_QUIET YES)
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_REFERENCED_BY_RELATION YES)
set(DOXYGEN_REFERENCES_RELATION YES)
set(DOXYGEN_SORT_BY_SCOPE_NAME YES)
set(DOXYGEN_SORT_MEMBER_DOCS NO)
set(DOXYGEN_SOURCE_BROWSER YES)
set(DOXYGEN_STRIP_CODE_COMMENTS NO)

doxygen_add_docs(
doc "doc/" "src/" "test/" ALL
WORKING_DIRECTORY "${Halide_SOURCE_DIR}"
COMMENT "Generate HTML documentation"
)

---

Doc/CodeStyleCMake

Contributing CMake code to Halide

This document specifies the coding standards we adhere to when authoring new
CMake code. If you need directions for building Halide, see
[BuildingHalideWithCMake.md]. If you are looking for Halide's CMake package
documentation, see [HalideCMakePackage.md].

This document is necessary for two major reasons. First, due to its long
history, size, and dedication to backwards compatibility, CMake is _incredibly_
difficult to learn and full of traps. Second, Halide bundles its own LLVM-based
native code generator, which CMake deeply does not expect. This means we
routinely push CMake's build model to its limit.

Therefore, we must be careful to write high-quality CMake code so that it is
clear when CMake's limitations are being tested. While not comprehensive, the
guide outlines the code quality expectations we have as they apply to CMake.

When contributing new CMake code to Halide, keep in mind that the minimum
version is 3.28. Therefore, it is not only possible, but _required_, to use
modern CMake best practices.

- Contributing CMake code to Halide
- General guidelines and best practices
- Prohibited modules list
- FetchContent
- Prohibited commands list
- Prohibited variables list
- Adding tests
- Adding apps

General guidelines and best practices

The following are some common mistakes that lead to subtly broken builds.

- Reading the build directory. While setting up the build, the build
directory should be considered _write only_. Using the build directory as a
read/write temporary directory is acceptable as long as all temp files are
cleaned up by the end of configuration.
- Not using [generator expressions][cmake-genex]. Declarative is better than
imperative and this is no exception. Conditionally adding to a target property
can leak unwanted details about the build environment into packages. Some
information is not accurate or available except via generator expressions,
e.g. the build configuration.
- Using the wrong variable. CMAKE_SOURCE_DIR doesn't always point to the
Halide source root. When someone uses Halide via
[FetchContent][fetchcontent], it will point to _their_ source root instead.
The correct variable is [Halide_SOURCE_DIR][project-name_source_dir]. If you
want to know if the compiler is MSVC, check it directly with the
[MSVC][msvc] variable; don't use [WIN32][win32]. That will be wrong when
compiling with clang on Windows. In most cases, however, a generator
expression will be more appropriate.
- Using directory properties. Directory properties have vexing behavior and
are essentially deprecated from CMake 3.0+. Propagating target properties is
the way of the future.
- Using the wrong visibility. Target properties can be PRIVATE,
INTERFACE, or both (aka PUBLIC). Pick the most conservative one for each
scenario. Refer to the [transitive usage requirements][cmake-propagation] docs
for more information.
- Needlessly expanding variables The [if][cmake_if] and
[foreach][cmake_foreach] commands generally expand variables when provided
by name. Expanding such variables manually can unintentionally change the
behavior of the command. Use foreach (item IN LISTS list) instead of
foreach (item ${list}). Similarly, use if (varA STREQUAL varB) instead of
if ("${varA}" STREQUAL "${varB}") and _definitely_ don't use
if (${varA} STREQUAL ${varB}) since that will fail (in the best case) if
either variable's value contains a semicolon (due to argument expansion).

Prohibited modules list

All deprecated, legacy, and "miscellaneous" (internal) modules are prohibited.
The list of these may be found in the upstream documentation:
https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html#deprecated-modules

FetchContent

At the moment, only one supported module is prohibited: FetchContent. There
are many reasons to avoid its use:

01. It brings third-party CMake code into the build, which can cause all sorts
of issues. In the common case, third-party projects hard-code incompatible
build settings, which are tricky to work around in CMake. In the worst case,
they can set cache variables or directory properties that break the
including project's build. Worse still, those cache variables persist in
CMakeCache.txt even after the dependency is removed or replaced, so the
only reliable fix is a clean reconfigure. Because configuration is expected
to be idempotent, these failures can be difficult to diagnose.
02. It is a poor fit for cross-compilation scenarios that require separate host
and target artifacts. FetchContent inlines the dependency's project into the
including build, so it is configured with the same toolchain as the rest of
that build. For instance, a project may need both the flatbuffers compiler
for the host system and the flatbuffers library for the target system. This
scenario is not supported by FetchContent's population model.
03. It performs network access at configure time. This makes air-gapped and
offline builds awkward. FETCHCONTENT_FULLY_DISCONNECTED=ON only works
after a successful first configure and adds latency to every fresh
configure. Source pinning is also weak: only commit SHAs are truly
immutable, branch and tag refs can be moved server-side, and URL_HASH is
opt-in. Package managers like vcpkg require hash-pinned archives by default
and produce a baseline that can be locked.
04. It does not maintain a persistent source or binary cache outside the build
tree. Populated sources and build products live under the build directory by
default, so deleting the build directory also deletes them. A fresh build
can therefore require another download and rebuild of dependencies, rather
than just rebuilding the top-level project. This also couples dependency
iteration to the parent project: tweaking a dependency's options forces a
parent reconfigure, and the dependency cannot be built or tested in
isolation.
05. The above issues exacerbate diamond dependency problems. Even if a
consistent version happens to be chosen, different intermediate dependencies
along each branch might impose incompatible build settings. For instance,
one project might try to enable an optional feature while another project
disables it.
06. Applying local fixes to dependencies is awkward. FetchContent_Declare's
PATCH_COMMAND runs an arbitrary shell snippet that is hard to review,
version, or attribute. vcpkg ports keep patches as versioned .patch files
alongside the portfile, so they appear in code review and survive upstream
version bumps cleanly.
07. It pollutes the cache and target environment, even when steps are taken to
exclude test and utility targets. This clutters both graphical IDE
interfaces and the diagnostic output of build tools like Ninja (e.g. its
dependency graph and build profiler).
08. Targets created by FetchContent are considered _first-party_ targets,
meaning that special care must be taken when writing installation and
packaging rules. This complexity compounds when simultaneously supporting
other dependency resolution mechanisms that create third-party (i.e.
IMPORTED) targets.
09. It produces no provenance, license, or SBOM metadata. Package managers like
vcpkg and Conan emit machine-readable manifests of versions, licenses, and
source hashes that compliance tooling can consume. FetchContent emits
nothing, so every audit becomes a manual exercise.
10. FetchContent requests can be intercepted by a Dependency Provider which can
only be chosen by the top-level project. That means code that appears to
vendor a specific source tree can instead be redirected to some other
dependency resolution mechanism, such as a package manager. This makes the
resulting targets and build settings less predictable, and it compounds the
first-party versus imported-target packaging issues described above.

After broader approval, third-party dependencies must be consumed with
find_package. This also lets packagers and distributors substitute a system or
pre-built copy, rather than forcing every downstream to rebuild dependencies
from source. We use vcpkg in CI to manage our dependencies. If vcpkg lacks a
port, you must write a custom port in cmake/vcpkg-ports (for the main Halide
build) or apps/vcpkg/ports (for the apps).

Prohibited commands list

As mentioned above, using directory properties is brittle, and they are
therefore _not allowed_. The following functions may not appear in any new CMake
code.

| Command | Alternative |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| add_compile_definitions | Use [target_compile_definitions][target_compile_definitions] |
| add_compile_options | Use [target_compile_options][target_compile_options] |
| add_definitions | Use [target_compile_definitions][target_compile_definitions] |
| add_link_options | Use [target_link_options][target_link_options], but prefer not to use either |
| include_directories | Use [target_include_directories][target_include_directories] |
| link_directories | Use [target_link_libraries][target_link_libraries] |
| link_libraries | Use [target_link_libraries][target_link_libraries] |
| remove_definitions | [Generator expressions][cmake-genex] in [target_compile_definitions][target_compile_definitions] |
| set_directory_properties | Use (cache) variables or target properties |
| set_property(DIRECTORY) | Use (cache) variables or target properties (custom properties excluded, but require justification) |
| target_link_libraries(target lib) | Use [target_link_libraries][target_link_libraries] _with a visibility specifier_ (eg. PRIVATE) |

As an example, it was once common practice to write code similar to this:

cmake

WRONG: do not do this


include_directories(include)
add_library(my_lib source1.cpp ..)

However, this has two major pitfalls. First, it applies to _all_ targets created
in that directory, even those before the call to include_directories and those
created in [include()][include]-ed CMake files. As CMake files get larger and
more complex, this behavior gets harder to pinpoint. This is particularly vexing
when using the link_libraries or add_definitions commands. Second, this form
does not provide a way to _propagate_ the include directory to consumers of
my_lib. The correct way to do this is:

cmake

CORRECT


add_library(my_lib source1.cpp ...)
target_sources(
my_lib
PUBLIC
FILE_SET HEADERS
BASE_DIRS include
FILES include/header1.h
)

This is better in many ways. It only affects the target in question. It
propagates the include path to the targets linking to it (via PUBLIC). It also
correctly exports the host-filesystem-specific include path when installing or
packaging the target and installs the headers themselves, too.

If common properties need to be grouped together, use an INTERFACE target
(better) or write a function (worse).

There are also several functions that are disallowed for other reasons:

| Command | Reason | Alternative |
| ------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------ |
| aux_source_directory | Interacts poorly with incremental builds and Git | List source files explicitly |
| build_command | CTest internal function | Use CTest build-and-test mode via [CMAKE_CTEST_COMMAND][cmake_ctest_command] |
| cmake_host_system_information | Usually misleading information. | Inspect [toolchain][cmake-toolchains] variables and use generator expressions. |
| cmake_policy(... OLD) | OLD policies are deprecated by definition. | Instead, fix the code to work with the new policy. |
| create_test_sourcelist | We use our own unit testing solution | See the adding tests section. |
| define_property | Adds unnecessary complexity | Use a cache variable. Exceptions under special circumstances. |
| enable_language | Halide is C/C++ only | [FindCUDAToolkit][findcudatoolkit], appropriately guarded. |
| file(GLOB ...) | Interacts poorly with incremental builds and Git | List source files explicitly. Allowed if not globbing for source files. |
| fltk_wrap_ui | Halide does not use FLTK | None |
| include_external_msproject | Halide must remain portable | Write a CMake package config file or find module. |
| include_guard | Use of recursive inclusion is not allowed | Write (recursive) functions. |
| include_regular_expression | Changes default dependency checking behavior | None |
| load_cache | Superseded by [ExternalProject][externalproject] | Write a vcpkg port or present a case for an exception. |
| macro | CMake macros are not hygienic and are therefore error-prone | Use functions instead. |
| site_name | Privacy: do not want leak host name information | Provide a cache variable, generate a unique name. |
| variable_watch | Debugging helper | None. Not needed in production. |

Do not introduce new dependencies without broader approval. Once approved, add
dependencies to vcpkg.json or create a custom port, and consume them with
[find_package][find_package] rather than FetchContent.

Prohibited variables list

Any variables that are specific to languages that are not enabled should, of
course, be avoided. But of greater concern are variables that are easy to misuse
or should not be overridden for our end-users. The following (non-exhaustive)
list of variables shall not be used in code merged into main.

| Variable | Reason | Alternative |
| ------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| CMAKE_ROOT | Code smell | Rely on find_package search options; include HINTS if necessary |
| CMAKE_DEBUG_TARGET_PROPERTIES | Debugging helper | None |
| CMAKE_FIND_DEBUG_MODE | Debugging helper | None |
| CMAKE_RULE_MESSAGES | Debugging helper | None |
| CMAKE_VERBOSE_MAKEFILE | Debugging helper | None |
| CMAKE_BACKWARDS_COMPATIBILITY | Deprecated | None |
| CMAKE_BUILD_TOOL | Deprecated | ${CMAKE_COMMAND} --build or [CMAKE_MAKE_PROGRAM][cmake_make_program] (but see below) |
| CMAKE_CACHEFILE_DIR | Deprecated | [CMAKE_BINARY_DIR][cmake_binary_dir], but see below |
| CMAKE_CFG_INTDIR | Deprecated | $<CONFIG>, $<TARGET_FILE:..>, target resolution of [add_custom_command][add_custom_command], etc. |
| CMAKE_CL_64 | Deprecated | [CMAKE_SIZEOF_VOID_P][cmake_sizeof_void_p] |
| CMAKE_COMPILER_IS_* | Deprecated | [CMAKE_<LANG>_COMPILER_ID][cmake_lang_compiler_id] |
| CMAKE_HOME_DIRECTORY | Deprecated | [CMAKE_SOURCE_DIR][cmake_source_dir], but see below |
| CMAKE_DIRECTORY_LABELS | Directory property | None |
| CMAKE_BUILD_TYPE | Only applies to single-config generators. | $<CONFIG> |
| CMAKE__FLAGS (w/o _INIT) | User-only | Write a [toolchain][cmake-toolchains] file with the corresponding _INIT variable |
| CMAKE_COLOR_MAKEFILE | User-only | None |
| CMAKE_ERROR_DEPRECATED | User-only | None |
| CMAKE_CONFIGURATION_TYPES | We only support the four standard build types | None |

Of course feel free to insert debugging helpers _while developing_ but please
remove them before review. Finally, the following variables are allowed, but
their use must be motivated:

| Variable | Reason | Alternative |
| ---------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| [CMAKE_SOURCE_DIR][cmake_source_dir] | Points to global source root, not Halide's. | [Halide_SOURCE_DIR][project-name_source_dir] or [PROJECT_SOURCE_DIR][project_source_dir] |
| [CMAKE_BINARY_DIR][cmake_binary_dir] | Points to global build root, not Halide's | [Halide_BINARY_DIR][project-name_binary_dir] or [PROJECT_BINARY_DIR][project_binary_dir] |
| [CMAKE_MAKE_PROGRAM][cmake_make_program] | CMake abstracts over differences in the build tool. | Prefer CTest's build and test mode or CMake's --build mode |
| [CMAKE_CROSSCOMPILING][cmake_crosscompiling] | Often misleading. | Inspect relevant variables directly, eg. [CMAKE_SYSTEM_NAME][cmake_system_name] |
| [BUILD_SHARED_LIBS][build_shared_libs] | Could override user setting | None, but be careful to restore value when overriding for a dependency |

Any use of these functions or variables will block a PR.

Adding tests

When adding a file to any of the folders under test, be aware that CI expects
that every .c and .cpp appears in the CMakeLists.txt file _on its own
line_, possibly as a comment. This is to avoid globbing and also to ensure that
added files are not missed.

For most test types, it should be as simple as adding to the existing lists.
Generator tests are trickier, but following the existing examples is a safe way
to go.

Adding apps

If you're contributing a new app to Halide: great! Thank you! There are a few
guidelines you should follow when writing a new app.

- Write the app as if it were a top-level project. You should call
find_package(Halide) and set the C++ version to 11.
- Call [enable_testing()][enable_testing] and add a small test that runs the
app.
- Don't assume your app will have access to a GPU. Write your schedules to be
robust to varying buildbot hardware.
- Don't assume your app will be run on a specific OS, architecture, or bitness.
Write your apps to be robust (ideally efficient) on all supported platforms.
- If you rely on any additional packages, don't include them as REQUIRED,
instead test to see if their targets are available and, if not, call
return() before creating any targets. In this case, print a
message(STATUS "[SKIP] ..."), too.
- Look at the existing apps for examples.
- Test your app with ctest before opening a PR. Apps are built as part of the
test, rather than the main build.

[add_custom_command]: https://cmake.org/cmake/help/latest/command/add_custom_command.html
[buildinghalidewithcmake.md]: ./BuildingHalideWithCMake.md
[build_shared_libs]: https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html
[cmake-genex]: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html
[cmake-propagation]: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#transitive-usage-requirements
[cmake-toolchains]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html
[cmake_binary_dir]: https://cmake.org/cmake/help/latest/variable/CMAKE_BINARY_DIR.html
[cmake_crosscompiling]: https://cmake.org/cmake/help/latest/variable/CMAKE_CROSSCOMPILING.html
[cmake_ctest_command]: https://cmake.org/cmake/help/latest/variable/CMAKE_CTEST_COMMAND.html
[cmake_foreach]: https://cmake.org/cmake/help/latest/command/foreach.html
[cmake_if]: https://cmake.org/cmake/help/latest/command/if.html
[cmake_lang_compiler_id]: https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
[cmake_make_program]: https://cmake.org/cmake/help/latest/variable/CMAKE_MAKE_PROGRAM.html
[cmake_sizeof_void_p]: https://cmake.org/cmake/help/latest/variable/CMAKE_SIZEOF_VOID_P.html
[cmake_source_dir]: https://cmake.org/cmake/help/latest/variable/CMAKE_SOURCE_DIR.html
[cmake_system_name]: https://cmake.org/cmake/help/latest/variable/CMAKE_SYSTEM_NAME.html
[enable_testing]: https://cmake.org/cmake/help/latest/command/enable_testing.html
[externalproject]: https://cmake.org/cmake/help/latest/module/ExternalProject.html
[fetchcontent]: https://cmake.org/cmake/help/latest/module/FetchContent.html
[findcudatoolkit]: https://cmake.org/cmake/help/latest/module/FindCUDAToolkit.html
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
[halidecmakepackage.md]: ./HalideCMakePackage.md
[include]: https://cmake.org/cmake/help/latest/command/include.html
[msvc]: https://cmake.org/cmake/help/latest/variable/MSVC.html
[project-name_binary_dir]: https://cmake.org/cmake/help/latest/variable/PROJECT-NAME_BINARY_DIR.html
[project-name_source_dir]: https://cmake.org/cmake/help/latest/variable/PROJECT-NAME_SOURCE_DIR.html
[project_binary_dir]: https://cmake.org/cmake/help/latest/variable/PROJECT_BINARY_DIR.html
[project_source_dir]: https://cmake.org/cmake/help/latest/variable/PROJECT_SOURCE_DIR.html
[target_compile_definitions]: https://cmake.org/cmake/help/latest/command/target_compile_definitions.html
[target_compile_options]: https://cmake.org/cmake/help/latest/command/target_compile_options.html
[target_include_directories]: https://cmake.org/cmake/help/latest/command/target_include_directories.html
[target_link_libraries]: https://cmake.org/cmake/help/latest/command/target_link_libraries.html
[target_link_options]: https://cmake.org/cmake/help/latest/command/target_link_options.html
[win32]: https://cmake.org/cmake/help/latest/variable/WIN32.html

---

Doc/CustomRuntimes

Custom Runtime Prefixes

Every Halide runtime exposes a set C ABI symbols consisting of common host
functions -- halide_malloc, halide_free, halide_error,
halide_do_par_for, and so on -- as well as target specific methods which
handle device functionality -- halide_cuda_, halide_direct3dcompute_,
halide_metal_, halide_opencl_, halide_vulkan_*, etc. This C ABI also
consists of mutable process-global state stored as global variables (e.g. the
installed custom allocator, the thread pool, the memoization cache, the
profiler, etc.). This works well when a program contains a single Halide
runtime, but it becomes a problem when a program must contain more than one.

Two independently produced components -- say, two libraries that each embed
their own AOT-compiled Halide pipelines and runtime -- both define
halide_malloc and both carry the same runtime state globals. When they are
linked into one process the linker collapses those duplicate (weak/linkonce)
symbols into a single copy, so the two components silently share one runtime.
Installing a custom allocator or error handler for one then affects the other,
and the two cannot be given different runtime configurations at all.

Runtime prefixes solve this by letting you rename the runtime's symbols with a
prefix of your choosing, so that each component carries its own, independent
runtime. This document describes the feature, the scopes it exposes, and how to
use it from C++, from the GenGen command line, and from CMake.

Scopes

Rather than a single prefix, three independent prefixes are available, one per
"visibility" of a runtime symbol. They correspond to the enum
Halide::RuntimeLinkage:

- Import -- the names a generated kernel uses to call into the runtime.
When a pipeline is compiled with no_runtime, its calls to halide_malloc
and friends are left as external references; the import prefix renames those
references so they resolve against a matching prefixed runtime at link time.

- Export -- the names a runtime library makes externally visible. When you
compile a standalone runtime, the export prefix renames the public C ABI it
defines (halide_malloc becomes, e.g., my_prefix_malloc).

- Internal -- the names used within the runtime library. This covers the
runtime's own C++ symbols in the Halide::Runtime::Internal namespace,
including the mutable state globals. Renaming these is what actually keeps two
prefixed runtimes' state independent; without it the state globals would still
collide even if the public ABI were renamed.

Each prefix is optional and they are set independently. A prefix replaces the
leading halide_ of the C ABI names; because the internal C++ symbols contain
no halide_ to replace, the internal prefix is prepended to them.

The pipeline's own entry points (the function you called compile_to_* on, its
_argv wrapper, and its metadata) are never renamed, and neither are C library
symbols.

How the pieces fit together

For a component to link and run, the prefixes of its kernel and its runtime must
agree:

- The kernel's import prefix must equal the runtime's export prefix, so
the kernel's calls resolve to the runtime's definitions.
- The kernel's internal prefix must equal the runtime's internal prefix,
for the same reason applied to any internal symbols they share.

Different components use different prefixes from one another; that is what
keeps them isolated. A typical setup for two components A and B is:

| Component | Runtime (export, internal) | Kernel (import, internal) |
| --------- | ------------------------------ | ----------------------------- |
| A | A_, A_internal_ | A_, A_internal_ |
| B | B_, B_internal_ | B_, B_internal_ |

When A and B are linked into one process, A_malloc and B_malloc (and their
respective state globals) are distinct symbols, so each pipeline uses its own
runtime and their state stays independent.

Backends

Both the LLVM and the C backend honor runtime prefixes.

- The LLVM backend renames the symbols directly on the generated module: a
definition takes the export prefix, a kernel-called external declaration takes
the import prefix, and the runtime's internal C++ symbols (including its state
globals) take the internal prefix.
- The C backend emits a kernel that calls into an external runtime, so only
the import prefix applies to it. It renames the runtime's C ABI functions with
a block of #define halide_x <prefix>x at the top of the generated source;
the preprocessor rewrites the runtime's function declarations and every call
site consistently, while leaving types (halide_buffer_t), typedefs
(halide_malloc_t), and enum values untouched. These #defines are emitted
only into the generated C/C++ source, never the header, so several prefixed
headers can still be included together.

Limitations

- Runtime prefixes are not currently supported for JIT, but it's something we
would like to support in the future. The difficulty is that the JIT resolves
runtime calls against a single process-global shared runtime which already
exists. So, for now, requesting runtime prefixes on a JIT target will emit an
error.

Usage from C++

The prefixes are described by a Halide::RuntimePrefixParams, which wraps a
std::map<RuntimeLinkage, std::string>.

To compile a standalone runtime with a set of prefixes, pass the map to
compile_standalone_runtime:

``c++
#include "Halide.h"
using namespace Halide;

Target target = get_host_target();

std::map<RuntimeLinkage, std::string> ns = {
{RuntimeLinkage::Export, "my_prefix_"},
{RuntimeLinkage::Internal, "my_prefix_internal_"},
};

compile_standalone_runtime("my_prefix_runtime.o", target, ns);

text
To compile a pipeline whose runtime calls match that runtime, apply the
matching prefixes and compile with
no_runtime:
c++
Func consumer = / ... /;
Pipeline p(consumer);

Target target = get_host_target().with_feature(Target::NoRuntime);

p.apply_runtime_prefixes(target, RuntimePrefixParams({
{RuntimeLinkage::Import, "my_prefix_"},
{RuntimeLinkage::Internal, "my_prefix_internal_"},
}));

p.compile_to_module({}, "my_pipeline", target)
.compile({{OutputFileType::object, "my_pipeline.o"},
{OutputFileType::c_header, "my_pipeline.h"}});

text
apply_runtime_prefixes records the prefixes on the pipeline; any subsequent
compile_to_* for a non-JIT target then applies them. Calling it with a JIT
target raises a
Halide::CompileError.

Inside a Generator, the prefixes travel on the GeneratorContext as
RuntimePrefixParams and are applied automatically when the generator's module
is built; in practice these are supplied through the command line or CMake,
described below.

Usage from the GenGen command line

The prefixes are ordinary generator parameters named runtime_prefixes.import,
runtime_prefixes.export, and runtime_prefixes.internal. Any of them may be
omitted.

To emit a prefixed standalone runtime (the -r output):


./my_generator -r my_prefix_runtime -o . -e object \
target=host \
runtime_prefixes.export=my_prefix_ \
runtime_prefixes.internal=my_prefix_internal_
text
To emit a matching pipeline with no_runtime:

./my_generator -g my_generator -f my_pipeline -o . -e object,c_header \
target=host-no_runtime \
runtime_prefixes.import=my_prefix_ \
runtime_prefixes.internal=my_prefix_internal_
text

Usage from CMake

add_halide_runtime accepts a PARAMS argument that is forwarded to the
runtime generator, and
add_halide_library already forwards PARAMS to the
pipeline generator. Give a runtime its export/internal prefixes, and give each
library its matching import/internal prefixes together with
USE_RUNTIME:

cmake
add_halide_generator(my_pipeline.generator SOURCES my_pipeline_generator.cpp)

A runtime with the "my_prefix_" prefix.


add_halide_runtime(
my_prefix_runtime
PARAMS runtime_prefixes.export=my_prefix_ runtime_prefixes.internal=my_prefix_internal_
)

A pipeline that links against it. add_halide_library() compiles with


no_runtime automatically when USE_RUNTIME is given.


add_halide_library(
my_pipeline
FROM my_pipeline.generator
GENERATOR my_pipeline
USE_RUNTIME my_prefix_runtime
PARAMS runtime_prefixes.import=my_prefix_ runtime_prefixes.internal=my_prefix_internal_
)
text
Repeating this with a second, differently-prefixed runtime and library produces
two components that can be linked into the same program without their runtimes
colliding. For a complete, working example -- three variants of one pipeline,
each with its own runtime, linked into a single test that checks their state
stays independent -- see
test/generator/runtime_prefixes_iso_aottest.cpp and
its CMake wiring in
test/generator/CMakeLists.txt.

Verifying the result

The renaming happens on the symbols of the emitted object, so you can confirm it
with
nm. A stock runtime exports halide_malloc:


$ nm my_prefix_runtime.o | grep malloc
0000000000000000 T my_prefix_malloc
text
and its internal state globals are prefixed as well:

$ nm my_prefix_runtime.o | grep custom_malloc
0000000000000000 D my_prefix_internal__ZN6Halide7Runtime8Internal13custom_mallocE
text
A no_runtime pipeline object correspondingly imports the renamed symbols
rather than the stock
halide_ ones:

$ nm my_pipeline.o | grep malloc
U my_prefix_malloc
text
---

Doc/FuzzTesting

Fuzz testing

Halide has a set of fuzz-testing harnesses in test/fuzz/ that can find tricky
edge cases and bugs that are hard to catch with a regular unit-testing suite.
The fuzz tests are built on a small in-tree framework (
fuzz_helpers.h,
halide_fuzz_main.h) that abstracts over two backends:

- stdlib backend — uses std::mt19937_64 seeded from std::random_device.
Works with any standard C++ toolchain; no special compiler flags or external
runtime libraries required. Each run prints its seed so failures are
reproducible by re-running with that seed.
- libfuzzer backend — uses
libFuzzer for coverage-guided
fuzzing. Requires a Clang toolchain built with
-fsanitize=fuzzer support.

The stdlib backend is the default for regular development builds. The libfuzzer
backend is enabled automatically when the build system detects
-fsanitize=fuzzer (or equivalent) linker flags.

Building fuzz tests

Standard build (stdlib backend)

No special flags are needed. The fuzz tests build as part of any normal CMake
configuration that has
WITH_TEST_FUZZ=YES:


cmake -B build <your-usual-options> -DWITH_TEST_FUZZ=YES
cmake --build build -j$(nproc) --target test_fuzz
text

libfuzzer backend (coverage-guided fuzzing)

Use one of the fuzzing CMake presets, which set the necessary
-fsanitize=fuzzer[-no-link] flags across the entire build:

Linux:


cmake -B build --preset linux-x64-fuzzer -DHalide_LLVM_ROOT=/path/to/llvm-install
cmake --build build -j$(nproc)
text
macOS (Homebrew LLVM):

cmake -B build --preset macOS-fuzz
cmake --build build -j$(nproc)
text
The LLVM install used for libfuzzer builds must include the compiler-rt
runtime (i.e. built with
-DLLVM_ENABLE_RUNTIMES="compiler-rt"). Not all
prebuilt LLVM installs include this; you may need to build LLVM from source or
use Homebrew's LLVM package on macOS.

Running fuzz tests

stdlib backend

Run a fuzz harness directly:


./build/test/fuzz/fuzz_simplify
text
By default this runs 10,000 iterations, printing the seed before each one:

Seed: 12345678901234567
Seed: 98765432109876543
...
text
Control the number of iterations with -runs=N:

./build/test/fuzz/fuzz_simplify -runs=100000
text
Run all fuzz tests via CTest (1,000 iterations each, exit-code–based pass/fail):

ctest --test-dir build -L fuzz
text

libfuzzer backend

After building with a fuzzing preset, run the harness with no arguments to start
coverage-guided fuzzing on a single core:


./build/test/fuzz/fuzz_simplify
text
To persist the corpus between runs (recommended):

mkdir -p fuzz_simplify_corpus
./build/test/fuzz/fuzz_simplify fuzz_simplify_corpus
text
To fuzz in parallel across all available cores:

./build/test/fuzz/fuzz_simplify fuzz_simplify_corpus -fork=$(nproc)
text

Reproducing failures

stdlib backend

When a run fails, rerun with the seed that was printed just before the crash:


./build/test/fuzz/fuzz_simplify 12345678901234567
text
This performs a single deterministic iteration with that seed.

libfuzzer backend

libFuzzer writes a crash-input file on failure:


crash-<some_random_hash>
text
Replay it by passing it as the first argument:

./build/test/fuzz/fuzz_simplify crash-<some_random_hash>
text

Adding new fuzz tests

All fuzz tests use the FUZZ_TEST macro defined in fuzz_helpers.h. This macro
generates the correct entry point for whichever backend is active —
LLVMFuzzerTestOneInput for libfuzzer or a main that calls
Halide::fuzz_main for the stdlib backend.

A minimal fuzz test looks like this:

cpp
#include "fuzz_helpers.h"

FUZZ_TEST(my_test, Halide::FuzzingContext &fuzz) {
int x = fuzz.ConsumeIntegralInRange<int>(0, 100);
bool b = fuzz.ConsumeBool();
my_function(x, b);
return 0;
}

text
FuzzingContext wraps FuzzedDataProvider (from libfuzzer's
compiler-rt/include/fuzzer/FuzzedDataProvider.h) and re-implements its
interface on top of
std::mt19937_64 for the stdlib backend, so the same API
works with both backends. Key methods:

- ConsumeIntegral<T>() — random value of type T
-
ConsumeIntegralInRange<T>(min, max) — random value in [min, max]
-
ConsumeBool() — random boolean
-
PickValueInArray(arr) — random element from an array or initializer list
-
PickValueInVector(vec) — random element from a std::vector

For richer examples, see test/fuzz/simplify.cpp and
test/fuzz/random_expr_generator.h.

To register a new fuzz test with CMake, add it to the SOURCES list in
test/fuzz/CMakeLists.txt.

Other useful materials

- The official libfuzzer docs
- The libfuzzer tutorial
- FuzzedDataProvider reference

---

Doc/GeneratorCache

Generator compile cache

Overview

Building a large app suite runs one generator process per (generator, target)
pair, and most of that time is spent recompiling pipelines that haven't actually
changed since the last build. The generator compile cache is an opt-in,
content-addressed cache that lets
execute_generator skip that work: when the
inputs to a generator invocation are unchanged, the previously emitted artifacts
(object files, headers, static libraries, etc.) are copied back into place
instead of being recompiled.

The cache is inert unless you opt in by setting the HL_CACHE_DIR environment
variable (or the corresponding
Halide_CACHE_DIR CMake variable, see below) to
a directory. When it is unset, none of this machinery runs and build output is
identical to a Halide with no cache support at all.

The feature also requires that Halide was built with WITH_SERIALIZATION=ON
(the default), since the cache key is only sound if it includes a serialized
snapshot of the pipeline being compiled. If
HL_CACHE_DIR is set but
serialization support isn't compiled in, Halide prints a one-time warning and
disables caching for that build.

What gets cached

Both compilation paths that execute_generator can take are cached
independently:

- Compiling a generator (the compile_multitarget path), once per
-g/-o/target=... invocation.
- Compiling the standalone runtime (the
-r/GenRT path).

Cache key

A cache entry is addressed by a SHA-256 digest that mixes in everything that can
affect the emitted files, so that two invocations agreeing on all of the
following are guaranteed to produce identical outputs:

- Compiler identity: a fingerprint of the running libHalide (or, if
statically linked, the generator executable itself). This is normally the
linker-assigned build ID, read directly from the already-loaded image (Mach-O
LC_UUID on macOS, ELF .note.gnu.build-id on Linux, the PE debug
directory's CodeView/PDB GUID on Windows) so that rebuilding Halide
invalidates the cache without hashing the ~32 MB binary on every run. If no
build ID is available (e.g.
--build-id=none, or a Windows image built
without debug info), it falls back to hashing the whole binary.
- The generator name, output types, build mode (normal vs. gradient), and
the fully-resolved generator-param settings (
target= is handled
separately, below).
- The target(s) and per-target suffixes being compiled.
- The serialized pipeline for each target: the generator is instantiated and
its (pre-autoschedule) algorithm and schedule are serialized via
Serialization.h and folded into the key. This is what makes the key sound
for source edits — the actual algorithm and schedule are captured, so editing
a generator's
generate()/schedule() invalidates the cache even when none
of its command-line params changed. Autoscheduling itself is deliberately
excluded so that computing a cache key never requires running a (potentially
expensive) autoscheduler.
- The contents of any
-p plugins (e.g. autoschedulers), since they affect
codegen but live outside libHalide.

If the compiler identity can't be determined, or the pipeline can't be
serialized (for example, a generator that throws while building), the invocation
falls back to compiling normally without touching the cache.

Using it from the command line

Set HL_CACHE_DIR before invoking a generator executable directly:

shell
$ export HL_CACHE_DIR=$HOME/.cache/halide-generators
$ ./my_generator -g my_pipeline -o . target=host
text
The first invocation compiles and populates the cache; subsequent invocations
with the same generator, target, params, plugins, and (unchanged) pipeline
source restore the outputs instead of recompiling.

Using it from CMake

The shipped CMake helpers (add_halide_library, add_halide_runtime, etc.)
read the
Halide_CACHE_DIR cache variable — which defaults to
$ENV{HL_CACHE_DIR} — and, when it's set, wrap every generator/GenRT invocation
so the value reaches the generator process:

shell
$ cmake -B build -DHalide_CACHE_DIR=$HOME/.cache/halide-generators
$ cmake --build build
text
When Halide_CACHE_DIR is empty, the emitted build commands are byte-for-byte
identical to a build with no cache configured. See
Halide_CACHE_DIR in the CMake package docs.

Cache maintenance

Entries are installed atomically (staged in a temp directory under the cache
root, then renamed into place), so concurrent builds never observe a partial
entry.

The cache is pruned opportunistically after each store to honor two optional
size/age limits, read from the environment:

- HL_CACHE_MAX_SIZE — maximum total size of the cache, evicting the
least-recently-used entries first once exceeded. Accepts a plain byte count or
a
K/M/G suffix (powers of 1024). Defaults to 1G.
-
HL_CACHE_MAX_AGE — if set, entries not used within this many seconds are
evicted regardless of total size.

Because a large parallel build runs many generator processes that each store an
entry, pruning is debounced to at most once every 60 seconds (via a
.last_prune stamp in the cache directory), so the size limit is a soft cap
that a burst of concurrent stores can briefly exceed. Pruning across processes
is additionally serialized by a non-blocking advisory lock; if another process
already holds it, a store simply skips its own prune pass rather than waiting.

To clear the cache entirely, just delete the directory named by HL_CACHE_DIR.

---

Doc/HalideCMakePackage

Using Halide from your CMake build

This is a detailed guide to building your own Halide programs with the official
CMake package. If you need directions for building Halide, see
[BuildingHalideWithCMake.md]. If you are looking for Halide's CMake coding
guidelines, see [CodeStyleCMake.md].

This document assumes some basic familiarity with CMake but tries to be explicit
in all its examples. To learn more about CMake, consult the
[documentation][cmake-docs] and engage with the community on the
[CMake Discourse][cmake-discourse].

- Using Halide from your CMake build
- A basic CMake project
- JIT mode
- AOT mode
- Autoschedulers
- RunGenMain
- Halide package documentation
- Components
- Variables
- Imported targets
- Functions
-
add_halide_generator
-
add_halide_library
-
add_halide_python_extension_library
-
add_halide_runtime
- Cross compiling
- Use
add_halide_generator
- Use a super-build
- Use
ExternalProject directly
- Use an emulator or run on device
- Bypass CMake

A basic CMake project

There are two main ways to use Halide in your application: as a JIT compiler
for dynamic pipelines or an ahead-of-time (AOT) compiler for static
pipelines. CMake provides robust support for both use cases.

No matter how you intend to use Halide, you will need some basic CMake
boilerplate.

cmake
cmake_minimum_required(VERSION 3.28)
project(HalideExample)

set(CMAKE_CXX_STANDARD 17) # or newer
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)

find_package(Halide REQUIRED)

text
The [cmake_minimum_required][cmake_minimum_required] command is required to be
the first command executed in a CMake program. It disables all the deprecated
behavior ("policies" in CMake lingo) from earlier versions. The
[
project][project] command sets the name of the project (and accepts arguments
for versioning, language support, etc.) and is required by CMake to be called
immediately after setting the minimum version.

The next three variables set the project-wide C++ standard. The first,
[
CMAKE_CXX_STANDARD][cmake_cxx_standard], simply sets the standard version.
Halide requires at least C++17. The second,
[
CMAKE_CXX_STANDARD_REQUIRED][cmake_cxx_standard_required], tells CMake to
fail if the compiler cannot provide the requested standard version. Lastly,
[
CMAKE_CXX_EXTENSIONS][cmake_cxx_extensions] tells CMake to disable
vendor-specific extensions to C++. This is not necessary to simply use Halide,
but we do not allow such extensions in the Halide repo.

Finally, we use [find_package][find_package] to locate Halide on your system.
When using the pip package on Linux and macOS, CMake's
find_package command
should find Halide as long as you're in the same virtual environment you
installed it in. On Windows, you will need to add the virtual environment root
directory to [
CMAKE_PREFIX_PATH][cmake_prefix_path]:

shell
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=%VIRTUAL_ENV%
text
If find_package cannot find Halide, set CMAKE_PREFIX_PATH to the Halide
installation directory.

JIT mode

To use Halide in JIT mode (like the [tutorials][halide-tutorials] do, for
example), you can simply link to
Halide::Halide.

cmake

... same project setup as before ...


add_executable(my_halide_app main.cpp)
target_link_libraries(my_halide_app PRIVATE Halide::Halide)
text
Then Halide.h will be available to your code and everything should just work.
That's it!

AOT mode

Using Halide in AOT mode is more complicated so we'll walk through it step by
step. Note that this only applies to Halide generators, so it might be useful to
re-read the [tutorial on generators][halide-generator-tutorial]. Assume (like in
the tutorial) that you have a source file named
my_generators.cpp and that in
it, you have generator classes
MyFirstGenerator and MySecondGenerator with
registered names
my_first_generator and my_second_generator respectively.

Then the first step is to add a generator executable to your build:

cmake

... same project setup as before ...


add_halide_generator(my_generators SOURCES my_generators.cpp)
text
Using the generator executable, we can add a Halide library corresponding to
MyFirstGenerator.
cmake

... continuing from above


add_halide_library(my_first_generator FROM my_generators)
text
This will create a static library target in CMake that corresponds to the output
of running your generator. The second generator in the file requires generator
parameters to be passed to it. These are also easy to handle:
cmake

... continuing from above


add_halide_library(my_second_generator FROM my_generators
PARAMS parallel=false scale=3.0 rotation=ccw output.type=uint16)
text
Adding multiple configurations is easy, too:
cmake

... continuing from above


add_halide_library(my_second_generator_2 FROM my_generators
GENERATOR my_second_generator
PARAMS scale=9.0 rotation=ccw output.type=float32)

add_halide_library(my_second_generator_3 FROM my_generators
GENERATOR my_second_generator
PARAMS parallel=false output.type=float64)

text
Here, we had to specify which generator to use (my_second_generator) since it
uses the target name by default. The functions in these libraries will be named
after the target names,
my_second_generator_2 and my_second_generator_3, by
default, but it is possible to control this via the
FUNCTION_NAME parameter.

Each one of these targets, <GEN>, carries an associated <GEN>.runtime
target, which is also a static library containing the Halide runtime. It is
transitively linked through
<GEN> to targets that link to <GEN>. On an
operating system like Linux, where weak linking is available, this is not an
issue. However, on Windows, this can fail due to symbol redefinitions. In these
cases, you must declare that two Halide libraries share a runtime, like so:

cmake

... updating above


add_halide_library(my_second_generator_2 FROM my_generators
GENERATOR my_second_generator
USE_RUNTIME my_first_generator.runtime
PARAMS scale=9.0 rotation=ccw output.type=float32)

add_halide_library(my_second_generator_3 FROM my_generators
GENERATOR my_second_generator
USE_RUNTIME my_first_generator.runtime
PARAMS parallel=false output.type=float64)

text
This will even work correctly when different combinations of targets are
specified for each halide library. A "greatest common denominator" target will
be chosen that is compatible with all of them (or the build will fail).

Autoschedulers

When the autoschedulers are included in the release package, they are very
simple to apply to your own generators. For example, we could update the
definition of the
my_first_generator library above to use the Adams2019
autoscheduler:

cmake
add_halide_library(my_second_generator FROM my_generators
AUTOSCHEDULER Halide::Adams2019)
text

RunGenMain

Halide provides a generic driver for generators to be used during development
for benchmarking and debugging. Suppose you have a generator executable called
my_gen and a generator within called my_filter. Then you can pass a variable
name to the
REGISTRATION parameter of add_halide_library which will contain
the name of a generated C++ source that should be linked to
Halide::RunGenMain
and
my_filter.

For example:

cmake
add_halide_library(my_filter FROM my_gen
REGISTRATION filter_reg_cpp)
add_executable(runner ${filter_reg_cpp})
target_link_libraries(runner PRIVATE my_filter Halide::RunGenMain)
text
Then you can run, debug, and benchmark your generator through the runner
executable. Learn how to interact with these executables in
RunGen.md.

Halide package documentation

Halide provides a CMake _package configuration_ module. The intended way to use
the CMake build is to run
find_package(Halide ...) in your CMakeLists.txt
file. Closely read the [
find_package documentation][find_package] before
proceeding.

Components

The Halide package script understands a handful of optional components when
loading the package.

First, if you plan to use the Halide Image IO library, you will want to include
the
png and jpeg components when loading Halide.

Second, Halide releases can contain a variety of configurations: static, shared,
debug, release, etc. CMake handles Debug/Release configurations automatically,
but generally only allows one type of library to be loaded.

The package understands two components, static and shared, that specify
which type of library you would like to load. For example, if you want to make
sure that you link against shared Halide, you can write:

cmake
find_package(Halide REQUIRED COMPONENTS shared)
text
If the shared libraries are not available, this will result in a failure.

If no component is specified, then the Halide_SHARED_LIBS variable is checked.
If it is defined and set to true, then the shared libraries will be loaded or
the package loading will fail. Similarly, if it is defined and set to false, the
static libraries will be loaded.

If no component is specified and Halide_SHARED_LIBS is _not_ defined, then the
[
BUILD_SHARED_LIBS][build_shared_libs] variable will be inspected. If it is
not defined or defined and set to true, then it will attempt to load the
shared libs and fall back to the static libs if they are not available.
Similarly, if
BUILD_SHARED_LIBS is defined and set to false, then it will
try the static libs first then fall back to the shared libs.

To ensure that the Python bindings are available, include the Python
component.

Finally, when cross compiling,
find_package(Halide REQUIRED) never pulls in the compiled compiler/JIT library
(
Halide::Halide, Halide::Generator, etc.) unless you explicitly ask for it.
Add the
JIT component (or Python) to force it to be loaded even while
cross-compiling:

cmake
find_package(Halide REQUIRED COMPONENTS JIT)
text
This looks for a HalideCompiler package matching your current (target)
platform and fails with a normal
find_package error if none is found.
HalideCompiler is the name of the underlying platform-specific package that
actually contains the compiled libraries; you generally don't need to
find_package(HalideCompiler) directly, but its name is useful for
HalideCompiler_ROOT/-DHalideCompiler_DIR=... when pointing CMake at a
specific installed build.

Autoscheduler plugins (see Autoschedulers) are resolved
separately, via a
HalideAutoschedulers package, and are always available to
add_halide_library(... AUTOSCHEDULER ...) regardless of cross-compiling --
unlike
HalideCompiler, this package's targets are never linked against (only
dlopen()'d, by their build path, at Generator run time), so there's no reason to
gate them behind the
JIT/Python components.

Note that static/shared, unlike JIT/Python, never force this load by
themselves -- requesting one merely records your preference for whichever
package eventually loads the compiled compiler (whether that's this same
find_package(Halide ...) call, because you're not cross-compiling or also
requested
JIT/Python, or a later, unrelated one, such as the internal lookup
add_halide_generator performs when it needs to build a generator). This
preference is scoped to the current directory (and any subdirectories added
after it), so independent parts of a project -- so long as neither is a
subdirectory of the other -- can request different linkage without conflicting
with each other. If one directory's
find_package call ends up loading the
compiled compiler before a subdirectory requests the other flavor, that's a real
conflict (CMake can only load one flavor of
Halide::Halide per directory
scope) and fails cleanly with a descriptive error rather than silently keeping
whichever flavor loaded first.

Variables

Variables that control package loading:

| Variable | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Halide_SHARED_LIBS | override BUILD_SHARED_LIBS when loading the Halide package via find_package. Has no effect when using Halide via add_subdirectory as a Git or FetchContent submodule. |
|
Halide_RUNTIME_NO_THREADS | skip linking of Threads library to runtime. Should be set if your toolchain does not support it (e.g. baremetal). |
|
Halide_RUNTIME_NO_DL_LIBS | skip linking of DL library to runtime. Should be set if your toolchain does not support it (e.g. baremetal). |

Variables set by the package:

| Variable | Description |
| -------------------------- | ------------------------------------------------------------------ |
|
Halide_VERSION | The full version string of the loaded Halide package |
|
Halide_VERSION_MAJOR | The major version of the loaded Halide package |
|
Halide_VERSION_MINOR | The minor version of the loaded Halide package |
|
Halide_VERSION_PATCH | The patch version of the loaded Halide package |
|
Halide_VERSION_TWEAK | The tweak version of the loaded Halide package |
|
Halide_HOST_TARGET | The Halide target triple corresponding to "host" for this build. |
|
Halide_CMAKE_TARGET | The Halide target triple corresponding to the active CMake target. |
|
Halide_ENABLE_EXCEPTIONS | Whether Halide was compiled with exception support |
|
Halide_ENABLE_RTTI | Whether Halide was compiled with RTTI |
|
WITH_AUTOSCHEDULERS | Whether the autoschedulers are available |

Variables that control package behavior:

| Variable | Description |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Halide_PYTHON_LAUNCHER | Semicolon separated list containing a command to launch the Python interpreter. Can be used to set environment variables for Python generators. |
|
Halide_NO_DEFAULT_FLAGS | Off by default. When enabled, suppresses recommended compiler flags that would be added by add_halide_generator |
|
Halide_CACHE_DIR | Defaults to $ENV{HL_CACHE_DIR}. When set, routes HL_CACHE_DIR to every generator/GenRT invocation so it can reuse cached artifacts instead of recompiling. See doc/GeneratorCache.md. |

Imported targets

Halide defines the following targets that are available to users:

| Imported target | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Halide::Halide | this is the JIT-mode library to use when using Halide from C++. |
|
Halide::Generator | this is the target to use when manually defining a generator executable. It supplies a main() function. |
|
Halide::Runtime | adds include paths to the Halide runtime headers |
|
Halide::Tools | adds include paths to the Halide tools, including the benchmarking utility. |
|
Halide::ImageIO | adds include paths to the Halide image IO utility. Depends on PNG::PNG and JPEG::JPEG if they exist or were loaded through the corresponding package components. |
|
Halide::ThreadPool | adds include paths to the Halide _simple_ thread pool utility library. This is not the same as the runtime's thread pool and is intended only for use by tests. Depends on Threads::Threads. |
|
Halide::RunGenMain | used with the REGISTRATION parameter of add_halide_library to create simple runners and benchmarking tools for Halide libraries. |

The following targets only guaranteed when requesting the Python component
(
Halide_Python_FOUND will be true):

| Imported target | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Halide::Python | this is a Python 3 package that can be referenced as $<TARGET_FILE_DIR:Halide::Python>/.. when setting up PYTHONPATH for Python tests or the like from CMake. |

The following targets only guaranteed when WITH_AUTOSCHEDULERS is true:

| Imported target | Description |
| ----------------------- | --------------------------------------------------------------- |
|
Halide::Adams2019 | the Adams et.al. 2019 autoscheduler (no GPU support) |
|
Halide::Anderson2021 | the Anderson, et.al. 2021 autoscheduler (full GPU support) |
|
Halide::Li2018 | the Li et.al. 2018 gradient autoscheduler (limited GPU support) |
|
Halide::Mullapudi2016 | the Mullapudi et.al. 2016 autoscheduler (no GPU support) |

These come from a separate HalideAutoschedulers package (distinct from
HalideCompiler), loaded automatically the first time
add_halide_library(... AUTOSCHEDULER ...) needs one -- including while
cross-compiling.

Functions

The Halide package provides several useful functions for dealing with AOT
compilation steps.

add_halide_generator

This function aids in creating cross-compilable builds that use Halide
generators.


add_halide_generator(
target
[PACKAGE_NAME package-name]
[PACKAGE_NAMESPACE namespace]
[EXPORT_FILE export-file]
[PYSTUB generator-name]
[LINK_LIBRARIES lib1 ...]
[[SOURCES] source1 ...]
)
text
Every named argument is optional, and the function uses the following default
arguments:

- If PACKAGE_NAME is not provided, it defaults to
${PROJECT_NAME}-halide_generators.
- If
PACKAGE_NAMESPACE is not provided, it defaults to
${PROJECT_NAME}::halide_generators::.
- If
EXPORT_FILE is not provided, it defaults to
${PROJECT_BINARY_DIR}/cmake/${ARG_PACKAGE_NAME}-config.cmake

This function guarantees that a Halide generator target named
<namespace><target> is available. It will first search for a package named
<package-name> using find_package; if it is found, it is assumed that it
provides the target. Otherwise, it will create an executable target named
target and an ALIAS target <namespace><target>. This function also creates
a custom target named
<package-name> if it does not exist and <target> would
exist. In this case,
<package-name> will depend on <target>, this enables
easy building of _just_ the Halide generators managed by this function.

After the call, <PACKAGE_NAME>_FOUND will be set to true if the host
generators were imported (and hence won't be built). Otherwise, it will be set
to false. This variable may be used to conditionally set properties on
<target>.

Please see
test/integration/xc
for a simple example and
apps/hannk for a
complete app that uses it extensively.

The SOURCES keyword marks the beginning of sources to be used to build
<target>, if it is not loaded. All unparsed arguments will be interpreted as
sources.

The LINK_LIBRARIES argument lists libraries that should be linked to
<target> when it is being built in the present build system.

If PYSTUB is specified, then a Python Extension will be built that wraps the
Generator with CPython glue to allow use of the Generator Python 3. The result
will be a shared library of the form
<target>_pystub.<soabi>.so, where
<soabi> describes the specific Python version and platform (e.g.,
cpython-310-darwin for Python 3.10 on macOS). See Python.md for
examples of use.

add_halide_library

This is the main function for managing generators in AOT compilation. The full
signature follows:


add_halide_library(<target> FROM <generator-target>
[GENERATOR generator-name]
[FUNCTION_NAME function-name]
[NAMESPACE cpp-namespace]
[USE_RUNTIME hl-target]
[PARAMS param1 [param2 ...]]
[DEPENDS [dep1 dep2 ...]]
[TARGETS target1 [target2 ...]]
[FEATURES feature1 [feature2 ...]]
[FEATURES[<triple>] feature1 [feature2 ...]]
[PLUGINS plugin1 [plugin2 ...]]
[AUTOSCHEDULER scheduler-name]
[FUNCTION_INFO_HEADER OUTVAR]
[HEADER OUTVAR]
[REGISTRATION OUTVAR]
[<extra-output> OUTVAR]
[GRADIENT_DESCENT]
[C_BACKEND]
[NO_THREADS]
[NO_DL_LIBS])

triple = <arch>-<bits>-<os>
arch = x86 | arm | powerpc | hexagon | wasm | riscv
bits = 32 | 64
os = linux | windows | osx | android | ios | qurt | noos | fuchsia | wasmrt

extra-output = ASSEMBLY | BITCODE | COMPILER_LOG | C_SOURCE | FEATURIZATION
| HLPIPE | LLVM_ASSEMBLY | PYTHON_EXTENSION | PYTORCH_WRAPPER
| SCHEDULE | STMT | STMT_HTML

text
This function creates a called <target> corresponding to running the
<generator-target> (an executable target which links to Halide::Generator)
one time, using command line arguments derived from the other parameters.

The arguments GENERATOR and FUNCTION_NAME default to <target>. They
correspond to the
-g and -f command line flags, respectively.

NAMESPACE is syntactic sugar to specify the C++ namespace (if any) of the
generated function; you can also specify the C++ namespace (if any) directly in
the
FUNCTION_NAME argument, but for repeated declarations or very long
namespaces, specifying this separately can provide more readable build files.

If USE_RUNTIME is not specified, this function will create another target
called
<target>.runtime which corresponds to running the generator with -r
and a compatible list of targets. This runtime target is an
INTERFACE
dependency of
<target>. If multiple runtime targets need to be linked
together, setting
USE_RUNTIME to another Halide runtime library, <target2>
will prevent the generation of
<target>.runtime and instead use
<target2>.runtime. This argument is most commonly used in conjunction with
add_halide_runtime.

Parameters can be passed to a generator via the PARAMS argument. Parameters
should be space-separated. Similarly,
TARGETS is a space-separated list of
targets for which to generate code in a single function. They must all share the
same platform/bits/os triple (e.g.
arm-32-linux). Features that are in common
among all targets, including device libraries (like
cuda) should go in
FEATURES. If TARGETS is not specified, the value of Halide_TARGET
specified at configure time will be used.

Every element of TARGETS must begin with the same arch-bits-os triple. This
function understands two _meta-triples_,
host and cmake. The meta-triple
host is equal to the arch-bits-os triple used to compile Halide along with
all the supported instruction set extensions. On platforms that support running
both 32 and 64-bit programs, this will not necessarily equal the platform the
compiler is running on or that CMake is targeting.

The meta-triple cmake is equal to the arch-bits-os of the current CMake
target. This is useful if you want to make sure you are not unintentionally
cross-compiling, which would result in an [
IMPORTED target][imported-target]
being created. When
TARGETS is empty and the host target would not
cross-compile, then
host will be used. Otherwise, cmake will be used and an
author warning will be issued.

When CMAKE_OSX_ARCHITECTURES is set and the TARGETS argument resolves to
cmake, the generator will be run once for each architecture and the results
will be fused together using
lipo. This behavior extends to runtime targets.

Sometimes, the generation will need to read files that were generated during the
build. To declare dependencies on these files, use the
DEPENDS argument. Paths
listed here will be passed verbatim to
add_custom_command, and so will be
relative to the source directory. Use absolute paths when referring to files
outside the source directory.

To use an autoscheduler, set the AUTOSCHEDULER argument to a target named like
Namespace::Scheduler, for example Halide::Adams2019. This will set the
autoscheduler GeneratorParam on the generator command line to Scheduler and
add the target to the list of plugins. Additional plugins can be loaded by
setting the
PLUGINS argument. If the argument to AUTOSCHEDULER does not
contain
:: or it does not name a target, it will be passed to the -s flag
verbatim.

If GRADIENT_DESCENT is set, then the module will be built suitably for
gradient descent calculation in TensorFlow or PyTorch. See
Generator::build_gradient_module() for more documentation. This corresponds to
passing
-d 1 at the generator command line.

If the C_BACKEND option is set, this command will invoke the configured C++
compiler on a generated source. Note that a
<target>.runtime target is _not_
created in this case, and the
USE_RUNTIME option is ignored. Other options
work as expected.

If REGISTRATION is set, the path (relative to CMAKE_CURRENT_BINARY_DIR) to
the generated
.registration.cpp file will be set in OUTVAR. This can be used
to generate a runner for a Halide library that is useful for benchmarking and
testing, as documented above. This is equivalent to setting
-e registration at
the generator command line.

If HEADER is set, the path (relative to CMAKE_CURRENT_BINARY_DIR) to the
generated
.h header file will be set in OUTVAR. This can be used with
install(FILES) to conveniently deploy the generated header along with your
library.

If FUNCTION_INFO_HEADER is set, the path (relative to
CMAKE_CURRENT_BINARY_DIR) to the generated .function_info.h header file will
be set in
OUTVAR. This produces a file that contains constexpr descriptions
of information about the generated functions (e.g., argument type and
information). It is generated separately from the normal
HEADER file because
HEADER is intended to work with basic extern "C" linkage, while
FUNCTION_INFO_HEADER requires C++17 or later to use effectively. (This can be
quite useful for advanced usages, such as producing automatic call wrappers,
etc.) Examples of usage can be found in the generated file.

Each of the extra-output arguments directly correspond to an extra output (via
-e) from the generator. The value OUTVAR names a variable into which a path
(relative to [
CMAKE_CURRENT_BINARY_DIR][cmake_current_binary_dir]) to the
extra file will be written.

When NO_THREADS is passed, the library targets will not depend on
Threads::Threads. It is your responsibility to link to an equivalent target.

When NO_DL_LIBS is passed, the library targets will not depend on
${CMAKE_DL_LIBS}. It is your responsibility to link to an equivalent library.

add_halide_python_extension_library

This function wraps the outputs of one or more add_halide_library targets with
glue code to produce a Python Extension library.


add_halide_python_extension_library(
target
[MODULE_NAME module-name]
HALIDE_LIBRARIES library1 ...
)
text
HALIDE_LIBRARIES is a list of one of more add_halide_library targets. Each
will be added to the extension as a callable method of the module. Note that
every library specified must be built with the
PYTHON_EXTENSION keyword
specified, and all libraries must use the same Halide runtime.

The result will be a shared library of the form <target>.<soabi>.so, where
<soabi> describes the specific Python version and platform (e.g.,
cpython-310-darwin for Python 3.10 on macOS.)

add_halide_runtime

This function generates a library containing a Halide runtime. Most user code
will never need to use this, as
add_halide_library() will call it for you if
necessary. The most common use case is usually in conjunction with
add_halide_python_extension_library(), as a way to ensure that all the halide
libraries share an identical runtime.


add_halide_runtime(
target
[TARGETS target1 [target2 ...]]
[NO_THREADS]
[NO_DL_LIBS]
)
text
The TARGETS, NO_THREADS, and NO_DL_LIBS arguments have identical semantics
to the argument of the same name for
add_halide_library.

Cross compiling

Cross-compiling in CMake can be tricky, since CMake doesn't easily support
compiling for both the host platform and the cross platform within the same
build. Unfortunately, Halide generator executables are just about always
designed to run on the host platform. Each project will be set up differently
and have different requirements, but here are some suggestions for effective use
of CMake in these scenarios.

Use add_halide_generator

If you are writing new programs that use Halide, you might wish to use
add_halide_generator. When using this helper, you are expected to build your
project twice: once for your build host and again for your intended target.

On the target-side build, a plain find_package(Halide REQUIRED) is all you
need (no separate package name): it never pulls in the compiled compiler, and
add_halide_generator will lazily load it under the hood only if it can't find
a prebuilt host generators package to import instead.

When building the host build, you can use the <package-name> (see the
documentation above) target to build _just_ the generators. Then, in the target
build, set
<package-name>_ROOT to the host build directory.

For example:


$ cmake -G Ninja -S . -B build-host -DCMAKE_BUILD_TYPE=Release
$ cmake --build build-host --target <package-name>
$ cmake -G Ninja -S . -B build-target --toolchain /path/to/target-tc.cmake \
-DCMAKE_BUILD_TYPE=Release \
-D<package-name>_ROOT:FILEPATH=$PWD/build-host
$ cmake --build build-target
text

Use a super-build

A CMake super-build consists of breaking down a project into subprojects that
are isolated by [toolchain][cmake-toolchains]. The basic structure is to have an
outermost project that only coordinates the sub-builds via the
[
ExternalProject][externalproject] module.

One would then use Halide to build a generator executable in one self-contained
project, then export that target to be used in a separate project. The second
project would be configured with the target [toolchain][cmake-toolchains] and
would call
add_halide_library with no TARGETS option and set FROM equal to
the name of the imported generator executable. Obviously, this is a significant
increase in complexity over a typical CMake project.

This is very compatible with the add_halide_generator strategy above.

Use ExternalProject directly

A lighter weight alternative to the above is to use
[
ExternalProject][externalproject] directly in your parent build. Configure
the parent build with the target [toolchain][cmake-toolchains], and configure
the inner project to use the host toolchain. Then, manually create an
[
IMPORTED target][imported-executable] for your generator executable and call
add_halide_library as described above.

The main drawback of this approach is that creating accurate IMPORTED targets
is difficult since predicting the names and locations of your binaries across
all possible platform and CMake project generators is difficult. In particular,
it is hard to predict executable extensions in cross-OS builds.

Use an emulator or run on device

The [CMAKE_CROSSCOMPILING_EMULATOR][cmake_crosscompiling_emulator] variable
allows one to specify a command _prefix_ to run a target-system binary on the
host machine. One could set this to a custom shell script that uploads the
generator executable, runs it on the device and copies back the results.

Another option is to install qemu-user-static to transparently emulate the
cross-built generator.

Bypass CMake

The previous two options ensure that the targets generated by
add_halide_library will be _normal_ static libraries. This approach does not
use [
ExternalProject][externalproject], but instead produces IMPORTED
targets. The main drawback of
IMPORTED targets is that they are considered
second-class in CMake. In particular, they cannot be installed with the typical
[
install(TARGETS) command][install-targets]. Instead, they must be installed
using [
install(FILES)][install-files] and the
[
$<TARGET_FILE:tgt>][target-file] generator expression.

[buildinghalidewithcmake.md]: ./BuildingHalideWithCMake.md
[build_shared_libs]: https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html
[cmake-discourse]: https://discourse.cmake.org/
[cmake-docs]: https://cmake.org/cmake/help/latest/
[cmake-toolchains]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html
[cmake_crosscompiling_emulator]: https://cmake.org/cmake/help/latest/variable/CMAKE_CROSSCOMPILING_EMULATOR.html
[cmake_current_binary_dir]: https://cmake.org/cmake/help/latest/variable/CMAKE_CURRENT_BINARY_DIR.html
[cmake_cxx_extensions]: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_EXTENSIONS.html
[cmake_cxx_standard]: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_STANDARD.html
[cmake_cxx_standard_required]: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_STANDARD_REQUIRED.html
[cmake_minimum_required]: https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html
[cmake_prefix_path]: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
[codestylecmake.md]: ./CodeStyleCMake.md
[externalproject]: https://cmake.org/cmake/help/latest/module/ExternalProject.html
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
[halide-generator-tutorial]: https://halide-lang.org/tutorials/tutorial_lesson_15_generators.html
[halide-tutorials]: https://halide-lang.org/tutorials/tutorial_introduction.html
[imported-executable]: https://cmake.org/cmake/help/latest/command/add_executable.html#imported-executables
[imported-target]: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#imported-targets
[install-files]: https://cmake.org/cmake/help/latest/command/install.html#files
[install-targets]: https://cmake.org/cmake/help/latest/command/install.html#targets
[project]: https://cmake.org/cmake/help/latest/command/project.html
[target-file]: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#target-dependent-queries

---

Doc/Hexagon

Halide for Hexagon HVX

Halide supports offloading work to Qualcomm Hexagon DSP on Qualcomm Snapdragon
845/710 devices or newer. The Hexagon DSP provides a set of 128 byte vector
instruction extensions - the Hexagon Vector eXtensions (HVX). HVX is well suited
for image processing, and Halide for Hexagon HVX will generate the appropriate
HVX vector instructions from a program authored in Halide.

Halide can be used to compile Hexagon object files directly, by using a target
such as
hexagon-32-qurt-hvx.

Halide can also be used to offload parts of a pipeline to Hexagon using the
hexagon scheduling directive. To enable the hexagon scheduling directive,
include the
hvx target feature in your target. The currently supported
combination of targets is to use the HVX target features with an x86 linux host
(to use the simulator) or with an ARM android target (to use Hexagon DSP
hardware). For examples of using the
hexagon scheduling directive on both the
simulator and a Hexagon DSP, see the blur example app.

To build and run an example app using the Hexagon target,

1. Obtain and build trunk LLVM and Clang. (Earlier versions of LLVM may work but
are not actively tested and thus not recommended.)
2. Download and install the Hexagon SDK and Hexagon Tools. Hexagon SDK 4.3.0 or
later is needed. Hexagon Tools 8.4 or later is needed.
3. Build and run an example for Hexagon HVX

1. Obtain and build trunk LLVM and Clang

(Follow the instructions given previously, just be sure to check out the main
branch.)

2. Download and install the Hexagon SDK and Hexagon Tools

Go to https://qpm.qualcomm.com/#/main/home

1. Go to Tools, and download Qualcomm Package Manager 3. Install the package
manager on your machine.
2. Run the installed Qualcomm Package Manager and install the Qualcomm Hexagon
SDK 5.x (or 4.x). The SDK can be selected from the Qualcomm Hexagon SDK
Products.
3. Set an environment variable to point to the SDK installation location


export SDK_LOC=/location/of/SDK
text

3. Build and run an example for Hexagon HVX

In addition to running Hexagon code on device, Halide also supports running
Hexagon code on the simulator from the Hexagon tools.

To build and run the blur example in Halide/apps/blur on the simulator:


cd apps/blur
export HL_HEXAGON_SIM_REMOTE=../../src/runtime/hexagon_remote/bin/v65/hexagon_sim_remote
export HL_HEXAGON_TOOLS=$SDK_LOC/Hexagon_Tools/8.x/Tools/
LD_LIBRARY_PATH=../../src/runtime/hexagon_remote/bin/host/:$HL_HEXAGON_TOOLS/lib/iss/:. HL_TARGET=host-hvx make test
text

To build and run the blur example in Halide/apps/blur on Android:

To build the example for Android, first ensure that you have Android NDK r19b or
later installed, and the ANDROID_NDK_ROOT environment variable points to it.
(Note that Qualcomm Hexagon SDK v4.3.0 includes Android NDK r19c, which is
fine.)

Now build and run the blur example using the script to run it on device:


export HL_HEXAGON_TOOLS=$SDK_LOC/HEXAGON_Tools/8.4.11/Tools/
HL_TARGET=arm-64-android-hvx ./adb_run_on_device.sh
text
---

Doc/Python

Halide Bindings for Python

- Halide Bindings for Python
- Acquiring the Python bindings
- Building the Python bindings
- Using CMake directly
- Using wheel infrastructure
- Documentation and Examples
- Differences from C++ API
- Example of Simple Usage
- Halide Generators In Python
- Writing a Generator in Python
-
hl.generator("name")
- hl.GeneratorParam
- hl.InputBuffer, hl.InputScalar
- hl.OutputBuffer, hl.OutputScalar
- Names
- generate() method
- Types for Inputs and Outputs
- Using a Generator for JIT compilation
- Using a Generator for AOT compilation
- Calling Generator-Produced code from Python
- Advanced Generator-Related Topics
- Generator Aliases
- Dynamic Inputs and Outputs
- Calling a Generator Directly
- The Lifecycle Of A Generator
- Notable Differences Between C++ and Python Generators
- Keeping Up To Date
- License

Halide provides Python bindings for most of its public API. Python 3.10 (or
higher) is required. The Python bindings are supported on 64-bit Linux, OSX, and
Windows systems.

In addition to the ability to write just-in-time Halide code using Python, you
can write Generators using the Python bindings,
which can simplify build-system integration (since no C++ metacompilation step
is required).

You can also use existing Halide Generators (written in either C++ or Python) to
produce Python extensions that can be used within Python code.

Acquiring the Python bindings

As of Halide 19.0.0, we provide binary wheels on PyPI which include the Python
bindings and the C++/CMake package for native development. Full releases may be
installed with
pip like so:

shell
$ pip install halide
text
Every commit to main is published to a private PyPI index as a development
version and these may be installed with a few extra flags:
shell
$ pip install halide --pre --extra-index-url https://pypi.halide-lang.org/simple
text
Currently, we provide wheels for: Windows x86-64, macOS x86-64, macOS arm64, and
Linux x86-64. The Linux wheels are built for manylinux_2_28, which makes them
broadly compatible (Debian 10, Ubuntu 18.10, Fedora 29).

Building the Python bindings

If pip isn't enough for your purposes, or you are developing Halide directly,
you have two options for building and using the Python bindings. Note that the
bindings require Halide to be built with RTTI and exceptions enabled, which
in turn requires LLVM to be built with RTTI, but this is not the default for
LLVM.

Before configuring with CMake, you should ensure you have prerequisite packages
installed in your local Python environment. The best way to get set up is to use
a virtual environment with
uv:

shell
$ uv sync --no-install-project
text
If you don't have LLVM installed already, you can try using the same ones the
buildbots use by adding
--group ci-llvm-<VERSION> to the uv sync command,
where
<VERSION> is the LLVM major version number (e.g. 23) or main.

If you install ci-llvm-*, you can set
Halide_LLVM_ROOT=$(halide-llvm --prefix) in your environment.

Ensure you have flatbuffers and wabt installed, too. (The wheel build does
not use vcpkg for manylinux compatibility reasons, so these must be available as
system packages or installed from source.)

Using wheel infrastructure

When using uv, this entire workflow can be run via:

shell
$ uv pip install . --no-build-isolation
text

Using CMake directly

Assuming dependencies are available, you can build the Python bindings directly
with CMake:

shell
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build build
$ cmake --install build --prefix .local
text
Now you can set the PYTHONPATH variable to point to the freshly built Python
package:
shell
$ export PYTHONPATH="$PWD/.local/lib/python3/site-packages"
text

Documentation and Examples

As mentioned elsewhere, the Python API attempts to mimic the
C++ Halide API as directly as possible; there
isn't separate Python-specific documentation for the API at this time.

For now, examine the code for the example applications in the test/apps/ and
tutorial/ subdirectories.

The tests run as part of the standard CTest infrastructure and are labeled with
the
python label. You can run the Python tests specifically by running:


$ ctest -L python
text
from the Halide build directory.

Differences from C++ API

The Python bindings attempt to mimic the Halide C++ API as closely as possible,
with some differences where the C++ idiom is either inappropriate or impossible:

- Most APIs that take a variadic argument list of ints in C++ take an explicit
list in Python. For instance, the usual version of the
Buffer ctor in C++
offers both variadic and list versions:

cpp
Buffer<>(Type t, int extent_dim_0, int extent_dim_1, ...., extent_dim_N, string name = "");
Buffer<>(Type t, vector<int> extents, string name = "");
text
In Python, only the second variant is provided.

- Func and Buffer access is done using [] rather than ()

- For zero-dimensional Func and Buffer, you must explicitly specify [()]
-- that is, use an empty tuple as the index -- because
[] is not
syntactically acceptable in Python.

- Some classes in the Halide API aren't provided because standard Python idioms
are a better fit:

- Halide::Tuple doesn't exist in the Python bindings; an ordinary Python
tuple of
Halide::Expr is used instead.
-
Halide::Realization doesn't exist in the Python bindings; an ordinary
Python tuple of
Halide::Buffer is used instead.

- static and instance method overloads with the same name in the same class
aren't allowed, so some convenience methods are missing from
Halide::Var

- Templated types (notably Halide::Buffer<> and Halide::Param<>) aren't
provided, for obvious reasons; only the equivalents of
Halide::Buffer<void>
and
Halide::Param<void> are supported.

- The functions in Halide::ConciseCasts are present in the toplevel Halide
module in Python, rather than a submodule: e.g., use
halide.i8_sat(), not
halide.ConciseCasts.i8_sat().

- Only things in the Halide namespace are supported; classes and methods that
involve using the
Halide::Internal namespace are not provided.

- No mechanism is provided for overriding any runtime functions from Python for
JIT-compiled code. (Runtime functions for AOT-compiled code can be overridden
by building and linking a custom runtime, but not currently via any runtime
API, e.g. halide_set_custom_print() does not exist.)

- No mechanism is provided for supporting Func::define_extern.

- Buffer::for_each_value() isn't supported yet.

- Func::in becomes Func.in_ because in is a Python keyword.

- Func::async becomes Func.async_ because async is a Python keyword.

- The not keyword cannot be used to negate boolean Halide expressions.
Instead, the
logical_not function can be used and is equivalent to using
operator! in C++.

- There is no way to override the logical and/or operators in Python to work
with
Expr: you must use the bitwise | and & instead. (Note that
incorrectly using
and/or just short-circuits weirdly, rather than failing
with some helpful error; this is an issue that we have not yet found any way
to improve, unfortunately.)

- Some error messages need to be made more informative.

- Some exceptions are the "incorrect" type (compared to C++ expectations).

- Many hooks to override runtime functions (e.g. Func::set_error_handler) aren't
yet implemented.

- The following parts of the Halide public API are currently missing entirely
from the Python bindings (but are all likely to be supported at some point in
the future):

- DeviceInterface
-
evaluate()

Example of Simple Usage

Here is a basic example of using Halide to produce a procedural image.

python

By convention, we import halide as 'hl' for terseness


import halide as hl

Some constants


edge = 512
k = 20.0 / float(edge)

Simple formula


x, y, c = hl.Var("x"), hl.Var("y"), hl.Var("c")
f = hl.Func("f")
e = hl.sin(x ((c + 1) / 3.0) k) hl.cos(y ((c + 1) / 3.0) * k)
f[x, y, c] = hl.cast(hl.UInt(8), e * 255.0)
f.vectorize(x, 8).parallel(y)

Realize into a Buffer.


buf = f.realize([edge, edge, 3])

Do something with the image. We'll just save it to a PNG.


from halide import imageio

imageio.imwrite("/tmp/example.png", buf)

text
It's worth noting in the example above that the Halide Buffer object supports
the Python Buffer Protocol (https://www.python.org/dev/peps/pep-3118) and thus
is converted to and from other compatible objects (e.g., NumPy's
ndarray), at
essentially zero cost, with storage being shared. Thus, we can usually pass it
directly to existing Python APIs (like
imsave()) that expect 'image-like'
objects without any explicit conversion necessary.

Halide Generators In Python

In Halide, a "Generator" is a unit of encapsulation for Halide code. It is a
self-contained piece of code that can:

- Produce a chunk of Halide IR (in the form of an hl.Pipeline) that is
appropriate for compilation (via either JIT or AOT)
- Expose itself to the build system in a discoverable way
- Fully describe itself for the build system with metadata for (at least) the
type and number of inputs and outputs expected
- Allow for build-time customization of coder-specified parameters in a way that
doesn't require editing of source code

Originally, Halide only supported writing Generators in C++. In this document,
we'll use the term "C++ Generator" to mean "Generator written in C++ using the
classic API", the term "Python Generator" to mean "Generator written in Halide's
Python bindings", and just plain "Generator" when the discussion is relatively
neutral with respect to the implementation language/API.

Writing a Generator in Python

A Python Generator is a class that:

- has the @hl.generator decorator applied to it
- declares zero or more member fields that are initialized with values of
hl.InputBuffer or hl.InputScalar, which specify the expected input(s) of
the resulting
Pipeline.
- declares one or more member fields that are initialized with values of
hl.OutputBuffer or hl.OutputScalar, which specify the expected output(s)
of the resulting
Pipeline.
- declares zero or more member fields that are initialized with values of
hl.GeneratorParam, which can be used to pass arbitrary information from the
build system to the Generator. A GeneratorParam can carry a value of type
bool, int, float, str, or hl.Type.
- declares a
generate() method that fill in the Halide IR needed to define all
the Outputs
- optionally declares a
configure() method to dynamically add Inputs or
Outputs to the pipeline, based on (e.g.) the values of
GeneratorParam values
or other external inputs

Let's look at a fairly simple example:

TODO: this example is pretty contrived; is there an equally simple

Generator to use here that would demonstrate the basics?
python
import halide as hl

x = hl.Var("x")
y = hl.Var("y")

_operators = {
"xor": lambda a, b: a ^ b,
"and": lambda a, b: a & b,
"or": lambda a, b: a | b,
}


Apply a mask value to a 2D image using a logical operator that is selected at compile-time.


@hl.generator(name="logical_op_generator")
class LogicalOpGenerator:
op = hl.GeneratorParam("xor")

input = hl.InputBuffer(hl.UInt(8), 2)
mask = hl.InputScalar(hl.UInt(8))

output = hl.OutputBuffer(hl.UInt(8), 2)

def generate(g):
# Algorithm
operator = _operators[g.op]
g.output[x, y] = operator(g.input[x, y], g.mask)

# Schedule
v = g.natural_vector_size(hl.UInt(8))
g.output.vectorize(x, v)


if __name__ == "__main__":
hl.main()

text
If you've worked with Halide Generators written in C++, the "shape" of this will
likely look familiar. (If not, no worries; you shouldn't need any knowledge of
C++ Generators for the following to make sense.)

Let's take the details here one at a time.

#### hl.generator("name")

This decorator adds appropriate "glue" machinery to the class to enforce various
invariants. It also serves as the declares a "registered name" for the
Generator, which is a unique name that the build system will use to identify the
Generator. If you omit the name, it defaults to
module.classname; if module is
__main__ then we omit it and just use the plain classname. Note that the
registered name need not match the classname. (Inside Halide, we use the
convention of
CamelCase for class names and snake_case for registered names,
but you can use whatever convention you like.)

#### hl.GeneratorParam

Each GeneratorParam is an arbitrary key-value pair that can be used to provide
configurable options at compile time. You provide the name and a default value.
The default value can be overridden by the build machinery, which will replace
the value (based on user specified text).

Note that the type of the default value is used to define the expected type of
the
GeneratorParam, and trying to set it to an incompatible value will throw
an exception. The types that are acceptable to use in a
GeneratorParam are:

- Python's bool, int, float, or str
- Halide's
hl.Type
- ...that's all

Note that the value of a GeneratorParam is read-only from the point of view of
the Generator; they are set at Generator construction time and attempting to
change their value will throw an exception.

#### hl.InputBuffer, hl.InputScalar

These declare the inputs to the hl.Pipeline that the Generator will produce.
An
hl.InputScalar is, essentially, a "factory" that produces an hl.Param in
the existing Python API, while an
hl.InputBuffer is a factory for
hl.ImageParam.

From the Generator author's perspective, a field initialized with InputScalar
is a
Param – not kinda-like-one, not a magic wrapper that forwards
everything; it is literally just
hl.Param. Similarly, an InputBuffer
produces
ImageParam, and an InputFunc is a wrapper around Func. You won't
be able to assign a new value to the member field for Inputs – as with
GeneratorParams, they are "read-only" to the Generator – but you will be able to
set constraints on them.

Note that in addition to specifying a concrete type and dimensionality for the
inputs, these factory classes support the ability to specify either (or both)
None, which means the type/dimensionality will be provided by GeneratorParams
in the build system.

#### hl.OutputBuffer, hl.OutputScalar

These declare the output(s) of the Pipeline that the Generator will produce. An
hl.OutputBuffer is, essentially, a "factory" that produces an hl.Func in the
existing Python API. (
hl.OutputScalar is just an hl.OutputBuffer that always
has zero dimensions.)

From the Generator author's perspective, a field declared with OutputBuffer
is a
Func – not kinda-like-one, not a magic wrapper that forwards
everything; it is literally just
hl.Func (with type-and-dimensionality set to
match, see recent PR https://github.com/halide/Halide/pull/6734) . You won't be
able to assign a new value to the member field for Inputs – as with
GeneratorParams, they are "read-only" to the Generator – but you will be able to
set constraints on them.

Note that in addition to specifying a concrete type and dimensionality for the
inputs, these factory classes support the ability to specify either (or both) as
None, which means the type/dimensionality will be provided by GeneratorParams
in the build system.

#### Names

Note that all the GeneratorParams, Inputs, and Outputs have names that are
implicitly filled in based on the field name of their initial assignment; unlike
in C++ Generators, there isn't a way to "override" this name (i.e., the name in
the IR will always exactly match the Python field name). Names have the same
constraints as for C++ Generators (essentially, a C identifier, but without an
initial underscore, and without any double underscore anywhere).

#### generate() method

This will be called by the Generator machinery to build the Pipeline. As with
C++ Generators, the only required task is to ensure that all Output fields are
fully defined, in a way that matches the type-and-dimension constraints
specified.

It is required that the generate() method be defined by the Generator.

(Note that, by convention, Halide Generators use g instead of self in their
generate() method to make the expression language terser; this is not in any
way required, but is recommended to improve readability.)

#### Types for Inputs and Outputs

For all the Input and Output fields of Generators, you can specify native Python
types (instead of
hl.Type) for certain cases that are unambiguous. At present,
we allow
bool as an alias for hl.Bool(), int as an alias for hl.Int(32),
and
float as an alias for hl.Float(32).

Using a Generator for JIT compilation

You can use the compile_to_callable() method to JIT-compile a Generator into a
hl.Callable, which is (essentially) just a dynamically-created function.

python
import LogicalOpGenerator
from halide import imageio
import numpy as np

Instantiate a Generator -- we can only set the GeneratorParams


by passing in a dict to the Generator's constructor


or_op_generator = LogicalOpGenerator({"op": "or"})

Now compile the Generator into a Callable


or_filter = or_op_generator.compile_to_callable()

Read in some file for input


input_buf = imageio.imread("/path/to/some/file.png")
assert input_buf.ndim == 2
assert input_buf.dtype == np.uint8

create a Buffer-compatible object for the output; we'll use np.array


output_buf = np.empty(input_buf.shape, dtype=input_buf.dtype)

Note, Python code throws exception for error conditions rather than returning an int


or_filter(input_buf, 0x7F, output_buf)

Note also that we can use named arguments for any/all, in the Python manner:


or_filter(mask=0x7F, input=input_buf, output=output_buf)

imageio.imwrite("/tmp/or.png", output_buf)

text
By default, a Generator will produce code targeted at Target("host") (or the
value of the
HL_JIT_TARGET environment variable, if set); you can override
this behavior selectively by activating a
GeneratorContext when the Generator
is created:
python
import LogicalOpGenerator

Compile with debugging enabled


t = hl.Target("host-debug")
with hl.GeneratorContext(t):
or_op_generator = LogicalOpGenerator({"op": "or"})
or_filter = or_op_generator.compile_to_callable()
text

Using a Generator for AOT compilation

If you are using CMake, the simplest thing is to use add_halide_library and
add_halide_python_extension_library():

cmake

Build a Halide library as you usually would, but be sure to include PYTHON_EXTENSION


add_halide_library(xor_filter
FROM logical_op_generator
PARAMS op=xor
PYTHON_EXTENSION output_path_var
[ FEATURES ... ]
[ PARAMS ... ])

Now wrap the generated code with a Python extension.


(Note that module name defaults to match the target name; we only


need to specify MODULE_NAME if we need a name that may differ)


add_halide_python_extension_library(my_extension
MODULE_NAME my_module
HALIDE_LIBRARIES xor_filter)
text
(Note that this rule works for both C++ and Python Generators.)

This compiles the Generator code in logical_op_generator.py with the
registered name
logical_op_generator to produce the target xor_filter, and
then wraps the compiled output with a Python extension. The result will be a
shared library of the form
<target>.<soabi>.so, where <soabi> describes the
specific Python version and platform (e.g.,
cpython-310-darwin for Python 3.10
on OSX.)

Note that you can combine multiple Halide libraries into a single Python module;
this is convenient for packaging, but also because all the libraries in a single
extension module share the same Halide runtime (and thus, the same caches,
thread pools, etc.).

cmake
add_halide_library(xor_filter ...)
add_halide_library(and_filter ...)
add_halide_library(or_filter ...)

add_halide_python_extension_library(my_extension
MODULE_NAME my_module
HALIDE_LIBRARIES xor_filter and_filter or_filter)

text
Note that you must take care to ensure that all of the add_halide_library
targets specified use the same Halide runtime; it may be necessary to use
add_halide_runtime to define an explicit runtime that is shared by all the
targets:
cmake
add_halide_runtime(my_runtime)

add_halide_library(xor_filter USE_RUNTIME my_runtime ...)
add_halide_library(and_filter USE_RUNTIME my_runtime ...)
add_halide_library(or_filter USE_RUNTIME my_runtime ...)

add_halide_python_extension_library(my_extension
MODULE_NAME my_module
HALIDE_LIBRARIES xor_filter and_filter or_filter)

text
If you're not using CMake, you can "drive" a Generator directly from your build
system via command-line flags. The most common, minimal set looks something like
this:
shell
python3 /path/to/my/generator.py -g <registered-name> \
-o <output-dir> \
target=<halide-target-string> \
[generator-param=value ...]
text
The argument to -g is the name supplied to the @hl.generator decorator. The
argument to -o is a directory to use for the output files; by default, we'll
produce a static library containing the object code, and a C++ header file with
a forward declaration.
target specifies a Halide Target string describing
the OS, architecture, features, etc. that should be used for compilation. Any
other arguments to the command line that don't begin with
- are presumed to
name
GeneratorParam values to set.

There are other flags and options too, of course; use
python3 /path/to/my/generator.py -help to see a list with explanations.

(Unfortunately, there isn't (yet) a way to produce a Python Extension just by
running a Generator; the logic for
add_halide_python_extension_library is
currently all in the CMake helper files.)

Calling Generator-Produced code from Python

As long as the shared library is in PYTHONPATH, it can be imported and used
directly. For the example above:

python
from my_module import xor_filter
from halide import imageio
import numpy as np

Read in some file for input


input_buf = imageio.imread("/path/to/some/file.png")
assert input_buf.ndim == 2
assert input_buf.dtype == np.uint8

create a Buffer-compatible object for the output; we'll use np.array


output_buf = np.empty(input_buf.shape, dtype=input_buf.dtype)

Note, Python code throws exception for error conditions rather than returning an int


xor_filter(input_buf, 0xFF, output_buf)

Note also that we can use named arguments for any/all, in the Python manner:


xor_filter(input=input_buf, mask=0xff, output=output_buf)

imageio.imwrite("/tmp/xored.png", output_buf)

text
Above, we're using common Python utilities (numpy) to construct the
input/output buffers we want to pass to Halide.

Note: Getting the memory order correct can be a little confusing for numpy.
By default, numpy uses "C-style"
row-major
order, which sounds like the right option for Halide; however, this nomenclature
assumes the matrix-math convention of ordering axes as
[rows, cols], whereas
Halide (and imaging code in general) generally assumes
[x, y] (i.e.,
[cols, rows]). Thus, what you usually want in Halide is column-major ordering.
This means numpy arrays, by default, come with the wrong memory layout for
Halide. But if you construct the numpy arrays yourself (like above), you can
pass
order='F' to make numpy use the Halide-compatible memory layout. If
you're passing in an array constructed somewhere else, the easiest thing to do
is to
.transpose() it before passing it to your Halide code.

#### Generator Aliases

A Generator alias is a way to associate a Generator with one (or more) specific
sets of GeneratorParams; the 'alias' is just another registered name. This
offers a convenient alternative to specifying multiple sets of GeneratorParams
via the build system. To define alias(es) for a Generator, just add the
@hl.alias decorator before @hl.generator decorator:

python
@hl.alias(
xor_generator={"op": "xor"},
and_generator={"op": "and"},
or_generator={"op": "or"},
)
@hl.generator("logical_op_generator")
class LogicalOpGenerator: ...
text
#### Dynamic Inputs and Outputs

If you need to build Input and/or Output dynamically, you can define a
configure() method. It will always be called after all GeneratorParam values
are valid, but before
generate() is called. Let's take our example and add an
option to pass an offset to be added after the logical operator is done:

python
import halide as hl

x = hl.Var("x")
y = hl.Var("y")

_operators = {
"xor": lambda a, b: a ^ b,
"and": lambda a, b: a & b,
"or": lambda a, b: a | b,
}


Apply a mask value to a 2D image using a logical operator that is selected at compile-time.


@hl.generator(name="logical_op_generator")
class LogicalOpGenerator:
op = hl.GeneratorParam("xor")
with_offset = hl.GeneratorParam(False)

input = hl.InputBuffer(hl.UInt(8), 2)
mask = hl.InputScalar(hl.UInt(8))

output = hl.OutputBuffer(hl.UInt(8), 2)

def configure(g):
# If with_offset is specified, we
if g.with_offset:
g.add_input("offset", hl.InputScalar(hl.Int(32)))

# See note the use of 'g' instead of 'self' here
def generate(g):
# Algorithm
operator = _operators[g.op]
if hasattr(g, "offset"):
g.output[x, y] = operator(g.input[x, y], g.mask) + g.offset
else:
g.output[x, y] = operator(g.input[x, y], g.mask)

# Schedule
v = g.natural_vector_size(hl.UInt(8))
g.output.vectorize(x, v)


if __name__ == "__main__":
hl.main()

text
The only thing you can (usefully) do from configure() is to call add_input()
or
add_output(), which accept only the appropriate Input or Output
classes. The resulting value is stored as a member variable with the name
specified (if there is already a member with the given name, an exception is
thrown).

#### Calling a Generator Directly

Each Generator has a class method (injected by @hl.generator) that allows you
to "call" the Generator like an ordinary function; this allows you to directly
take the Halide IR produced by the Generator and do anything you want to with
it. This can be especially useful when writing library code, as you can
'compose' more complex pipelines this way.

This method is named call() and looks like this:

python
@classmethod
def call(cls, args, *kwargs): ...
text
It takes the inputs (specified either by-name or by-position in the usual Python
way). It also allows for an optional by-name-only argument,
generator_params,
which is a simple Python dict that allows for overriding
GeneratorParams. It
returns a tuple of the Output values. For the earlier example, usage might be
something like:
python
import LogicalOpFilter

x, y = hl.Var(), hl.Var()

input_buf = hl.Buffer(hl.UInt(8), [2, 2])
mask_value = 0x7F

Inputs by-position


func_out = LogicalOpFilter.call(input_buf, mask_value)

Inputs by-name


func_out = LogicalOpFilter.call(mask=mask_value, input=input_buf)

Above again, but with generator_params


func_out = LogicalOpFilter.call(input_buf, mask_value, generator_params={"op": "and"})
func_out = LogicalOpFilter.call(
generator_params={"op": "and"}, input=input_buf, mask=mask_value
)
text
#### The Lifecycle Of A Generator

Whether being driven by a build system (for AOT use) or by another piece of
Python code (typically for JIT use), the lifecycle of a Generator looks
something like this:

- An instance of the Generator in question is created. It uses the
currently-active
GeneratorContext (which contains the Target to be used
for code generation), which is stored in a thread-local stack.
- Some (or all) of the default values of the
GeneratorParam members may be
replaced based on (e.g.) command-line arguments in the build system
- All
GeneratorParam members are made immutable.
- The
configure() method is called, allowing the Generator to use
add_input() or add_output() to dynamically add inputs and/or outputs.
- If any
Input or Output members were defined with unspecified type or
dimensions (e.g.
some_input = hl.InputBuffer(None, 3)), those types and
dimensions are filled in from
GeneratorParam values (e.g. some_input.type
in this case). If any types or dimensions are left unspecified after this
step, an exception will be thrown.
- If the Generator is being invoked via its
call() method (see below), the
default values for
Inputs will be replaced by the values from the argument
list.
- The Generator instance has its
generate() method called.
- The calling code will extract the values of all
Output values and validate
that they match the type, dimensions, etc. of the declarations.
- The calling code will then either call
compile_to_file() and friends (for
AOT use), or return the output values to the caller (for JIT use).
- Finally, the Generator instance will be discarded, never to be used again.

Note that almost all the code doing the hand-wavy bits above is injected by the
@hl.generator decorator – the Generator author doesn't need to know or care
about the specific details, only that they happen.

All Halide Generators are single-use instances – that is, any given
Generator instance should be used at most once. If a Generator is to be executed
multiple times (e.g. for different
GeneratorParam values, or a different
Target), a new one must be constructed each time.

#### Notable Differences Between C++ and Python Generators

If you have written C++ Generators in Halide in the past, you might notice some
features are missing and/or different for Python Generators. Among the
differences are:

- In C++, you can create a Generator, then call set_generatorparam_value() to
alter the values of GeneratorParams. In Python, there is no public method to
alter a GeneratorParam after the Generator is created; instead, you must pass
a dict of GeneratorParam values to the constructor, after which the values are
immutable for that Generator instance.
- Array Inputs/Outputs: in our experience, they are pretty rarely used, it
complicates the implementation in nontrivial ways, and the majority of use
cases for them can all be reasonably supported by dynamically adding inputs or
outputs (and saving the results in a local array).
-
Input<Func> and Output<Func>: these were deliberately left out in order to
simplify Python Generators. It's possible that something similar might be
added in the future.
- GeneratorParams with LoopLevel types: these aren't useful without
Input<Func>/Output<Func>.
- GeneratorParams with Enum types: using a plain
str type in Python is
arguably just as easy, if not easier.
-
get_externs_map(): this allows registering ExternalCode objects to be
appended to the Generator's code. In our experience, this feature is very
rarely used. We will consider adding this in the future if necessary.
- Lazy Binding of Unspecified Input/Output Types: for C++ Generators, if you
left an Output's type (or dimensionality) unspecified, you didn't always have
to specify a
GeneratorParam to make it into a concrete type: if the type was
always fully specified by the contents of the
generate() method, that was
good enough. In Python Generators, by contrast, all types and dimensions
must be explicitly specified by either code declaration or by
GeneratorParam setting. This simplifies the internal code in nontrivial
ways, and also allows for (arguably) more readable code, since there are no
longer cases that require the reader to execute the code in their head in
order to deduce the output types.

Keeping Up To Date

If you use the Halide Bindings for Python inside Google, you are strongly
encouraged to
subscribe to announcements for new releases of Halide,
as it is likely that enhancements and tweaks to our Python support will be made
in future releases.

License

The Python bindings use the same
MIT license as Halide.

Python bindings provided by Connelly Barnes (2012-2013), Fred Rotbart (2014),
Rodrigo Benenson (2015) and the Halide open-source community.

---

Doc/RunGen

Running and Benchmarking Halide Generators

Overview

RunGen is a simple(ish) wrapper that allows an arbitrary Generator to be built
into a single executable that can be run directly from bash, without needing to
wrap it in your own custom main() driver. It also implements a rudimentary
benchmarking and memory-usage functionality.

If you use the standard CMake rules for Generators, you get RunGen functionality
automatically. (If you use Make, you might need to add an extra rule or two to
your Makefile; all the examples in
apps/ already have these rules.)

For every halide_library (or halide_library_from_generator) rule, there is
an implicit
name.rungen rule that generates an executable that wraps the
Generator library:


In addition to defining a static library named "local_laplacian", this rule


also implicitly defines an executable target named "local_laplacian.rungen"


halide_library(
local_laplacian
SRCS local_laplacian_generator.cc
)
text
You can build and run this like any other executable:

$ make bin/local_laplacian.rungen && ./bin/local_laplacian.rungen
Usage: local_laplacian.rungen argument=value [argument=value... ] [flags]
...typical "usage" text...
text
To be useful, you need to pass in values for the Generator's inputs (and
locations for the output(s)) on the command line, of course. You can use the
--describe flag to see the names and expected types:

('make bin/local_laplacian.rungen && ' prefix omitted henceforth for clarity)


$ ./bin/local_laplacian.rungen --describe
Filter name: "local_laplacian"
Input "input" is of type Buffer<uint16> with 3 dimensions
Input "levels" is of type int32
Input "alpha" is of type float32
Input "beta" is of type float32
Output "local_laplacian" is of type Buffer<uint16> with 3 dimensions
text
Warning: Outputs may have $X (where X is a small integer) appended to their
names in some cases (or, in the case of Generators that don't explicitly declare
outputs via
Output<>, an autogenerated name of the form fX). If this
happens, don't forget to escape the
$ with a backslash as necessary. These are
both bugs we intend to fix; see https://github.com/halide/Halide/issues/2194

As a convenience, there is also an implicit target that builds-and-runs, named
simply "NAME.run":


This is equivalent to "make bin/local_laplacian.rungen && ./bin/local_laplacian.rungen"


$ make bin/local_laplacian.run
Usage: local_laplacian.rungen argument=value [argument=value... ] [flags]

To pass arguments to local_laplacian.rungen, set the RUNARGS var:


$ make bin/local_laplacian.run RUNARGS=--describe
Filter name: "local_laplacian"
Input "input" is of type Buffer<uint16> with 3 dimensions
Input "levels" is of type int32
Input "alpha" is of type float32
Input "beta" is of type float32
Output "local_laplacian" is of type Buffer<uint16> with 3 dimensions
text
Inputs are specified as name=value pairs, in any order. Scalar inputs are
specified the typical text form, while buffer inputs (and outputs) are specified
via paths to image files. RunGen currently can read/write image files in any
format supported by halide_image_io.h; at this time, that means .png, .jpg,
.ppm, .pgm, and .tmp formats. (We plan to add .tiff and .mat (level 5) in the
future.)

$ ./bin/local_laplacian.rungen input=../images/rgb_small16.png levels=8 alpha=1 beta=1 output=/tmp/out.png
$ display /tmp/out.png
text
You can also specify any scalar input as default or estimate, which will use
the default value specified for the input, or the value specified by
set_estimate for that input. (If the relevant value isn't set for that input,
a runtime error occurs.)

$ ./bin/local_laplacian.rungen input=../images/rgb_small16.png levels=8 alpha=estimate beta=default output=/tmp/out.png
$ display /tmp/out.png
text
If you specify an input or output file format that doesn't match the required
type/dimensions for an argument (e.g., using an 8-bit PNG for an Input<float>,
or a grayscale image for a 3-dimensional input), RunGen will try to coerce the
inputs to something sensible; that said, it's hard to always get this right, so
warnings are always issued whenever an input or output is modified in any
way.

This filter expects a 16-bit RGB image as input, but we're giving it an 8-bit grayscale image:


$ ./bin/local_laplacian.rungen input=../images/gray.png levels=8 alpha=1 beta=1 output=/tmp/out.png
Warning: Image for Input "input" has 2 dimensions, but this argument requires at least 3 dimensions: adding dummy dimensions of extent 1.
Warning: Image loaded for argument "input" is type uint8 but this argument expects type uint16; data loss may have occurred.
text
By default, we try to guess a suitable size for the output image(s), based
mainly on the size of the input images (if any); you can also specify explicit
output extents. (Note that output_extents are subject to constraints already
imposed by the particular Generator's logic, so arbitrary values for
--output_extents may produce runtime errors.)

Constrain output extents to 100x200x3


$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=../images/rgb_small16.png levels=8 alpha=1 beta=1 output=/tmp/out.png
text
Sometimes you don't care what the particular element values for an input are
(e.g. for benchmarking), and you just want an image of a particular size; in
that case, you can use the
zero:[] pseudo-file; it infers the _type_ from the
Generator, and inits every element to zero:

Input is a 3-dimensional image with extent 123, 456, and 3


(bluring an image of all zeros isn't very interesting, of course)


$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=zero:[123,456,3] levels=8 alpha=1 beta=1 output=/tmp/out.png
text
You can also specify arbitrary (nonzero) constants:

Input is a 3-dimensional image with extent 123, 456, and 3,


filled with a constant value of 42


$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=constant:42:[123,456,3] levels=8 alpha=1 beta=1 output=/tmp/out.png
text
Similarly, you can create identity images where only the diagonal elements are
1-s (rest are 0-s) by invoking
identity:[]. Diagonal elements are defined as
those whose first two coordinates are equal.

There's also a random:SEED:[] pseudo-file, which fills the image with uniform
noise based on a specific random-number seed:


Input is a 3-dimensional image with extent 123, 456, and 3


$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=random:42:[123,456,3] levels=8 alpha=1 beta=1 output=/tmp/out.png
text
Instead of specifying an explicit set of extents for a pseudo-input, you can use
the string
auto, which will run a bounds query to choose a legal set of
extents for that input given the known output extents. (This is only useful when
used in conjunction with the
--output_extents flag.)

$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=zero:auto levels=8 alpha=1 beta=1 output=/tmp/out.png
text
You can also specify estimate for the extents, which will use the estimate
values provided, typically (but not necessarily) for auto_schedule. (If there
aren't estimates for all of the buffer's dimensions, a runtime error occurs.)

$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=zero:auto levels=8 alpha=1 beta=1 output=/tmp/out.png
text
You can combine the two and specify estimate_then_auto for the extents, which
will attempt to use the estimate values; if a given input buffer has no
estimates, it will fall back to the bounds-query result for that input:

$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] input=zero:estimate_then_auto levels=8 alpha=1 beta=1 output=/tmp/out.png
text
Similarly, you can use estimate for --output_extents, which will use the
estimate values for each output. (If there aren't estimates for all of the
outputs, a runtime error occurs.)

$ ./bin/local_laplacian.rungen --output_extents=estimate input=zero:auto levels=8 alpha=1 beta=1 output=/tmp/out.png
text
If you don't want to explicitly specify all (or any!) of the input values, you
can use the
--default_input_buffers and --default_input_scalars flags, which
provide wildcards for any omitted inputs:

$ ./bin/local_laplacian.rungen --output_extents=[100,200,3] --default_input_buffers=random:0:auto --default_input_scalars=estimate output=/tmp/out.png
text
In this case, all input buffers will be sized according to bounds query, and
filled with a random seed; all input scalars will be initialized to their
declared default values. (If they have no declared default value, a zero of the
appropriate type will be used.)

Note: --default_input_buffers can produce surprising sizes! For instance, any
input that uses
BoundaryConditions::repeat_edge to wrap itself can legally be
set to almost any size, so you may legitimately get an input with extent=1 in
all dimensions; whether this is useful to you or not depends on the code. It's
highly recommended you do testing with the
--verbose flag (which will log the
calculated sizes) to reality-check that you are getting what you expect,
especially for benchmarking.

A common case (especially for benchmarking) is to specify using estimates for
all inputs and outputs; for this, you can specify
--estimate_all, which is
just a shortcut for
--default_input_buffers=estimate_then_auto --default_input_scalars=estimate --output_extents=estimate.

Benchmarking

To run a benchmark, use the --benchmarks=all flag:


$ ./bin/local_laplacian.rungen --benchmarks=all input=zero:[1920,1080,3] levels=8 alpha=1 beta=1 --output_extents=[100,200,3]
Benchmark for local_laplacian produces best case of 0.0494629 sec/iter, over 3 blocks of 10 iterations.
Best output throughput is 39.9802 mpix/sec.
text
You can use --default_input_buffers and --default_input_scalars here as
well:

$ ./bin/local_laplacian.rungen --benchmarks=all --default_input_buffers --default_input_scalars --output_extents=estimate
Benchmark for local_laplacian produces best case of 0.0494629 sec/iter, over 3 blocks of 10 iterations.
Best output throughput is 39.9802 mpix/sec.
text
Note: halide_benchmark.h is known to be inaccurate for GPU filters; see
https://github.com/halide/Halide/issues/2278

Measuring Memory Usage

To track memory usage, use the --track_memory flag, which measures the
high-water-mark of CPU memory usage.


$ ./bin/local_laplacian.rungen --track_memory input=zero:[1920,1080,3] levels=8 alpha=1 beta=1 --output_extents=[100,200,3]
Maximum Halide memory: 82688420 bytes for output of 1.97754 mpix.
text
Warning: --track_memory may degrade performance; don't combine it with
--benchmark or expect meaningful timing measurements when using it.

Using RunGen in Make

To add support for RunGen to your Makefile, you need to add rules something like
this (see
apps/support/Makefile.inc for an example):


HALIDE_DISTRIB ?= /path/to/halide/distrib/folder

$(BIN)/RunGenMain.o: $(HALIDE_DISTRIB)/tools/RunGenMain.cpp
@mkdir -p $(@D)
@$(CXX) -c $< $(CXXFLAGS) $(LIBPNG_CXX_FLAGS) $(LIBJPEG_CXX_FLAGS) -I$(BIN) -o $@

.PRECIOUS: $(BIN)/%.rungen
$(BIN)/%.rungen: $(BIN)/%.a $(BIN)/%.registration.cpp $(BIN)/RunGenMain.o
$(CXX) $(CXXFLAGS) $^ -o $@ $(LIBPNG_LIBS) $(LIBJPEG_LIBS) $(LDFLAGS)

RUNARGS ?=

$(BIN)/%.run: $(BIN)/%.rungen
@$(CURDIR)/$< $(RUNARGS)

text
Note that the %.registration.cpp file is created by running a generator and
specifying
registration in the comma-separated list of files to emit; these
are also generated by default if
-e is not used on the generator command line.

Known Issues & Caveats

- If your Generator uses define_extern(), you must have all link-time
dependencies declared properly via
FILTER_DEPS; otherwise, you'll fail to
link.
- The code does its best to detect when inputs or outputs need to be
chunky/interleaved (rather than planar), but in unusual cases it might guess
wrong; if your Generator uses buffers with unusual stride setups, RunGen might
fail at runtime. (If this happens, please file a bug!)
- The code for deducing good output sizes is rudimentary and needs to be
smartened; it will sometimes make bad decisions which will prevent the filter
from executing. (If this happens, please file a bug!)

---

Doc/Testing

Testing

Halide uses CTest as its primary test platform and runner.

Organization

Halide's tests are organized beneath the top-level test/ directory. These
folders are described below:

| Folder | Description |
| -------------------- | -------------------------------------------------------------------------------- |
|
autoschedulers/$AS | Test for the $AS (e.g. adams2019) autoscheduler |
|
common | Code that may be shared across multiple tests |
|
correctness | Tests that check correctness of various compiler properties |
|
error | Tests that expect an exception to be thrown (or abort() to be called) |
|
failing_with_issue | Correctness tests that are associated with a particular issue on GitHub |
|
fuzz | Fuzz tests. Read more at FuzzTesting.md |
|
generator | Tests of Halide's AOT compilation infrastructure. |
|
integration | Tests of Halide's CMake package for downstream use, including cross compilation. |
|
performance | Tests that check that certain schedules indeed improve performance. |
|
runtime | Unit tests for the Halide runtime library |
|
warning | Tests that expected warnings are indeed issued. |

The tests in each of these directories are given CTest labels corresponding to
the directory name. Thus, one can use
ctest -L generator to run only the
generator tests. The performance tests configure CTest to not run them
concurrently with other tests (including each other).

The vast majority of our tests are simple C++ executables that link to Halide,
perform some checks, and print the special line
Success! upon successful
completion. There are three main exceptions to this:

First, the warning tests are expected to print a line that reads Warning:
and do not look for
Success!.

Second, some tests cannot run in all scenarios; for example, a test that
measures CUDA performance requires a CUDA-capable GPU. In these cases, tests are
expected to print
[SKIP] and exit and not print Success! or Warning:.

Finally, the error tests are expected to throw an (uncaught) exception that is
not a
Halide::InternalError (i.e. from a failing internal_assert). The logic
for translating uncaught exceptions into successful tests is in
test/common/expect_abort.cpp.

Debugging the compiler

We provide helpers for pretty-printing Halide's IR types (and other internal
data structures) in interactive debuggers. The LLDB and GDB helpers share a
single C++ bridge,
Halide::Internal::debug_string() (declared in
src/IRPrinter.h): each pretty-printer asks the inferior to render the value
with the normal IR printer, so all three front-ends produce the same output.
Because the value is rendered by calling into the program, the process must be
running (stopped at a breakpoint) for IR summaries to appear.

LLDB

The .lldbinit file at the repository root will load tools/lldbhalide.py
automatically if you launch
lldb from this directory and your ~/.lldbinit
file contains the line,


settings set target.load-cwd-lldbinit true
text
If you prefer to avoid such global configuration, you can directly load the
helpers with the LLDB command,

command script import ./tools/lldbhalide.py
text
again assuming that the repository root is your current working directory.

To see the benefit of using these helpers, let us debug correctness_bounds:


$ lldb ./build/test/correctness/correctness_bounds
(lldb) breakpoint set --file bounds.cpp --line 18
Breakpoint 1: where = correctness_bounds
main + 864 at bounds.cpp:18:12, address = 0x0000000100002054
(lldb) run
Process 29325 launched: '/Users/areinking/dev/Halide/build/test/correctness/correctness_bounds' (arm64)
Defining function...
Process 29325 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100002054 correctness_boundsmain(argc=1, argv=0x000000016fdff160) at bounds.cpp:18:12
15 g(x, y) = min(x, y);
16 h(x, y) = clamp(x + y, 20, 100);
17
-> 18 Var xo("xo"), yo("yo"), xi("xi"), yi("yi");
19
20 Target target = get_jit_target_from_environment();
21 if (target.has_gpu_feature()) {
Target 0: (correctness_bounds) stopped.
(lldb)
text
Now we can try to inspect the Func h. Without the helpers, we see:

(lldb) v h
(Halide::Func) {
func = {
contents = {
strong = (ptr = 0x0000600002486a20)
weak = nullptr
idx = 0
}
}
pipeline_ = {
contents = (ptr = 0x0000000000000000)
}
}
text
But if we load the helpers and try again, we get a much more useful output:

(lldb) command script import ./tools/lldbhalide.py
(lldb) v h
... lots of output ...
text
The amount of output here is maybe a bit _too_ much, but we gain the ability to
more narrowly inspect data about the func:

(lldb) v h.func.init_def.values
...
(std::vector<Halide::Expr>) h.func.init_def.values = size=1 {
[0] = max(min(x + y, 100), 20)
}
text
These helpers are particularly useful when using graphical debuggers, such as
the one found in CLion.

GDB

The GDB helpers live in tools/gdbhalide.py and mirror the LLDB coverage. The
.gdbinit file at the repository root loads them when you launch gdb from
this directory, but GDB only auto-loads a local
.gdbinit if you have marked it
safe, e.g. by adding to your
~/.gdbinit:


add-auto-load-safe-path /path/to/Halide/.gdbinit
text
Otherwise, load the helpers manually once the program is running:

(gdb) source ./tools/gdbhalide.py
text
You can then print an Expr, Stmt, Target, etc. and see the same
pretty-printed form that LLDB produces.

Natvis (Visual Studio and CLion)

tools/Halide.natvis provides Visual Studio-style visualizers. On MSVC it is
embedded into the Halide PDB automatically (via the build), so it loads whenever
you debug something linked against Halide. It is also attached to the target as
a source file, so IDEs whose LLDB-based debuggers understand Natvis — including
recent CLion and Rider on Linux and macOS — can load it as well (Settings |
Build, Execution, Deployment | Debugger | Data Views | Native).

Natvis is declarative and cannot call functions, so it does not reproduce the
one-line IR rendering from
debug_string() — use the LLDB/GDB helpers above for
that. Instead, it downcasts each
Expr/Stmt handle to its concrete node using
the
node_type discriminator and presents a fully expandable IR tree, recursing
through operands (leaf nodes such as immediates and
Variable show their value
in the summary). Because this uses no function calls, it also works on a core
dump or a process that isn't running. Natvis additionally covers buffers
(
halide_buffer_t, Halide::Buffer, Halide::Runtime::Buffer, including "Add
to Image Watch" in Visual Studio) and flat value types such as
halide_type_t,
Halide::Type, and ConstantInterval.

---

Doc/Vulkan

Vulkan Support for Halide

Halide supports the Khronos Vulkan framework as a compute API backend for
GPU-like devices, and compiles directly to a binary SPIR-V representation as
part of its code generation before submitting it to the Vulkan API. Both JIT and
AOT usage are supported via the
vulkan target flag (e.g.
HL_JIT_TARGET=host-vulkan).

Vulkan support is actively under development, and considered BETA quality at
this stage. Tests are passing, but performance tuning and user testing is needed
to identify potential issues before rolling this into production.

See below for details.

Compiling Halide w/Vulkan Support

You'll need to configure Halide and enable the cmake option TARGET_VULKAN (which
is now ON by default).

For example, on Linux & OSX:


% cmake -G Ninja -DTARGET_VULKAN=ON -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=$LLVM_ROOT/lib/cmake/llvm
% cmake --build build --config Release
text
On Windows, you may need to specify the location of the Vulkan SDK if the paths
aren't resolved by CMake automatically. For example (assuming the Vulkan SDK is
installed in the default path):

C:\> cmake -G Ninja -DTARGET_VULKAN=ON -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=$LLVM_ROOT/lib/cmake/llvm -DVulkan_LIBRARY=C:\VulkanSDK\1.3.231.1\Lib\vulkan-1.lib -DVulkan_INCLUDE_DIR=C:\VulkanSDK\1.3.231.1\Include\vulkan -S . -B build
C:\> cmake --build build --config Release

text

Vulkan Runtime Environment:

Halide has no direct dependency on Vulkan for code-generation, but the runtime
requires a working Vulkan environment to run Halide generated code. Any valid
Vulkan v1.0+ device driver should work.

Specifically, you'll need:

- A vendor specific Vulkan device driver
- The generic Vulkan loader library

For AMD & NVIDIA & Intel devices, download and install the latest graphics
driver for your platform. Vulkan support should be included.

Windows

To build Halide AOT generators, you'll need the Vulkan SDK (specifically the
Vulkan loader library and headers):
https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe

For Vulkan device drivers, consult the appropriate hardware vendor for your
device. A few common ones are listed below.

- AMD Vulkan Driver
- NVIDIA Vulkan Driver
- INTEL Vulkan Driver

Linux

The Vulkan SDK packages are now being maintained by LunarG. These include the
Vulkan Loader library, as well as the Vulkan Tools packages. Instructions for
installing these can be found on their
Getting Started Guide.

Once the SDK has been installed, you need to install the appropriate driver for
your device. Proprietary drivers can be installed via 'apt' using PPA's for each
vendor. Examples for AMD and NVIDIA are provided below.

For AMD on Ubuntu v22.04:


$ sudo add-apt-repository ppa:oibaf/graphics-drivers
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install libvulkan1 mesa-vulkan-drivers vulkan-tools
text
For NVIDIA on Ubuntu v22.04:

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt update
$ sudo apt upgrade

- replace ### with latest driver release (e.g. 515)


$ sudo apt install nvidia-driver-### nvidia-settings libvulkan1 vulkan-tools
text
Note that only valid drivers for your system should be installed since there are
reports of the Vulkan loader segfaulting just by having a non-supported driver
present. Specifically, the seemingly generic
mesa-vulkan-drivers actually
includes the AMD graphics driver, which can cause problems if installed on an
NVIDIA-only system.

Mac

You're better off using Halide's Metal backend instead, but it is possible to
run Vulkan apps on a Mac via the MoltenVK library:

- MoltenVK Project

The easiest way to get the necessary dependencies is to use the official
MoltenVK SDK installer provided by LunarG:

- MoltenVK SDK (Latest Release)

Alternatively, if you have the Homebrew package manager
installed for MacOS, you can use it to install the Vulkan Loader and MoltenVK
compatibility layer:


$ brew install vulkan-loader molten-vk
text

Testing Your Vulkan Environment

You can validate that everything is configured correctly by running the
vulkaninfo app (bundled in the vulkan-utils package) to make sure your device
is detected (eg):


$ vulkaninfo
==========
VULKANINFO
==========

Vulkan Instance Version: 1.3.224


Instance Extensions: count = 19
===============================
...

Layers: count = 10
==================
VK_LAYER_KHRONOS_profiles (Khronos Profiles layer) Vulkan version 1.3.224, layer version 1:
Layer Extensions: count = 0
Devices: count = 1
GPU id = 0 (NVIDIA GeForce RTX 3070 Ti)
Layer-Device Extensions: count = 1

...

text
Make sure everything looks correct before continuing!

Targeting Vulkan

To generate Halide code for Vulkan, simply add the vulkan flag to your target
as well as any other optional device specific features you wish to enable for
Halide:

| Target Feature | Description |
| -------------- | -------------------------------------------------------------- |
|
vulkan | Enables the vulkan backend |
|
vk_int8 | Allows 8-bit integer storage types to be used |
|
vk_int16 | Allows 16-bit integer storage types to be used |
|
vk_int64 | Allows 64-bit integer storage types to be used |
|
vk_float16 | Allows 16-bit floating-point values to be used for computation |
|
vk_float64 | Allows 64-bit floating-point values to be used for computation |
|
vk_v10 | Generates code compatible with the Vulkan v1.0+ API |
|
vk_v12 | Generates code compatible with the Vulkan v1.2+ API |
|
vk_v13 | Generates code compatible with the Vulkan v1.3+ API |

Note that 32-bit integer and floating-point types are always available. All
other optional device features are off by default (since they are not required
by the Vulkan API, and thus must be explicitly enabled to ensure that the code
being generated will be compatible with the device and API version being used
for execution).

For AOT generators add vulkan (and any other flags you wish to use) to the
target command line option:


$ ./lesson_15_generate -g my_first_generator -o . target=host-vulkan-vk_int8-vk_int16
text
For JIT apps use the HL_JIT_TARGET environment variable:

$ HL_JIT_TARGET=host-vulkan-vk_int8-vk_int16 ./tutorial/lesson_01_basics
text

Useful Runtime Environment Variables

To modify the default behavior of the runtime, the following environment
variables can be used to adjust the configuration of the Vulkan backend at
execution time:

HL_VK_LOADER_LIB=... can be used to specify the full path and filename of the
Vulkan loader library (e.g.
/usr/local/lib64/libvulkan.so.1). This can be
useful if you need to override the default search path, or if the loader is
installed in a custom location. If not set, the runtime will search across a
list of known paths.

HL_VK_LAYERS=... will tell Halide to choose a suitable Vulkan instance that
supports the given list of layers. If not set,
VK_INSTANCE_LAYERS=... will be
used instead. If neither are present, Halide will use the first Vulkan compute
device it can find. Multiple layers can be specified using the appropriate
environment variable list delimiter (
: on Linux/OSX/Posix, or ; on Windows).

HL_VK_DEVICE_TYPE=... will tell Halide to choose which type of device to
select for creating the Vulkan instance. Valid options are 'gpu',
'discrete-gpu', 'integrated-gpu', 'virtual-gpu', or 'cpu'. If not set, Halide
will search for the first 'gpu' like device it can find, or fall back to the
first compute device it can find.

HL_VK_ALLOC_CONFIG=... will tell Halide to configure the Vulkan memory
allocator use the given constraints specified as 5x integer values separated by
the appropriate environment variable list delimiter (e.g.
N:N:N:N:N on
Linux/OSX/Posix, or
N;N;N;N;N on Windows). These values correspond to
maximum_pool_size, minimum_block_size, maximum_block_size,
maximum_block_count and nearest_multiple.

The maximum_pool_size constraint will tell Halide to configure the Vulkan
memory allocator to never request more than N megabytes for the entire pool of
allocations for the context. This includes all resource blocks used for
suballocations. Setting this to a non-zero value will limit the amount device
memory used by Halide, which may be useful when other applications and
frameworks are competing for resources. Default is 0 ... meaning no limit.

The minimum_block_size constraint will tell Halide to configure the Vulkan
memory allocator to always request a minimum of N megabytes for a resource
block, which will be used as a pool for suballocations.\
Increasing this value may improve performance while sacrificing the amount of
available device memory. Default is 32MB.

The maximum_block_size constraint will tell Halide to configure the Vulkan
memory allocator to never exceed a maximum of N megabytes for a resource block.
Decreasing this value may free up more memory but may impact performance, and/or
restrict allocations to be unusably small. Default is 0 ... meaning no limit.

The maximum_block_count constraint will tell Halide to configure the Vulkan
memory allocator to never exceed a total of N block allocations.\
Decreasing this value may free up more memory but may impact performance, and/or
restrict allocations. Default is 0 ... meaning no limit.

The nearest_multiple constraint will tell Halide to configure the Vulkan
memory allocator to always round up the requested allocation sizes to the given
integer value. This is useful for architectures that require specific alignments
for subregions allocated within a block. Default is 32 ... setting this to zero
means no constraint.

Debug Environment Variables

The following environment variables may be useful for tracking down potential
issues related to Vulkan:

HL_DEBUG_CODEGEN=3 will print out debug info that includes the SPIR-V code
generator used for Vulkan while it is compiling.

HL_SPIRV_DUMP_FILE=... specifies a file to dump the binary SPIR-V generated
during compilation. Useful for debugging CodeGen issues. Can be inspected,
validated and disassembled via the SPIR-V tools:

https://github.com/KhronosGroup/SPIRV-Tools

In addition to the SPIR-V Tools, you may also wish to install the Khronos
Validation Layers which provide an exhaustive suite of runtime checks that can
be injected by adding
VK_LAYER_KHRONOS_validation to the VK_INSTANCE_LAYERS=
environment variable.

To install the validation layers and the SPIR-V tools on Ubuntu v22.04:


$ sudo apt install vulkan-validationlayers vulkan-validationlayers-dev spirv-tools
text
To test the validation layer, you can prepend your shell command for any Vulkan
enabled binary with the appropriate environment settings. For example, you can
run one of the JIT-enabled correctness tests w/debug output and validation
layers enabled like so:

$ VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation HL_JIT_TARGET=host-vulkan-vk_int8-vk_int16-vk_int64-vk_float16-vk_float64-vk_v13-debug ./build/test/correctness/correctness_hello_gpu
text

Current Status

All correctness tests are now passing on tested configs for Linux & Windows
using the target
host-vulkan-vk_int8-vk_int16-vk_int64-vk_float16-vk_float64-vk_v13 on LLVM
v14.x.

MacOS passes most tests but encounters internal MoltenVK code translation issues
for wide vectors, and ambiguous function calls.

Python apps, tutorials and correctness tests are now passing, but the AOT cases
are skipped since the runtime environment needs to be customized to locate the
platform specific Vulkan loader library.

Android platform support is currently being worked on.

Caveats:

- Other than 32-bit floats and integers, every other data type is optional per
the Vulkan spec
- Float 64-bit types can be enabled, but there aren't any native math functions
available in SPIR-V
- Only one dynamically sized shared memory allocation can be used, but any
number of fixed sized allocation are supported (up to the maximum amount
allowed by the device)

Known TODO:

- Performance tuning of CodeGen and Runtime
- More platform support (Android is work-in-progress, RISC-V, etc)
- Adapt unsupported types to supported types (if missing vk_int8 then promote to
uint32_t)?
- Better debugging utilities using the Vulkan debug hooks.
- Allow debug symbols to be stripped from SPIR-V during codegen to reduce memory
overhead for large kernels.
- Investigate floating point rounding and precision (v1.3 adds more controls)
- Investigate memory model usage (can Halide gain anything from these?)

---

Doc/WebAssembly

WebAssembly Support for Halide

Halide supports WebAssembly (Wasm) code generation from Halide using the LLVM
backend.

As WebAssembly itself is still under active development, Halide's support has
some limitations. Some of the most important:

- Sign-extension operations are enabled by default (but can be avoided via
Target::WasmMvpOnly).
- Non-trapping float-to-int conversions are enabled by default (but can be
avoided via Target::WasmMvpOnly).
- Fixed-width SIMD (128 bit) can be enabled via Target::WasmSimd128.
- Threads have very limited support via Target::WasmThreads; see
below for more details.
- Halide's JIT for Wasm is extremely limited and really useful only for internal
testing purposes.

Additional Tooling Requirements:

- In additional to the usual install of LLVM and clang, you'll need lld.
- Locally-installed version of Emscripten, 1.39.19+

Note that for all of the above, earlier versions might work, but have not been
tested.

AOT Limitations

Halide outputs a Wasm object (.o) or static library (.a) file, much like any
other architecture; to use it, of course, you must link it to suitable calling
code. Additionally, you must link to something that provides an implementation
of
libc; as a practical matter, this means using the Emscripten tool to do
your linking, as it provides the most complete such implementation we're aware
of at this time.

- Halide ahead-of-time tests assume/require that you have Emscripten installed
and available on your system, with the
EMSDK environment variable set
properly.

JIT Limitations

It's important to reiterate that the WebAssembly JIT mode is not (and will never
be) appropriate for anything other than limited self tests, for a number of
reasons:

- It actually uses an interpreter (from the WABT toolkit
[https://github.com/WebAssembly/wabt]) to execute wasm bytecode; not
surprisingly, this can be very slow.
- Wasm effectively runs in a private, 32-bit memory address space; while the
host has access to that entire space, the reverse is not true, and thus any
define_extern calls require copying all halide_buffer_t data across the
Wasm\<->host boundary in both directions. This has severe implications for
existing benchmarks, which don't currently attempt to account for this extra
overhead. (This could possibly be improved by modeling the Wasm JIT's buffer
support as a
device model that would allow lazy copy-on-demand.)
- Host functions used via
define_extern or HalideExtern cannot accept or
return values that are pointer types or 64-bit integer types; this includes
things like
const char * and user_context. Fixing this is tractable, but
is currently omitted as the fix is nontrivial and the tests that are affected
are mostly non-critical. (Note that
halide_buffer_t* is explicitly supported
as a special case, however.)
- Threading isn't supported at all (yet); all
parallel() schedules will be run
serially.
- The
.async() directive isn't supported at all, not even in serial-emulation
mode.
- You can't use
Param<void *> (or any other arbitrary pointer type) with the
Wasm jit.
- You can't use
Func.debug_to_file(), Func.set_custom_do_par_for(),
Func.set_custom_do_task(), or Func.set_custom_allocator().
- The implementation of
malloc() used by the JIT is incredibly simpleminded
and unsuitable for anything other than the most basic of tests.
- GPU usage (or any buffer usage that isn't 100% host-memory) isn't supported at
all yet. (This should be doable, just omitted for now.)

Note that while some of these limitations may be improved in the future, some
are effectively intrinsic to the nature of this problem. Realistically, this JIT
implementation is intended solely for running Halide self-tests (and even then,
a number of them are fundamentally impractical to support in a hosted-Wasm
environment and are disabled).

In sum: don't plan on using Halide JIT mode with Wasm unless you are working on
the Halide library itself.

Using V8 as the interpreter

There is experimental support for using V8 as the interpreter in JIT mode,
rather than WABT. This is enabled by the CMake command line options
-DWITH_V8=ON -DWITH_WABT=OFF (only one of them can be used at a time). You
must build V8 locally V8, then specify the path to the library and headers as
CMake options. This is currently only tested on x86-64-Linux and requires v8
version 9.8.177 as a minimum.

The canonical instructions to build V8 are at
v8.dev, and
there are examples for embedding v8. The process
for Halide is summarized below.

- Install
depot_tools
- Fetch v8 source code (and install required dependencies):


$ gclient
$ mkdir ~/v8 && cd ~/v8
$ fetch v8
$ cd ~/v8/v8
$ git checkout origin/9.8.177
text
- Create a build configuration: tools/dev/v8gen.py x64.release.sample
- Turn off pointer compression:
echo 'v8_enable_pointer_compression = false' >> out.gn/x64.release.sample/args.gn
- Disable the GDB-JIT interface (conflicts with LLVM):
echo 'v8_enable_gdbjit = false' >> out.gn/x64.release.sample/args.gn
- Build the static library:
autoninja -C out.gn/x64.release.sample v8_monolith

With V8 built, we can pass the CMake options:

- V8_INCLUDE_DIR, path to V8 includes, e.g. $HOME/v8/v8/include
-
V8_LIBRARY, path to V8 static library, e.g.
$HOME/v8/v8/out.gn/x64.release.sample/obj/libv8_monolith.a

An example to configure Halide with V8 support, build and run an example test:


$ cd /path/to/halide
$ export HL_TARGET=wasm-32-wasmrt-wasm_simd128
$ export HL_JIT_TARGET=${HL_TARGET}
$ cmake -G Ninja \
-DWITH_WABT=OFF \
-DWITH_V8=ON \
-DV8_INCLUDE_DIR=$HOME/v8/v8/include \
-DV8_LIBRARY=$HOME/v8/v8/out.gn/x64.release.sample/obj/libv8_monolith.a \
-DHalide_TARGET=${HL_TARGET} \
/ other cmake settings here as appropriate /

$ cmake --build .
$ ctest -L "correctness|generator" -j

text

To Use Halide For WebAssembly:

- Ensure WebAssembly is in LLVM_TARGETS_TO_BUILD; if you use the default
(
"all") then it's already present, but otherwise, add it explicitly:


-DLLVM_TARGETS_TO_BUILD="X86;ARM;NVPTX;AArch64;PowerPC;Hexagon;WebAssembly
text

Enabling wasm JIT

If you want to run test_correctness and other interesting parts of the Halide
test suite (and you almost certainly will), you'll need to ensure that LLVM is
built with wasm-ld:

- Ensure that you have lld in LVM_ENABLE_PROJECTS:


cmake -DLLVM_ENABLE_PROJECTS="clang;lld" ...
text
- To run the JIT tests, set HL_JIT_TARGET=wasm-32-wasmrt (possibly adding
wasm_simd128) and run CMake/CTest normally. Note that wasm testing is only
supported under CMake (not via Make).

Enabling wasm AOT

If you want to test ahead-of-time code generation (and you almost certainly
will), you need to install Emscripten locally.

- The simplest way to install is probably via the Emscripten emsdk
(https://emscripten.org/docs/getting_started/downloads.html).

- To run the AOT tests, set HL_TARGET=wasm-32-wasmrt (possibly adding
wasm_simd128) and run CMake/CTest normally. Note that wasm testing is only
supported under CMake (not via Make).

Running benchmarks

The test_performance benchmarks are misleading (and thus useless) for Wasm, as
they include JIT overhead as described elsewhere. Suitable benchmarks for Wasm
will be provided at a later date. (See
https://github.com/halide/Halide/issues/5119 and
https://github.com/halide/Halide/issues/5047 to track progress.)

Using Threads

You can use the wasm_threads feature to enable use of a normal pthread-based
thread pool in Halide code, but with some careful caveats:

- This requires that you use a wasm runtime environment that provides
pthread-compatible wrappers. At this time of this writing, the only
environment known to support this well is Emscripten (when using the
-pthread flag, and compiling for a Web environment). In this configuration,
Emscripten goes to great lengths to make WebWorkers available via the pthreads
API. (You can see an example of this usage in apps/HelloWasm.) Note that not
all wasm runtimes support WebWorkers; generally, you need a full browser
environment to make this work (though some versions of some shell tools may
also support this, e.g. nodejs).
- There is currently no support for using threads in a WASI environment, due to
current limitations in the WASI specification. (We hope that this will improve
in the future.)
- There is no support for using threads in the Halide JIT environment, and no
plans to add them anytime in the near-term future.

Known Limitations And Caveats

- Current trunk LLVM (as of July 2020) doesn't reliably generate all of the Wasm
SIMD ops that are available; see https://github.com/halide/Halide/issues/5130
for tracking information as these are fixed.
- Using the JIT requires that we link the
wasm-ld tool into libHalide; with
some work this need could possibly be eliminated.
- OSX and Linux-x64 have been tested. Windows hasn't; it should be supportable
with some work. (Patches welcome.)
- None of the
apps/ folder has been investigated yet. Many of them should be
supportable with some work. (Patches welcome.)
- We currently use v8/d8 as a test environment for AOT code; we may want to
consider using Node or (better yet) headless Chrome instead (which is probably
required to allow for using threads in AOT code).

Known TODO:

- There's some invasive hackiness in Codgen_LLVM to support the JIT trampolines;
this really should be refactored to be less hacky.
- Can we rework JIT to avoid the need to link in wasm-ld? This might be doable,
as the wasm object files produced by the LLVM backend are close enough to an
executable form that we could likely make it work with some massaging on our
side, but it's not clear whether this would be a bad idea or not (i.e., would
it be unreasonably fragile).
- Buffer-copying overhead in the JIT could possibly be dramatically improved by
modeling the copy as a "device" (i.e.
copy_to_device() would copy from host
-> wasm); this would make the performance benchmarks much more useful.
- Can we support threads in the JIT without an unreasonable amount of work?
Unknown at this point.

---

Doc/WebGPU

WebGPU support for Halide

Halide has work-in-progress support for generating and running WebGPU shaders.
This can be used in conjunction with the WebAssembly backend to bring
GPU-accelerated Halide pipelines to the web.

As the first version of the WebGPU standard is itself still being developed,
Halide's support has some limitations and may only work with certain browsers
and versions of Emscripten.

Known limitations

The following is a non-comprehensive list of known limitations:

- Only 32-bit integers and floats have efficient support.
- 8-bit and 16-bit integers are implemented using emulation. Future extensions
to WGSL will allow them to be implemented more efficiently.
- 64-bit integers and floats will likely remain unsupported until WGSL gains
extensions to support them.
- Wrapping native device buffer handles is not yet implemented.
- You must use CMake/CTest to build/test Halide for WebGPU; using the Makefile
is not supported for WebGPU testing (and probably never will be).

In addition to these functional limitations, the performance of the WebGPU
backend has not yet been evaluated, and so optimizations in the runtime or
device codegen may be required before it becomes profitable to use.

Running with WebAssembly via Emscripten: HL_TARGET=wasm-32-wasmrt-webgpu

_Tested with top-of-tree Emscripten as of 2023-02-23, against Chrome v113._

Halide can generate WebGPU code that can be integrated with WASM code using
Emscripten.

When invoking emcc to link Halide-generated objects, include these flags:
-s USE_WEBGPU=1 -s ASYNCIFY.

Tests that use AOT compilation can be run using a native WebGPU implementation
that has Node.js bindings, such as Dawn.
You must set an environment variable named
HL_WEBGPU_NODE_BINDINGS that has an
absolute path to the bindings to run these tests, e.g.
HL_WEBGPU_NODE_BINDINGS=/path/to/dawn.node.

See below for instructions on building the Dawn Node.js
bindings.

JIT compilation is not supported when using WebGPU with WASM.

Running natively: HL_TARGET=host-webgpu

_Tested with top-of-tree Dawn as of 2023-11-27 \[commit

b5d38fc7dc2a20081312c95e379c4a918df8b7d4\]._

For testing purposes, Halide can also target native WebGPU libraries, such as
Dawn or
wgpu. This is currently the only path that can
run the JIT correctness tests. See below for instructions on
building Dawn.

Note that as of 2023-11-27, wgpu is not supported due to

lacking override support for WGSL

which we require > in order to set GPU block sizes.

When targeting WebGPU with a native target, Halide defaults to looking for a
build of Dawn (with several common names and suffixes); you can override this by
setting the
HL_WEBGPU_NATIVE_LIB environment variable to the absolute path to
the library you want.

Note that it is explicitly legal to define both HL_WEBGPU_NATIVE_LIB and
HL_WEBGPU_NODE_BINDINGS at the same time; the correct executable environment
will be selected based on the Halide target specified.

Note that it is explicitly legal to specify both WEBGPU_NATIVE_LIB and
WEBGPU_NODE_BINDINGS for the same build; the correct executable environment will
be selected based on the Halide target specified.

Setting up Dawn

Building Dawn's Node.js bindings currently requires using CMake.

First,
install
depot_tools
and add it to the
PATH environment variable.

Next, get Dawn and its dependencies:


Clone the repo


git clone https://dawn.googlesource.com/dawn
cd dawn

Bootstrap the gclient configuration with Node.js bindings enabled


cp scripts/standalone-with-node.gclient .gclient

Fetch external dependencies and toolchains with gclient


gclient sync

Other dependencies that must be installed manually:


- golang


text
Finally, build Dawn, enabling both the Node.js bindings and shared libraries:

mkdir -p <build_dir>
cd <build_dir>

cmake <dawn_root_dir> -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DDAWN_BUILD_NODE_BINDINGS=1 \
-DDAWN_ENABLE_PIC=1 \
-DBUILD_SHARED_LIBS=ON

ninja dawn.node webgpu_dawn
`

This will produce the following artifacts:

- Node.js bindings: <build_dir>/dawn.node
- Native library:
<build_dir>/src/dawn/native/libwebgpu_dawn.{so,dylib,dll}

These paths can then be used for the HL_WEBGPU_NODE_BINDINGS and
HL_WEBGPU_NATIVE_LIB environment variables when using Halide.

Updating mini_webgpu.h

The recommended method for updating mini_webgpu.h is to copy the
gen/include/dawn/webgpu.h file from the Dawn build directory, then:

- Restore the // clang-format {off,on} lines.
- Comment out the
#include <std*> lines.
- Remove the
void parameter from the WGPUProc declaration.

This guarantees a version of the WebGPU header that is compatible with Dawn.
When the native API eventually stabilizes, it should be possible to obtain a
header from the
webgpu-native` GitHub organization that will be compatible with
Dawn, wgpu, and Emscripten.

---