Skip to content

Add GPUToolbox.Overlays with shared device overrides of Base - #21

Merged
maleadt merged 2 commits into
mainfrom
tb/math
Sep 23, 2026
Merged

maleadt merged 2 commits into
mainfrom
tb/math

Conversation

@maleadt

@maleadt maleadt commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

GPU back-ends replace Base methods that don't work well on GPUs with overlay methods in their own method table, and many of these overrides are the same across back-ends. This adds GPUToolbox.Overlays, a home for such overrides: method tables that back-ends stack underneath their own using GPUCompiler.StackedMethodTable in GPUCompiler.method_table_view (like OpenCL.jl and oneAPI.jl already do with SPIRVIntrinsics' table). The module docstring (also a new docs page) describes the design: lookup order (back-end first, then family tables like SPIRVIntrinsics, then these tables, then Base), the rules shared overrides follow so that the order only affects speed or accuracy and not correctness, and how to use them.

Overlays.float64_overrides

The first table replaces Base methods that use Float64 to compute single- and half-precision results, which fails to compile on devices without Float64 support (Metal, many Intel GPUs) and is slow where Float64 is emulated or has low throughput:

  • div, fld, cld and friends of Float32 values on Julia 1.12 and 1.13, using the generic implementation Base master switched to (Fix incorrect results from floating-point division (fld, cld, div) JuliaLang/julia#60497), while keeping 1.12's results for non-finite operands and zero quotients;
  • sind, cosd and friends of Float32 values, keeping Base's argument reduction but evaluating sin/cos in single precision;
  • sincospi, and thus cispi and complex sinpi/cospi;
  • hypot of Float32 values, and thus abs of complex numbers;
  • ^(::Float32, ::Integer);
  • division and inversion of ComplexF32, using a port of Base's robust ComplexF64 division (Baudin & Smith) with thresholds that keep intermediate values out of the subnormal range, which GPUs may flush to zero; zero components get the same signs as Base's;
  • comparisons between Float32/Float16 values and 32-bit integers.

These only fix Base's generic code: back-ends still provide Float64-free elementary functions like sin themselves, as documented. The plan is for Metal.jl to always stack this table, and for OpenCL.jl and oneAPI.jl to stack it on devices without Float64 support; those PRs follow once this is released. On Metal, it fixes JuliaGPU/Metal.jl#972, #973 and #871, and makes about 30 more Base functions compile.

Overlays.audit

Overlays.audit(tables...) checks a stack of method tables for overrides of Base functions that don't match any Base method (e.g. because Base changed a signature), and for overrides hidden by a table higher up the stack. GPUToolbox's tests run it on its own tables. Running it on the back-ends' tables found a dead override in every back-end (removed in JuliaGPU/Metal.jl#974, JuliaGPU/OpenCL.jl#494, JuliaGPU/KernelAbstractions.jl#789, JuliaGPU/oneAPI.jl#638, JuliaGPU/CUDA.jl#3284, JuliaGPU/AMDGPU.jl#1097).

Testing

The overrides are tested on the CPU by invoking their methods directly (Julia 1.12+); calls made by an override then resolve to Base, so the composed behavior is tested in the back-ends. With Metal.jl stacking the table, its math tests pass on Julia 1.10 through 1.13, and a comparison against Base on an M1 GPU matched div in all rounding modes (where the quotient is exact), with integer powers within 2.6 ulp, complex division within about 1.5 eps (normwise; 1.53 eps worst case on the CPU), sind/cosd/tand within 4 ulp and hypot within 1 ulp. OpenCL.jl compiles these functions without Float64 on PoCL.

Bumps the version to 3.1.0.

Base computes some single-precision math in double precision, which
fails to compile for devices without Float64 support (and is slow on
devices where it is emulated or has a low throughput). This adds
`GPUToolbox.Overlays`, for method tables with device overrides that
back-ends can share by stacking them underneath their own method table.
The module's documentation describes the lookup order and the rules the
overrides follow.

The first table, `Overlays.float64_overrides`, keeps these computations
in single precision:

- `div` and friends on Float32 (Julia 1.12 and 1.13)
- `sind`, `cosd` and friends on Float32
- `sincospi`, and thus `cispi` and complex `sinpi`/`cospi`
- `hypot` on Float32, and thus `abs` of complex numbers
- `^(::Float32, ::Integer)`
- division and inversion of ComplexF32
- comparisons between Float32/Float16 and 32-bit integers

These only fix Base's generic code: back-ends still need to provide
native implementations of elementary functions like `sin`.

`Overlays.audit` checks a stack of tables for overrides that don't match
any Base method, or that are hidden by a table higher up the stack.
@christiangnrd

Copy link
Copy Markdown
Member

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

div(::Float32, ::Float32) uses Float64 on Julia ≥ 1.12

2 participants