Feature - Metal GPU backend for macOS (Apple Silicon) - #324
Open
rflechner wants to merge 99 commits into
Open
Conversation
…rnel and metallib files
…A and in Metal version.
…implementation notes
…M projection layout and boundary handling
…V-cache factory logic for improved maintainability and functional consistency
… version is faster)
Add `xcuserdata/` to .gitignore to prevent platform-specific user files generated by Xcode from being tracked in the repository. This is relevant for macOS Metal backend development.
Expands `.gitignore` to include the `xcuserdata/` directory specific to the `dotllm_metal.xcodeproj` for the Metal backend. This prevents platform-specific user files generated by Xcode from being committed. Additionally, ignores `.claude/settings.local.json` to exclude local AI assistant configurations from the repository.
…d Metal tests run under Windows and Linux
rflechner
marked this pull request as ready for review
June 16, 2026 06:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[Backend] Metal GPU backend for macOS (Apple Silicon)
Thank you
Thank you for open-sourcing this project — the initiative is genuinely exciting and inspired me to use DotLLM.
Origin of the PR
It all started from a simple desire: running the samples on my MacBook. With no Metal backend available, the engine fell back to the CPU — which was expectedly much slower than what the Apple Silicon GPU could offer. Rather than living with that, I decided to implement the missing backend.
Summary
This PR adds a full GPU inference backend based on Apple Metal, enabling transformer model execution on Apple Silicon chips.
It is an end-to-end addition: from primitive Metal shaders all the way to a complete transformer forward pass with KV-cache and quantization, following the same architecture as the existing CUDA backend.
Status
Status:
What was added
DotLLM.MetalprojectA new project in the solution, with:
bridge.mm) exposing a flat C API for .NET P/Invoke.dotllm_metal.h) — stable API, same philosophy as the CUDA side.build.shscript that compiles the.metalfiles into a pre-compiled.metallibarchive.Build prerequisites
Since Xcode 26, the Metal toolchain is no longer bundled with Xcode by default: it is a separately downloadable component. Before running
build.sh, it must therefore be installed (otherwisexcrun metalfails withmissing Metal Toolchain):Note that an Xcode update may remove this component and require re-running the command. This is also something to plan for in the future GitHub Action (see below).
Kernels ported from CUDA to Metal
Each Metal kernel is a port of its CUDA counterpart in the project (
native/kernels/*.cu).The split into
.metalfiles mirrors the source.cufiles 1-for-1 (same file name, same kernel names, same variable names and reduction structure), to make side-by-side review easy.add.cu,add_f32.cuadd.metal,add_f32.metaladd_f16,add_f32,add_f32_f16attention.cu,attention_f32.cuattention.metal,attention_f32.metalattention_f16,attention_f32bias_add.cu,bias_add_f32.cubias_add.metal,bias_add_f32.metalbias_add_f16,bias_add_f32convert.cuconvert.metalconvert_f16_to_f32,convert_f32_to_f16dequant.cudequant.metalembedding.cu,embedding_f32out.cuembedding.metal,embedding_f32out.metal*_f16outand*_f32outvariantsfused_add_rmsnorm.cufused_add_rmsnorm.metalfused_add_rmsnorm_f16per_head_rmsnorm.cu,per_head_rmsnorm_f32.cuper_head_rmsnorm.metal,per_head_rmsnorm_f32.metalper_head_rmsnorm_f16,per_head_rmsnorm_f32quant_kv.cuquant_kv.metalquant_f16_to_q8_0,quant_f16_to_q4_0quantized_gemv.cu,quantized_gemv_f32in.cuquantized_gemv.metal,quantized_gemv_f32in.metalrmsnorm.cu,rmsnorm_f32.cu,rmsnorm_f32in.curmsnorm.metal,rmsnorm_f32.metal,rmsnorm_f32in.metalrmsnorm_f16,rmsnorm_f32,rmsnorm_f32in_f16outrope.cu,rope_f32.curope.metal,rope_f32.metalrope_f16,rope_f32softmax.cusoftmax.metalsoftmax_f16swiglu.cu,swiglu_f32.cuswiglu.metal,swiglu_f32.metalswiglu_f16,swiglu_f32CUDA → Metal mapping of the recurring constructs:
blockIdx.x[[threadgroup_position_in_grid]]threadIdx.x[[thread_position_in_threadgroup]]blockDim.x[[threads_per_threadgroup]]__shfl_down_syncsimd_shuffle_down__shared__threadgroup__syncthreads()threadgroup_barrierOne intentional deviation, documented in the code:
embedding_lookup_q6_k_f16out(embedding.metal) is a Metal-only extension, with no CUDA counterpart: the test model's embedding (Ministral-3B) is stored in Q6_K, a case the CUDA backend never had to handle.Metal inference pipeline
MetalWeights— loads weights from GGUF with several strategiesMetalForwardState/IMetalForwardState— scratch buffers in unified memory (GPU and CPU variants)MetalTransformerModel— full transformer forward passMetalModelLoader— GGUF loading adapted to the Metal backendMetalContext— lifecycle management of the shaders and the compiled pipelineGPU KV-cache
A persistent KV-cache in Metal GPU memory (
metal_kv_cache.mm), with dedicated tests.Memory optimizations
MTLBufferwith no intermediate copyhalf4) and SwiGLU (half2)Integration
--device metalCLI option to select the backendDotLLM.Sample.ServersampleModelRunContextas a unified return type for model loading.csproj).claude/settings.local.json) was removed from git tracking: it is specific to each dev machine, so committing it would mix everyone's settings and add a risk of leaking local information (paths, models). It stays ignored via.gitignore.Acknowledgements
This implementation was developed with assistance from Claude and ChatGPT, which helped accelerate learning and porting from CUDA to Metal.
Test environment
Tested only on an Apple M4 Max with 36 GB of RAM, and with a single model: Ministral-3-3B-Instruct-2512-Q4_K_M.
I tried other models (e.g. Gemma), but ran into errors about not-yet-implemented tensors. This is not specific to the Metal backend: I got exactly the same behavior with the Windows version (CUDA backend). It is therefore a pre-existing limitation of those architectures' support, not a regression introduced by this PR.
I was thus unable to validate other models or other quantization formats.
Remaining work / points of attention
The compiled Metal binaries (
.metallib) are not versioned in the repository.A GitHub Action running on a macOS runner and calling
build.shwill probably be needed to compile them on each build.What is not in this PR
Tests
Each kernel has unit tests comparing the Metal output against a scalar CPU reference, with an explicit numerical tolerance.
The Metal KV-cache is also covered by tests.
Notes for the reviewer
The code structure deliberately follows the same organization as the CUDA backend to ease side-by-side comparison. The Obj-C bridge exposes only a flat C API — same philosophy as
dotllm_native.h.The
README.mdinnative/metal/serves as a CUDA → Metal translation guide: it documents the mechanical substitutions used throughout the port (thread identity, warp/SIMD-group reductions, math intrinsics, shared memory, pointer arithmetic, grid-stride loops…). It is the reference to read before porting a new kernel, and it illustrates the mapping on the most complex cases (RoPE, attention, quantized GEMV).