github halide/Halide v22.0.0

3 hours ago

Halide 22.0.0

Highlights

  • New scheduling directives: eager_inline(), change_type(), hoist_invariants(), split_storage, stream_loads/stream_stores, aligned_split, a GPU max-register cap, and Pipeline::sever().
  • New profiler with counters, recompute statistics and automatic performance warnings.
  • Halidoscope, a new interactive trace and profiling visualizer.
  • Struct types are now supported in the language.
  • MultiRamp, a better representation of affine vectors (nested ramps), with simplifier and codegen work built on it.
  • ARM: SVE2 and SME2 streaming-SVE support, plus new Neon patterns.
  • Docs and tutorials moved to Sphinx, and all C++ tutorials were ported to Python.
  • Contributor tooling: pre-commit checks, a fuzz-test framework, and Claude skills for scheduling and simplifier rules.

Breaking changes and deprecations

Build and platform

  • Dropped Python 3.9 (#8830). Python 3.12 is now required, and the bindings target the stable ABI via nanobind (#9618).
  • Dropped LLVM 20 (#8998). LLVM 21, 22 and 23 are supported.
  • Removed the Halide_BUNDLE_STATIC feature (#8801).
  • Removed FetchContent from the compiler, hannk and onnx (#8962, #9001, #9005). Dependencies now come from vcpkg or the system.
  • The autoschedulers now have their own CMake package (#9292).
  • Fixed the Halide/HalideHelpers CMake package dependency inversion (#9234).
  • Python packages are split, and there is a standalone AOT runtime (#9272).
  • CUDA: per-capability libdevice bitcode is replaced by a unified libdevice.10.bc (#9226).

C++ API and ABI

  • ABI: halide_type_t no longer has a lanes field. It is now {code, bits, reserved}, and halide_buffer_t keeps its size and layout. Halide::Type is a separate language-level type that must be explicitly converted with as_abi (#9205).
  • For loops now store an inclusive min/max instead of min/extent (#8858).
  • Removed the confusing lanes parameter from Handle. Use .with_lanes() for vectors of handles (#9198).
  • The IRMutator/IRVisitor entry point is now operator(). Custom lowering passes that call custom mutators or visitors may need updating (#9023).
  • Func::in() and Func::clone_in() are now transitive (#9117) and eager (#9277).
  • Removed the CompilerLogger (#9269).
  • Removed the ability to iterate over a Scope (#9594).
  • Renamed CodeGen_Posix to CodeGen_CPU (#9097).
  • Deprecated Target::OS::OSX in favor of MacOS (#9451). The "osx" target string is still accepted, with a warning.
  • Deprecated TailStrategy::Predicate in favor of GuardWithIf (#9446).

Python

  • The axis convention between hl.Buffer and the Python buffer protocol is now consistent across the bindings, PyCallable and the Python extension (#8953, #8931).

Tests

  • Removed test/error/. Its tests are now correctness tests (#9463).
  • Moved the internal self-tests out of libHalide into test/correctness (#9276).

New features

Scheduling

  • eager_inline() (#9256): g.eager_inline({f1, ..., fN}) immediately and destructively inlines the bodies of the listed Funcs into g, in order, so direct calls between them are inlined too.
  • change_type() (#9257): replaces a Func's computation type with another (e.g. float → int32) by computing in the new type and casting back. It uses value-bounds analysis to prove no values change, adds runtime asserts when a reduction might be long enough to overflow, and rewrites products into widening intrinsics where possible. Unsafe float product accumulation can also be fused into an fma (#9578).
  • hoist_invariants() (#9190): like rfactor, but pulls factors that are invariant over a reduction out of the accumulation when the operators obey a distributive (semiring) law. Multi-term reductions are split into one intermediate per term, returned as a FuncVec (#9419). This lets quantized kernels be written naively and scheduled into efficient ones.
  • split_storage (#9460): splits a storage dimension into inner and outer dimensions to express swizzled memory layouts. This helps feed dot-product instructions and matrix-multiply accelerators, and avoids shared-memory bank conflicts. It works on Halide-allocated intermediates only and doesn't compose with fold_storage.
  • stream_loads and stream_stores (#9207): request non-temporal accesses for a stage's loads (all of them, or from named Funcs) and stores, with a fence after streamed stores. On an Apple M3 this fully recovers memcpy performance.
  • Aligned splits (#9409, #9501): split takes an optional offset so the first inner-loop iteration can be aligned to a boundary other than the Func's min. Inner loops now always run from 0 to the factor, which helps constant-bounds analysis.
  • GPU register cap (#9389): a scheduling directive replaces the HL_CUDA_MAX_REGISTERS environment variable for limiting registers per thread. It only affects CUDA.
  • Pipeline::sever() (#9484): replaces a set of Funcs with ImageParam inputs and returns a second pipeline that computes them. Work that would otherwise be redone on every realize(), such as quantizing static weights, becomes ordinary input data computed once.
  • Merged bounds directives (#9410): repeated bound/align_bounds/align_extent calls on the same Var now combine into one entry, so call order no longer affects bounds inference. Overwriting a previously set field emits a warning.
  • Stricter checks: Stage::fuse rejects fusions that illegally reorder dimensions, such as swapping inner and outer RVars (#9186). An explicit fold_storage that the compiler can't apply is now an error instead of being silently ignored (#9201).
  • Name collisions (#9145, #9210): Generator Input<>s and named scalar Params now reserve their names so later Funcs get unique names. If a Func was declared first, you get a clear error instead of a crash in lowering.

Language and IR

  • Struct types (#9416): Type::Struct defines record types with named, optionally array-valued fields, e.g. Type::Struct({{"d", Float(16)}, {"qs", Int(8), 32}}). Inputs and outputs can use packed wire formats, such as blocks of quantized weights, directly, and you read fields with field(x(b), "d"). Records are not meant for efficient computation: you extract fields first.
  • fma intrinsic (#8900): the equivalent of std::fma, for when you need a real fused multiply-add inside a strict_float context, such as for bit-exact transcendentals.
  • round_half_away (#9573): rounds ties away from zero like C's round(), while round() keeps rounding ties to even. It maps to native instructions such as frinta and fcvtas on AArch64, and is exposed in Python as hl.round_half_away.
  • fixed_to_float intrinsic (#9572): float(x) * 2^-k is recognized as a fixed-point to float conversion, which becomes a single scvtf/ucvtf on ARM. It saves a multiply per vector when dequantizing fixed-point data, and is lowered back to cast-and-multiply elsewhere.
  • Better widening (#9195): FindIntrinsics now handles 8-bit integer to float32 widening multiplies, and concat_bits/extract_bits have lowerings for the C and Vulkan backends.
  • MultiRamp (#9113): represents a vector whose lanes are an affine function of a multi-dimensional index, i.e. sums of compatible nested ramps. Multidimensional SIMD targets (and matrix multiplies built from nested vectorized loops) used to scalarize easily. Related work includes the AMX fix (#9122), dense load/store canonicalization (#9535), alignment-aware div/mod (#9536), and load recognition (#9261).
  • Front-end helpers:
    • FuncRef::type() is available, which matters mostly for the Python bindings (#8895).
    • You can get the Vars that have splits applied, for writing generic schedules (#8889).
    • likely, strict_float, require, print and similar non-arithmetic helpers now accept Tuples (#9425).
  • Compiler-developer APIs:
    • mutate_with/visit_with let you write mutators and visitors from lambdas that can capture local state (#8877, #8893).
    • Load::make(type, name, index) and Store::make(name, value, index) short forms are available (#9247).
    • IR nodes have remake methods that copy a node with new children (#9245).
    • Block::to_vector unpacks a Stmt into its non-Block statements (#9384).

Profiling, tracing and debugging

  • New profiler (#9157, #9164, #9344, #9368): the report is organized by loop nesting, with allocations and device copies as their own entries. Cheap runtime counters feed recompute ratios and a set of performance warnings about likely schedule problems, which are especially useful to LLMs scheduling Halide code. Inlined Funcs are counted but can't be sampled.
  • Profiler control (#9423, #9492): JIT code can access profiler state programmatically. halide_profiler_set_json_output sets the JSON path from code, and ProfilerScope accepts a Callable so repeated calls accumulate statistics.
  • Halidoscope (#9356, #9413): an interactive tool for exploring traces and profiler output, with a trace view and a profile view. It complements HalideTraceViz, which shows how a schedule executes but not how much time it takes, and installs via pip.
  • Tracing:
    • Tasks that run in parallel now emit trace events, using a more compact packet format (#9192).
    • trace_bounds_required events report the region bounds inference requires of each Func, so tools can tell required values from ones computed outside that region (#9506).
    • Produce/consume events now sit inside the node body, so a skipped stage emits no unmatched begin event (#9505).
    • JITSharedRuntime::set_trace_file redirects JIT trace output between realizations (#9516).
  • HL_DEBUG_CODEGEN:
    • Tag-based filtering selects a specific debug() call without relying on file and line ranges (#9381). debug(verbosity, "tag") is selected with HL_DEBUG_CODEGEN=tag:name.
    • HL_DEBUG_CODEGEN_LOG_FILE redirects debug output to a file (#9361).
  • IR debugging tools:
    • The Stmt HTML output has better alignment, predicate placement and rainbow parentheses (#9318).
    • lldb, gdb and Natvis share one pretty-printer (#9270).
    • An IRGraph C++ printer emits code that rebuilds an Expr, which is handy for reproducing fuzzer failures (#9042).
    • add_requirement() prints the failing Expr (#9424).
    • HalideTraceViz accepts abbreviated flags (#9160).

Runtime and tooling

  • Thread-pool keep-awake (#9526): halide_thread_pool_keep_awake(bool) is a refcounted switch that keeps idle workers spinning instead of sleeping, so back-to-back small parallel loops with serial work between them don't pay a wake-up each time. RAII holders exist for AOT, JIT and Python (with hl.ThreadPoolKeepAwake()).
  • no_metadata target feature (#9555): omits the _argv() and _metadata() entry points, so pipeline, argument and target names don't end up in the shipped binary.
  • Runtime symbol prefixes (#9240): runtime import, export and internal symbols can each be renamed with a user-chosen prefix. Several differently-namespaced runtimes can then coexist in one process without sharing state.
  • GPU context accessors (#9431): halide_set_*_acquire_context and halide_set_*_release_context are now available for OpenCL, Metal, D3D12, Vulkan and WebGPU, as CUDA already had. They work on Windows, where weak-symbol overrides don't.
  • Generator compile cache (#9266): setting HL_CACHE_DIR makes generators reuse artifacts when the compiler build, targets, parameters and serialized pipeline are all unchanged.
  • Dispatch control (#9320): call_cached_indirect_function can now decline to cache a dispatch result. This allows changing allowed CPU features at runtime for benchmarking, or deferring GPU API setup.
  • Internal linkage (#9556): internal LoweredFuncs now get internal linkage.

Compiler improvements

Simplifier and bounds

  • The simplifier is now idempotent (#8840). It no longer preserves dead LetStmts (#8899).
  • Single-use lets are substituted (#9365), can_prove() can use facts (#9400), and broadcasts are lifted out of pure calls (#9595).
  • New rules cover:
    • And/Or (#8834)
    • general rules (#8902)
    • (x*c0 + y)/c1 (#9520)
    • nested-vector division (#9262)
    • nested selects (#9324, #9339)
    • clamps against shifted clamps (#9342)
    • slices of broadcasts (#9422)
    • common-factor cancellation (#9305)
    • bitwise or/xor bounds (#9585)
  • Solver improvements for min/max (#9162, #9331, #9457) and solve polarity (#9345).
  • Added an upstream simplifier rule verification tool (#9334).
  • Added a cheap hash to Expr nodes to speed up IREquality (#9432).
  • Fixed a quadratic bounds blow-up at the source (#9448).
  • Sliding window bounds are propagated and rewritten correctly (#9150, #9170).

Lowering

  • Storage hoisting is now its own mutator (#8832).
  • skip_stages works with CSE, no longer blows up exponentially on chains of let-bound selects, and no longer narrows loaded by a Select condition (#8980, #9147, #9152).
  • Inlining is faster (#9148), and inject_host_dev_buffer_copies is faster (#8996).
  • No pointless copy-to-host in CPU-only pipelines (#9139).
  • Better vector interleaves (#8925), strided-load staging (#8964, #9363), and no deinterleave for div/mod of a ramp (#9584).
  • specialize() branches are no longer merged away by the simplifier (#9321).

GPU

  • Added a check for GPU register crosstalk (#9325, #9450).
  • Block-level GPU register allocations are shrink-wrapped (#9343), and colliding register allocations are renamed (#9264).
  • GPU shared and heap allocation fusing is deferred to the offload site (#9209).
  • Thread barriers are injected only where threads disagree about memory (#9390).

Backends and targets

ARM

  • SVE2 is much more complete (many PRs by @stevesuzuki-arm):
    • narrow/widen, atomics, strided and dense load/store, shuffles, deinterleave, and reductions
    • the host vector length is now queried (#8812)
    • fallback depends on vector lanes (#9084)
  • SME2 streaming SVE (#9126).
  • Widening FMA (FMLAL) (#8865).
  • By-element sdot/udot tests (#9544).
  • ARM64 prefetches split at the cache line size (#9599).

x86

  • Generic x86-64-v{N} mcpu with explicit mattrs (#9134).
  • Better float-of-widening-op codegen (#8976).
  • AVX10 detection under SDE (#9135).
  • AMX detection requests OS XSAVE permission on Linux (#9199).
  • CPU feature detection is shared between the compiler and runtime (#9267).

Other CPUs

  • RISC-V: saturating add/sub (#9185).
  • Hexagon: more general shuffle flattening and three bug fixes (#8979, #8989).
  • WebAssembly: workaround for an LLVM bug in widening loads (#8981).
  • Restored the V8 backend (#8961).
  • Target feature-flag implications are centralized (#9241).

CUDA

  • The driver chooses the register count (#9260).
  • More compute capabilities (#9259).
  • ptxas is told the largest block size (#9284).
  • Async global→shared copies (#9263).

Metal

  • Precompiled shaders (#8814).
  • gpu_lanes() via SIMD-group shuffles, shared with CUDA (#9514, #9589).
  • A private temp dir for compiler outputs (#9554).
  • Asserts inside kernels warn instead of erroring (#9445).

Vulkan

  • More robust loader search paths (#8891).
  • Version-based extension detection (#8896).
  • Buffer index offsets for arbitrary crops (#8954).
  • Allocator and region fixes (#9223, #9224, #9237, #9238).
  • A bool UBO layout fix (#9547).
  • Boolean min/max SPIR-V lowering (#9494).

Direct3D 12

OpenCL

  • Fixed dense 3-lane vector loads/stores (#9576).

LLVM

  • Support for LLVM 22 and 23 (#8939, #8892, #8897 and others).
  • Halide now pins LLVM versions in-repo (#8942) and tracks LLVM 21.1.8, 22.1.8 and 23.1.4 (#9370).

Bug fixes

  • round now lowers to llvm.roundeven, so it rounds ties to even as documented (#8926).
  • Fixed double-rounding in double → (b)float16 casts (#8906).
  • Fixed fast_integer_divide for a signed numerator with denominator 1 (#9271).
  • Fixed unique_name() returning duplicate names (#9274).
  • Fixed these lossless_cast/lossless_negate issues (#9012, #9166, #9243, #9279).
  • Fixed rfactor issues:
    • argmax/argmin patterns (#8863, #8932)
    • saturating-add patterns (#8839)
    • the split-tail predicate merge (#9518)
  • Uniquified specialization RVars when domains differ (#9541).
  • Fixed alignment tracking and inference (#9104, #9323, #9333).
  • Fixed implicit pure defs with RVar args (#9103).
  • Fixed compute_with sliding (#9149).
  • Fixed vector_reduce simplification (#9031, #9074).
  • Fixed infinite recursion in FindIntrinsics (#9109, #9215).
  • Fixed LICM re-traversal (#9433).
  • Fixed negative-case switch constants (#9290).
  • Fixed TBAA metadata for negative indices (#9310).
  • Fixed a store clobbering a store of another type (#9577).
  • Fixed hoisting of warp shuffles out of if statements (#9513).
  • Fixed predicated load/store of one-lane vectors (#8924).
  • Fixed an async thread-pool deadlock from semaphore-blocked B-team workers (#9372).
  • Fixed a lost wakeup when a stolen serial task is put back (#9534).
  • Bounded stack depth from nested parallelism (#9378).
  • Size-one parallel loops stay in the task system (#9454).
  • _Float16 is enabled only when the compiler supports it (#9189).
  • Disabled compiler stack switching in Python 3.14 (#9168).

Security and robustness hardening

A large batch of fuzzer-driven fixes added bounds and size checks to:

Build system and packaging

  • Added add_halide_stub (#9093), add_halide_library(ENVIRONMENT ...) (#9545), and lazy loading of HalideAutoschedulers for PLUGINS (#9304).
  • Fixed universal2 macOS builds (#9469).
  • Fixed per-subtarget entrypoints in multitarget headers (#9315).
  • Fixed Windows pip packaging and libHalide shadowing (#9233).
  • Fixed PCH visibility (#9455).
  • Filtered std:: instantiations out of Halide.dll's exports (#9489).
  • JIT-compiled CRT calls bind to libHalide's CRT on Windows (#9498).
  • Fixed the macOS Homebrew header ordering (#9562).
  • Fixed a getauxval redeclaration on aarch64 Linux (#9548).
  • Fixed wabt 1.0.42 compatibility (#9473).
  • Added Makefile features to CMake (#9227), and removed -lz from the Makefiles (#9525).
  • Python bindings:
  • run_process() replaces system() (#8940).

Documentation, tutorials and apps

  • Docs and tutorial sites migrated to Sphinx (#9347, #9367, #9369).
  • All C++ tutorials were ported to Python (#9351). Tutorial text was improved (#9348), and Python-only lessons are rendered (#9408).
  • Revised the build documentation (#9006, #9475) and the fuzz-testing docs (#9125).
  • Added contribution guidelines and templates (#8975), AI-disclosure requirements (#8625, #9453), CLAUDE.md (#9275), and Claude skills for scheduling and simplifier rules (#9435, #9447).
  • New and revived apps and examples:
  • The FFT app fix (#9449) and Apple M3 Max matmul tuning (#9183) are also in.

Contributor infrastructure

  • Linting: reworked clang-tidy (#8816) with many new checks, plus pre-commit with shfmt/shellcheck, codespell, mdformat, CMake style checks and cmtk formatting.
  • Fuzzing: a tiny fuzz-test framework (#9050), a shared expression generator (#9048), a solve fuzzer (#9105), and fuzzers moved to test/fuzz (#9053).
  • CI:
    • macOS, ARM Linux, x86-32 and Makefile testing moved to GitHub Actions
    • a code coverage workflow, actionlint, and -Werror on CI
    • a devcontainer
    • an automated halide-llvm upgrade bot
    • a public nightly PyPI index

New contributors

Welcome and thank you to @stevenraphael, @soufianekhiat, @rajeshgangam, @soma0212, @Nashit-h, @johningve, @gregcotten, @prabalghosh, @arcusbuilds and @parkerziegler.

Full Changelog: v21.0.0...v22.0.0

Don't miss a new Halide release

NewReleases is sending notifications on new releases.