An out-of-tree GPU backend that lets you write a constrained subset of Numba-style Python kernels and run them on the GPU of Apple-silicon Macs through Metal.
import numpy as np
from numba_metal import metal
@metal.jit
def vector_add(a, b, output):
i = metal.grid(1)
if i < output.size:
output[i] = a[i] + b[i]
a = np.arange(1_000_000, dtype=np.float32)
b = np.arange(1_000_000, dtype=np.float32)
d_a = metal.to_device(a)
d_b = metal.to_device(b)
d_output = metal.device_array_like(a)
threads = 256
blocks = (a.size + threads - 1) // threads
vector_add[blocks, threads](d_a, d_b, d_output)
metal.synchronize()
output = d_output.copy_to_host()
This is a real compiler backend, not a wrapper: kernels are compiled from
Numba's typed intermediate representation into Metal Shading Language
(MSL), compiled by Apple's Metal shader compiler, and executed by the GPU.
See docs/architecture.md for how the pipeline works.
Read the research report — a longer, paper-structured writeup of the compiler architecture, the full capability surface, a controlled roofline-model demonstration on Apple Silicon (an identical stencil's memory-access pattern degrades under 100x scaling in one arithmetic-intensity regime and improves 13x+ in another), and a full, undiscarded account of a compiler fix that passed 500 randomized tests and the entire suite before being found to silently miscompile a real production kernel and fully reverted. Includes an explicit scope statement on what this document is and is not.
This project is an early, deliberately narrow-scope MVP. It supports one
dialect of one language subset, on one platform, for the operations
documented in docs/supported-features.md. It is not a drop-in
replacement for numba.cuda, and it does not aim for broad Python or
NumPy compatibility. Read docs/limitations.md before relying on it for
anything beyond experimentation.
docs/installation.md for the exact supported range.pyproject.toml).docs/installation.md).On any other platform or OS version, numba-metal fails immediately with
a clear error -- it never silently falls back to the CPU.
See docs/installation.md for full instructions, including creating a
virtual environment, installing the Metal toolchain, and verifying your
machine is capable before writing any kernels.
See docs/supported-features.md for the full compatibility matrix
(Python syntax, scalar types, operators, math functions, memory
operations, launch dimensions). In short: scalar arithmetic/comparison/
boolean operators, if/if-else, for x in range(...) (with break/
continue), 1D and 2D grids, 1D arrays with flattened multidimensional
indexing, and a documented subset of abs/min/max/math.sqrt/
math.exp/math.log/math.sin/math.cos, over float32/int32/
uint32/bool (with float16/int64 also supported).
View the benchmark report — a plain-language walkthrough of what each benchmark actually computes, side-by-side timings against NumPy and Numba, and honest wins/losses (open the file directly in a browser, or view it rendered on GitHub via htmlpreview).
Nine benchmark programs (benchmarks/*.py) compare plain Python, NumPy,
Numba CPU (@njit), and numba-metal, at multiple problem sizes, with
correctness checks and cold/warm/transfer-inclusive timing. Five are wired
into one runner; three more (cyclist aerodynamics, implied volatility, Asian
option pricing) measure their own CPU-vs-Metal comparison directly since
they don't share run_all.py's shape; the ninth
(device_function_compile_cache.py) is compiler infrastructure rather than
a numerical workload — it measures cold-compile time, not runtime, for
kernels sharing a @metal.device_func:
python benchmarks/run_all.py # text report (5 core benchmarks)
python benchmarks/run_all.py --json out.json # + machine-readable JSON
python benchmarks/run_all.py --quick # smaller sizes, for a fast check
python benchmarks/cyclist_aerodynamics.py
python benchmarks/implied_volatility.py
python benchmarks/asian_option_pricing.py
python benchmarks/device_function_compile_cache.py
See docs/benchmarking.md for methodology and how to interpret results.
No speedup numbers are hard-coded anywhere in this repository; every
number reported by these scripts is measured on the machine you run them
on.
The numba-metal advisor CLI answers "which of my functions would
actually benefit from this?" -- as a terminal-only static scanner and
profiler, never a browser or notebook UI.
numba-metal advisor scan . # find candidates, runs nothing
numba-metal advisor compare my_workload.py # measure CPU vs. Metal, with correctness checks
[HIGH POTENTIAL] mandelbrot.py:97 _make_metal_kernel.<locals>.metal_kernel
Why:
- Already running on numba-metal (@metal.jit)
Recommendation (USE_METAL): measured 15.38x faster (steady_state), and
results match the CPU reference within tolerance.
Static analysis never claims a speedup by itself -- only compare,
which actually runs your code and checks correctness first, can say
that. See docs/advisor.md for the full command reference, how
compatibility and opportunity scoring work, and how to read the ASCII
flame graphs and CPU/GPU timeline.
UnsupportedFeatureError naming the offending code. numba-metal never
quietly executes your kernel through NumPy, Numba's CPU target, a
Python loop, or any other substitute.docs/limitations.md for exactly what this means numerically.docs/architecture.md.docs/limitations.md before using it for
anything where numerical correctness matters beyond experimentation.See CONTRIBUTING.md.
BSD 2-Clause. See LICENSE.
47 commits
Hacker News (1)
Python
99.6%
An out-of-tree GPU backend that lets you write a constrained subset of Numba-style Python kernels and run them on the GPU of Apple-silicon Macs through Metal.
import numpy as np
from numba_metal import metal
@metal.jit
def vector_add(a, b, output):
i = metal.grid(1)
if i < output.size:
output[i] = a[i] + b[i]
a = np.arange(1_000_000, dtype=np.float32)
b = np.arange(1_000_000, dtype=np.float32)
d_a = metal.to_device(a)
d_b = metal.to_device(b)
d_output = metal.device_array_like(a)
threads = 256
blocks = (a.size + threads - 1) // threads
vector_add[blocks, threads](d_a, d_b, d_output)
metal.synchronize()
output = d_output.copy_to_host()
This is a real compiler backend, not a wrapper: kernels are compiled from
Numba's typed intermediate representation into Metal Shading Language
(MSL), compiled by Apple's Metal shader compiler, and executed by the GPU.
See docs/architecture.md for how the pipeline works.
Read the research report — a longer, paper-structured writeup of the compiler architecture, the full capability surface, a controlled roofline-model demonstration on Apple Silicon (an identical stencil's memory-access pattern degrades under 100x scaling in one arithmetic-intensity regime and improves 13x+ in another), and a full, undiscarded account of a compiler fix that passed 500 randomized tests and the entire suite before being found to silently miscompile a real production kernel and fully reverted. Includes an explicit scope statement on what this document is and is not.
This project is an early, deliberately narrow-scope MVP. It supports one
dialect of one language subset, on one platform, for the operations
documented in docs/supported-features.md. It is not a drop-in
replacement for numba.cuda, and it does not aim for broad Python or
NumPy compatibility. Read docs/limitations.md before relying on it for
anything beyond experimentation.
docs/installation.md for the exact supported range.pyproject.toml).docs/installation.md).On any other platform or OS version, numba-metal fails immediately with
a clear error -- it never silently falls back to the CPU.
See docs/installation.md for full instructions, including creating a
virtual environment, installing the Metal toolchain, and verifying your
machine is capable before writing any kernels.
See docs/supported-features.md for the full compatibility matrix
(Python syntax, scalar types, operators, math functions, memory
operations, launch dimensions). In short: scalar arithmetic/comparison/
boolean operators, if/if-else, for x in range(...) (with break/
continue), 1D and 2D grids, 1D arrays with flattened multidimensional
indexing, and a documented subset of abs/min/max/math.sqrt/
math.exp/math.log/math.sin/math.cos, over float32/int32/
uint32/bool (with float16/int64 also supported).
View the benchmark report — a plain-language walkthrough of what each benchmark actually computes, side-by-side timings against NumPy and Numba, and honest wins/losses (open the file directly in a browser, or view it rendered on GitHub via htmlpreview).
Nine benchmark programs (benchmarks/*.py) compare plain Python, NumPy,
Numba CPU (@njit), and numba-metal, at multiple problem sizes, with
correctness checks and cold/warm/transfer-inclusive timing. Five are wired
into one runner; three more (cyclist aerodynamics, implied volatility, Asian
option pricing) measure their own CPU-vs-Metal comparison directly since
they don't share run_all.py's shape; the ninth
(device_function_compile_cache.py) is compiler infrastructure rather than
a numerical workload — it measures cold-compile time, not runtime, for
kernels sharing a @metal.device_func:
python benchmarks/run_all.py # text report (5 core benchmarks)
python benchmarks/run_all.py --json out.json # + machine-readable JSON
python benchmarks/run_all.py --quick # smaller sizes, for a fast check
python benchmarks/cyclist_aerodynamics.py
python benchmarks/implied_volatility.py
python benchmarks/asian_option_pricing.py
python benchmarks/device_function_compile_cache.py
See docs/benchmarking.md for methodology and how to interpret results.
No speedup numbers are hard-coded anywhere in this repository; every
number reported by these scripts is measured on the machine you run them
on.
The numba-metal advisor CLI answers "which of my functions would
actually benefit from this?" -- as a terminal-only static scanner and
profiler, never a browser or notebook UI.
numba-metal advisor scan . # find candidates, runs nothing
numba-metal advisor compare my_workload.py # measure CPU vs. Metal, with correctness checks
[HIGH POTENTIAL] mandelbrot.py:97 _make_metal_kernel.<locals>.metal_kernel
Why:
- Already running on numba-metal (@metal.jit)
Recommendation (USE_METAL): measured 15.38x faster (steady_state), and
results match the CPU reference within tolerance.
Static analysis never claims a speedup by itself -- only compare,
which actually runs your code and checks correctness first, can say
that. See docs/advisor.md for the full command reference, how
compatibility and opportunity scoring work, and how to read the ASCII
flame graphs and CPU/GPU timeline.
UnsupportedFeatureError naming the offending code. numba-metal never
quietly executes your kernel through NumPy, Numba's CPU target, a
Python loop, or any other substitute.docs/limitations.md for exactly what this means numerically.docs/architecture.md.docs/limitations.md before using it for
anything where numerical correctness matters beyond experimentation.See CONTRIBUTING.md.
BSD 2-Clause. See LICENSE.
Hacker News (1)
47 commits
Python
99.6%