build: revamp build for better developer UX

This commit is contained in:
Daniel Hiltgen 2026-05-15 14:22:50 -07:00
parent 5bd580c525
commit 4feba5ad7d
14 changed files with 959 additions and 332 deletions

View file

@ -24,7 +24,7 @@ jobs:
run: |
echo GOFLAGS="'-ldflags=-w -s \"-X=github.com/ollama/ollama/version.Version=${GITHUB_REF_NAME#v}\" \"-X=github.com/ollama/ollama/server.mode=release\"'" | tee -a $GITHUB_OUTPUT
echo VERSION="${GITHUB_REF_NAME#v}" | tee -a $GITHUB_OUTPUT
echo vendorsha=$(cat LLAMA_CPP_VERSION) | tee -a $GITHUB_OUTPUT
echo vendorsha=$(cat LLAMA_CPP_VERSION)-$(cat MLX_VERSION)-$(cat MLX_C_VERSION) | tee -a $GITHUB_OUTPUT
darwin-build:
runs-on: macos-26-xlarge

View file

@ -38,7 +38,21 @@ jobs:
| xargs python3 -c "import sys; from pathlib import Path; print(any(Path(x).match(glob) for x in sys.argv[1:] for glob in '$*'.split(' ')))"
}
echo changed=$(changed 'llama/server/**/*' 'llama/compat/**/*' 'LLAMA_CPP_VERSION' 'llama/llama.cpp/**/*' 'ml/backend/ggml/ggml/**/*' '.github/**/*') | tee -a $GITHUB_OUTPUT
echo changed=$(changed \
'CMakeLists.txt' \
'CMakePresets.json' \
'cmake/**' \
'cmake/**/*' \
'llama/server/**/*' \
'llama/compat/**/*' \
'LLAMA_CPP_VERSION' \
'MLX_VERSION' \
'MLX_C_VERSION' \
'llama/llama.cpp/**/*' \
'ml/backend/ggml/ggml/**/*' \
'x/imagegen/mlx/**' \
'x/imagegen/mlx/**/*' \
'.github/**/*') | tee -a $GITHUB_OUTPUT
echo app_changed=$(changed 'app/**' 'app/**/*') | tee -a $GITHUB_OUTPUT
echo enginehash=$(cat LLAMA_CPP_VERSION)-$(cat MLX_VERSION)-$(cat MLX_C_VERSION) | tee -a $GITHUB_OUTPUT
@ -68,32 +82,38 @@ jobs:
matrix:
include:
- preset: CPU
server_preset: cpu
server_build_dir: build/llama-server-cpu
superbuild_target: ollama-local
superbuild_dir: build/local-superbuild
superbuild_args: '-DMLX_ENGINE=OFF'
expected_payload: lib/ollama/llama-server
install-go: true
- preset: CUDA
container: nvidia/cuda:13.0.0-devel-ubuntu22.04
flags: '-DCMAKE_CUDA_ARCHITECTURES=87'
server_preset: cuda-v13
server_build_dir: build/llama-server-cuda-v13
superbuild_target: ollama-llama-server-cuda-v13
superbuild_dir: build/local-superbuild-cuda-v13
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=cuda-v13 -DCMAKE_CUDA_ARCHITECTURES=87'
expected_payload: lib/ollama/cuda_v13/libggml-cuda.so
- preset: ROCm
container: rocm/dev-ubuntu-22.04:7.2.1
extra-packages: rocm-libs
flags: '-DAMDGPU_TARGETS=gfx1010 -DCMAKE_PREFIX_PATH=/opt/rocm'
server_preset: rocm
server_build_dir: build/llama-server-rocm
superbuild_target: ollama-llama-server-rocm
superbuild_dir: build/local-superbuild-rocm
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=rocm -DAMDGPU_TARGETS=gfx1010 -DCMAKE_PREFIX_PATH=/opt/rocm'
expected_payload: lib/ollama/rocm/libggml-hip.so
- preset: Vulkan
container: ubuntu:22.04
extra-packages: >
mesa-vulkan-drivers vulkan-tools
libvulkan1 libvulkan-dev
vulkan-sdk cmake ccache g++ make
server_preset: vulkan
server_build_dir: build/llama-server-vulkan
superbuild_target: ollama-llama-server-vulkan
superbuild_dir: build/local-superbuild-vulkan
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=vulkan'
expected_payload: lib/ollama/vulkan/libggml-vulkan.so
- preset: 'MLX CUDA 13'
container: nvidia/cuda:13.0.0-devel-ubuntu22.04
extra-packages: libcudnn9-dev-cuda-13 libopenblas-dev liblapack-dev liblapacke-dev git curl
flags: '-DCMAKE_CUDA_ARCHITECTURES=87 -DMLX_CUDA_ARCHITECTURES=80-virtual -DBLAS_INCLUDE_DIRS=/usr/include/x86_64-linux-gnu -DLAPACK_INCLUDE_DIRS=/usr/include/x86_64-linux-gnu'
install-go: true
mlx_preset: 'MLX CUDA 13'
runs-on: linux
container: ${{ matrix.container }}
@ -122,18 +142,30 @@ jobs:
- if: matrix.install-go
name: Install Go
run: |
[ -n "${{ matrix.container }}" ] || sudo=sudo
GO_VERSION=$(awk '/^go / { print $2 }' go.mod)
curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" | tar xz -C /usr/local
curl -fsSL "https://golang.org/dl/go${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" | $sudo tar xz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- uses: actions/cache@v4
with:
path: /github/home/.cache/ccache
key: ccache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.preset }}-${{ needs.changes.outputs.enginehash }}
- name: Build llama-server
if: matrix.server_preset
- name: Build native superbuild
if: matrix.superbuild_target
run: |
cmake -S llama/server --preset "${{ matrix.server_preset }}" ${{ matrix.flags }}
cmake --build "${{ matrix.server_build_dir }}" -- -l $(nproc)
cmake -S . -B "${{ matrix.superbuild_dir }}" ${{ matrix.superbuild_args }}
CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) cmake --build "${{ matrix.superbuild_dir }}" --target "${{ matrix.superbuild_target }}" -- -l $(nproc)
test -e "${{ matrix.superbuild_dir }}/${{ matrix.expected_payload }}"
- name: Verify local superbuild install
if: matrix.superbuild_target == 'ollama-local'
run: |
./ollama --version
"${{ matrix.superbuild_dir }}/lib/ollama/llama-server" --version
test -x "${{ matrix.superbuild_dir }}/lib/ollama/llama-quantize"
cmake --install "${{ matrix.superbuild_dir }}" --component ollama-local --prefix "$RUNNER_TEMP/ollama-local"
"$RUNNER_TEMP/ollama-local/bin/ollama" --version
"$RUNNER_TEMP/ollama-local/lib/ollama/llama-server" --version
test -x "$RUNNER_TEMP/ollama-local/lib/ollama/llama-quantize"
- name: Build MLX
if: matrix.mlx_preset
run: |
@ -147,13 +179,16 @@ jobs:
matrix:
include:
- preset: CPU
server_preset: cpu
server_build_dir: build\llama-server-cpu
superbuild_target: ollama-local
superbuild_dir: build\local-superbuild
superbuild_args: '-DMLX_ENGINE=OFF'
expected_payload: lib\ollama\llama-server.exe
- preset: CUDA
install: https://developer.download.nvidia.com/compute/cuda/13.0.0/local_installers/cuda_13.0.0_windows.exe
flags: '-DCMAKE_CUDA_ARCHITECTURES=80'
server_preset: cuda-v13-windows
server_build_dir: build\llama-server-cuda-v13
superbuild_target: ollama-llama-server-cuda-v13-windows
superbuild_dir: build\local-superbuild-cuda-v13
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=cuda-v13-windows -DCMAKE_CUDA_ARCHITECTURES=80'
expected_payload: lib\ollama\cuda_v13\ggml-cuda.dll
cuda-components:
- '"cudart"'
- '"nvcc"'
@ -166,13 +201,16 @@ jobs:
- preset: ROCm
install: https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-26.Q1-Win11-For-HIP.exe
rocm-version: '7.1'
flags: '-DAMDGPU_TARGETS=gfx1010'
server_preset: rocm-windows
server_build_dir: build\llama-server-rocm
superbuild_target: ollama-llama-server-rocm-windows
superbuild_dir: build\local-superbuild-rocm
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=rocm-windows -DAMDGPU_TARGETS=gfx1010'
expected_payload: lib\ollama\rocm\ggml-hip.dll
- preset: Vulkan
install: https://sdk.lunarg.com/sdk/download/1.4.321.1/windows/vulkansdk-windows-X64-1.4.321.1.exe
server_preset: vulkan
server_build_dir: build\llama-server-vulkan
superbuild_target: ollama-llama-server-vulkan
superbuild_dir: build\local-superbuild-vulkan
superbuild_args: '-DOLLAMA_LLAMA_SERVER_BACKENDS=vulkan'
expected_payload: lib\ollama\vulkan\ggml-vulkan.dll
- preset: 'MLX CUDA 13'
install: https://developer.download.nvidia.com/compute/cuda/13.0.0/local_installers/cuda_13.0.0_windows.exe
cudnn-install: https://developer.download.nvidia.com/compute/cudnn/redist/cudnn/windows-x86_64/cudnn-windows-x86_64-9.18.1.3_cuda13-archive.zip
@ -275,19 +313,44 @@ jobs:
C:\Program Files\NVIDIA\CUDNN
key: ${{ matrix.install }}-${{ matrix.cudnn-install }}
- uses: actions/checkout@v4
- if: matrix.superbuild_target == 'ollama-local'
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}\.ccache
key: ccache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.preset }}-${{ needs.changes.outputs.enginehash }}
- name: Build llama-server
if: matrix.server_preset
- name: Build native superbuild
if: matrix.superbuild_target
run: |
$ErrorActionPreference = "Stop"
Import-Module 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll'
Enter-VsDevShell -VsInstallPath 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise' -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -no_logo'
cmake -S llama\server --preset "${{ matrix.server_preset }}" ${{ matrix.flags }}
cmake --build "${{ matrix.server_build_dir }}" -- -l $([Environment]::ProcessorCount)
cmake -S . -B "${{ matrix.superbuild_dir }}" ${{ matrix.superbuild_args }}
$env:CMAKE_BUILD_PARALLEL_LEVEL = [Environment]::ProcessorCount
cmake --build "${{ matrix.superbuild_dir }}" --target "${{ matrix.superbuild_target }}" -- -l $([Environment]::ProcessorCount)
if (!(Test-Path "${{ matrix.superbuild_dir }}\${{ matrix.expected_payload }}")) {
throw "missing ${{ matrix.expected_payload }}"
}
env:
CMAKE_GENERATOR: Ninja
- name: Verify local superbuild install
if: matrix.superbuild_target == 'ollama-local'
run: |
$ErrorActionPreference = "Stop"
& ".\ollama.exe" --version
& "${{ matrix.superbuild_dir }}\lib\ollama\llama-server.exe" --version
if (!(Test-Path "${{ matrix.superbuild_dir }}\lib\ollama\llama-quantize.exe")) {
throw "missing llama-quantize.exe"
}
$installPrefix = Join-Path $env:RUNNER_TEMP "ollama-local"
cmake --install "${{ matrix.superbuild_dir }}" --component ollama-local --prefix "$installPrefix"
& "$installPrefix\bin\ollama.exe" --version
& "$installPrefix\lib\ollama\llama-server.exe" --version
if (!(Test-Path "$installPrefix\lib\ollama\llama-quantize.exe")) {
throw "missing installed llama-quantize.exe"
}
- name: Build MLX
if: matrix.mlx_preset
run: |

View file

@ -32,11 +32,12 @@ endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON) # Recent versions of MLX Requires gnu++17 extensions to compile properly
set(CMAKE_CXX_EXTENSIONS ON) # Recent versions of MLX require gnu++17 extensions to compile properly
# GGML backend for inference is provided by llama-server (built separately via
# llama/server/CMakeLists.txt using FetchContent from upstream llama.cpp).
# This root CMakeLists.txt only builds MLX (when enabled).
# llama/server/CMakeLists.txt using FetchContent from the pinned llama.cpp source).
# The root CMake project is the orchestration entrypoint; backend-specific
# build rules live in subprojects under cmake/.
if(APPLE)
set(CMAKE_BUILD_RPATH "@loader_path")
@ -45,7 +46,7 @@ if(APPLE)
endif()
set(OLLAMA_BUILD_DIR ${CMAKE_BINARY_DIR}/lib/ollama)
set(OLLAMA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib/ollama/${OLLAMA_RUNNER_DIR})
set(OLLAMA_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/ollama/${OLLAMA_RUNNER_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OLLAMA_BUILD_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${OLLAMA_BUILD_DIR})
@ -55,188 +56,9 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${OLLAMA_BUILD_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${OLLAMA_BUILD_DIR})
option(MLX_ENGINE "Enable MLX backend" OFF)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/local.cmake)
if(MLX_ENGINE)
if(MLX_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES)
check_language(CUDA)
endif()
message(STATUS "Setting up MLX (this takes a while...)")
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/x/imagegen/mlx)
# Find CUDA toolkit if MLX is built with CUDA support
find_package(CUDAToolkit)
# Build list of directories for runtime dependency resolution
set(MLX_RUNTIME_DIRS ${CUDAToolkit_BIN_DIR} ${CUDAToolkit_BIN_DIR}/x64 ${CUDAToolkit_LIBRARY_DIR})
# Add cuDNN bin paths for DLLs (Windows MLX CUDA builds)
# CUDNN_ROOT_DIR is the standard CMake variable for cuDNN location
if(DEFINED ENV{CUDNN_ROOT_DIR})
# cuDNN 9.x has versioned subdirectories under bin/ (e.g., bin/13.0/)
file(GLOB CUDNN_BIN_SUBDIRS "$ENV{CUDNN_ROOT_DIR}/bin/*")
list(APPEND MLX_RUNTIME_DIRS ${CUDNN_BIN_SUBDIRS})
endif()
# Add build output directory and MLX dependency build directories
list(APPEND MLX_RUNTIME_DIRS ${OLLAMA_BUILD_DIR})
# OpenBLAS DLL location (pre-built zip extracts into openblas-src/bin/)
list(APPEND MLX_RUNTIME_DIRS ${CMAKE_BINARY_DIR}/_deps/openblas-src/bin)
# NCCL: on Linux, if real NCCL is found, cmake bundles libnccl.so via the
# regex below. If NCCL is not found, MLX links a static stub (OBJECT lib)
# so there is no runtime dependency. This path covers the stub build dir
# for windows so we include the DLL in our dependencies.
list(APPEND MLX_RUNTIME_DIRS ${CMAKE_BINARY_DIR}/_deps/mlx-build/mlx/distributed/nccl/nccl_stub-prefix/src/nccl_stub-build/Release)
# Base regexes for runtime dependencies (cross-platform)
set(MLX_INCLUDE_REGEXES cublas cublasLt cudart cufft nvrtc nvrtc-builtins cudnn nccl openblas gfortran)
# On Windows, also include dl.dll (dlfcn-win32 POSIX emulation layer)
if(WIN32)
list(APPEND MLX_INCLUDE_REGEXES "^dl\\.dll$")
endif()
# Keep mlx/mlxc targets separate from runtime dependencies so
# --strip only applies to the binaries we build, not vendor DLLs/libs.
install(TARGETS mlx mlxc
RUNTIME_DEPENDENCY_SET mlx_runtime_deps
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
FRAMEWORK DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
)
install(RUNTIME_DEPENDENCY_SET mlx_runtime_deps
DIRECTORIES ${MLX_RUNTIME_DIRS}
PRE_INCLUDE_REGEXES ${MLX_INCLUDE_REGEXES}
PRE_EXCLUDE_REGEXES ".*"
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX_VENDOR
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX_VENDOR
)
if(TARGET jaccl)
install(TARGETS jaccl
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
FRAMEWORK DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
)
endif()
# Install the Metal library for macOS arm64 (must be colocated with the binary)
# Metal backend is only built for arm64, not x86_64
if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
install(FILES ${CMAKE_BINARY_DIR}/_deps/mlx-build/mlx/backend/metal/kernels/mlx.metallib
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX)
endif()
# Install headers for NVRTC JIT compilation at runtime.
# MLX's own install rules use the default component so they get skipped by
# --component MLX. Headers are installed alongside libmlx in OLLAMA_INSTALL_DIR.
#
# Layout:
# ${OLLAMA_INSTALL_DIR}/include/cccl/{cuda,nv}/ — CCCL headers
# ${OLLAMA_INSTALL_DIR}/include/*.h — CUDA toolkit headers
#
# MLX's jit_module.cpp resolves CCCL via
# current_binary_dir()[.parent_path()] / "include" / "cccl"
# On Linux, MLX's jit_module.cpp resolves CCCL via
# current_binary_dir().parent_path() / "include" / "cccl", so we create a
# symlink from lib/ollama/include -> ${OLLAMA_RUNNER_DIR}/include
# This will need refinement if we add multiple CUDA versions for MLX in the future.
# CUDA runtime headers are found via CUDA_PATH env var (set by mlxrunner).
if(EXISTS ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/cuda)
install(DIRECTORY ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/cuda
DESTINATION ${OLLAMA_INSTALL_DIR}/include/cccl
COMPONENT MLX)
install(DIRECTORY ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/nv
DESTINATION ${OLLAMA_INSTALL_DIR}/include/cccl
COMPONENT MLX)
endif()
# Install minimal CUDA toolkit headers needed by MLX JIT kernels.
# These are the transitive closure of includes from mlx/backend/cuda/device/*.cuh.
# The Go mlxrunner sets CUDA_PATH to OLLAMA_INSTALL_DIR so MLX finds them at
# $CUDA_PATH/include/*.h via NVRTC --include-path.
if(CUDAToolkit_FOUND)
# CUDAToolkit_INCLUDE_DIRS may be a semicolon-separated list
# (e.g. ".../include;.../include/cccl"). Find the entry that
# contains the CUDA runtime headers we need.
set(_cuda_inc "")
foreach(_dir ${CUDAToolkit_INCLUDE_DIRS})
if(EXISTS "${_dir}/cuda_runtime_api.h")
set(_cuda_inc "${_dir}")
break()
endif()
endforeach()
if(NOT _cuda_inc)
message(WARNING "Could not find cuda_runtime_api.h in CUDAToolkit_INCLUDE_DIRS: ${CUDAToolkit_INCLUDE_DIRS}")
else()
set(_dst "${OLLAMA_INSTALL_DIR}/include")
set(_MLX_JIT_CUDA_HEADERS
builtin_types.h
cooperative_groups.h
cuda_bf16.h
cuda_bf16.hpp
cuda_device_runtime_api.h
cuda_fp16.h
cuda_fp16.hpp
cuda_fp8.h
cuda_fp8.hpp
cuda_runtime_api.h
device_types.h
driver_types.h
math_constants.h
surface_types.h
texture_types.h
vector_functions.h
vector_functions.hpp
vector_types.h
)
foreach(_hdr ${_MLX_JIT_CUDA_HEADERS})
install(FILES "${_cuda_inc}/${_hdr}"
DESTINATION ${_dst}
COMPONENT MLX)
endforeach()
# Subdirectory headers
install(DIRECTORY "${_cuda_inc}/cooperative_groups"
DESTINATION ${_dst}
COMPONENT MLX
FILES_MATCHING PATTERN "*.h")
install(FILES "${_cuda_inc}/crt/host_defines.h"
DESTINATION "${_dst}/crt"
COMPONENT MLX)
if(NOT WIN32 AND NOT APPLE)
install(CODE "
set(_link \"${CMAKE_INSTALL_PREFIX}/lib/ollama/include\")
set(_target \"${OLLAMA_RUNNER_DIR}/include\")
if(NOT EXISTS \${_link})
execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink \${_target} \${_link})
endif()
" COMPONENT MLX)
endif()
endif()
endif()
# On Windows, explicitly install dl.dll (dlfcn-win32 POSIX dlopen emulation)
# RUNTIME_DEPENDENCIES auto-excludes it via POST_EXCLUDE_FILES_STRICT because
# dlfcn-win32 is a known CMake target with its own install rules (which install
# to the wrong destination). We must install it explicitly here.
if(WIN32)
install(FILES ${OLLAMA_BUILD_DIR}/dl.dll
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX)
endif()
# Manually install CUDA runtime libraries that MLX loads via dlopen
# (not detected by RUNTIME_DEPENDENCIES since they aren't link-time deps)
if(CUDAToolkit_FOUND)
file(GLOB MLX_CUDA_LIBS
"${CUDAToolkit_LIBRARY_DIR}/libcudart.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcublas.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcublasLt.so*"
"${CUDAToolkit_LIBRARY_DIR}/libnvrtc.so*"
"${CUDAToolkit_LIBRARY_DIR}/libnvrtc-builtins.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcufft.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcudnn.so*")
if(MLX_CUDA_LIBS)
install(FILES ${MLX_CUDA_LIBS}
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX_VENDOR)
endif()
endif()
add_subdirectory(cmake/mlx)
endif()

View file

@ -187,6 +187,7 @@ ENV LAPACK_INCLUDE_DIRS=/usr/include/openblas
ENV CGO_LDFLAGS="-L/usr/local/cuda-13/lib64 -L/usr/local/cuda-13/targets/x86_64-linux/lib/stubs"
WORKDIR /go/src/github.com/ollama/ollama
COPY CMakeLists.txt CMakePresets.json .
COPY cmake cmake
COPY x/imagegen/mlx x/imagegen/mlx
COPY go.mod go.sum .
COPY MLX_VERSION MLX_C_VERSION .

301
cmake/local.cmake Normal file
View file

@ -0,0 +1,301 @@
# Local Ollama superbuild targets.
#
# This file keeps the repository-root CMake project focused on orchestration:
# it builds a runnable local Ollama payload by delegating llama.cpp work to the
# llama/server CMake project and building the Go binary into a matching layout.
include(ExternalProject)
set(OLLAMA_LLAMA_SERVER_BACKENDS "" CACHE STRING
"Semicolon-separated llama-server GPU backends to build: cuda-v12;cuda-v13;cuda-v13-windows;rocm;rocm-windows;vulkan;jetpack5;jetpack6")
set(OLLAMA_VERSION "0.0.0" CACHE STRING "Ollama version embedded in the local Go binary")
string(REGEX REPLACE "^v" "" OLLAMA_VERSION "${OLLAMA_VERSION}")
set(OLLAMA_LLAMA_SERVER_CONFIG_ARG)
if(CMAKE_CONFIGURATION_TYPES)
set(OLLAMA_LLAMA_SERVER_CONFIG_ARG --config Release)
endif()
set(OLLAMA_LLAMA_SERVER_EXTERNAL_OPTIONS)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28)
list(APPEND OLLAMA_LLAMA_SERVER_EXTERNAL_OPTIONS BUILD_JOB_SERVER_AWARE TRUE)
endif()
function(ollama_escape_cmake_list input output)
string(REPLACE ";" "|" _escaped "${input}")
set(${output} "${_escaped}" PARENT_SCOPE)
endfunction()
function(ollama_add_llama_server_build name)
cmake_parse_arguments(ARG "" "RUNNER_DIR" "TARGETS;CMAKE_ARGS" ${ARGN})
if(NOT ARG_TARGETS)
message(FATAL_ERROR "ollama_add_llama_server_build(${name}) requires TARGETS")
endif()
if(WIN32 AND name STREQUAL "vulkan")
# The Vulkan shader generator nests deeply enough to hit Windows MAX_PATH.
set(_build_dir ${CMAKE_BINARY_DIR}/ls-vk)
else()
set(_build_dir ${CMAKE_BINARY_DIR}/llama-server-${name})
endif()
set(_cmake_args
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}
-DOLLAMA_RUNNER_DIR=${ARG_RUNNER_DIR}
-DGGML_NATIVE=OFF
-DGGML_OPENMP=OFF
${ARG_CMAKE_ARGS}
)
if(APPLE)
if(CMAKE_OSX_ARCHITECTURES)
list(APPEND _cmake_args
-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES})
endif()
if(CMAKE_OSX_DEPLOYMENT_TARGET)
list(APPEND _cmake_args
-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET})
endif()
endif()
if(DEFINED FETCHCONTENT_SOURCE_DIR_LLAMA_CPP)
list(APPEND _cmake_args
-DFETCHCONTENT_SOURCE_DIR_LLAMA_CPP=${FETCHCONTENT_SOURCE_DIR_LLAMA_CPP})
endif()
ExternalProject_Add(ollama-llama-server-${name}
SOURCE_DIR ${CMAKE_SOURCE_DIR}/llama/server
BINARY_DIR ${_build_dir}
CMAKE_ARGS ${_cmake_args}
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR>
${OLLAMA_LLAMA_SERVER_CONFIG_ARG}
--target ${ARG_TARGETS}
INSTALL_COMMAND ${CMAKE_COMMAND} --install <BINARY_DIR>
${OLLAMA_LLAMA_SERVER_CONFIG_ARG}
--component llama-server
LIST_SEPARATOR |
BUILD_ALWAYS TRUE
${OLLAMA_LLAMA_SERVER_EXTERNAL_OPTIONS}
USES_TERMINAL_CONFIGURE TRUE
USES_TERMINAL_BUILD TRUE
USES_TERMINAL_INSTALL TRUE)
endfunction()
find_program(GO_EXECUTABLE go)
if(NOT OLLAMA_GO_OUTPUT)
if(WIN32)
set(OLLAMA_GO_OUTPUT ${CMAKE_SOURCE_DIR}/ollama.exe)
else()
set(OLLAMA_GO_OUTPUT ${CMAKE_SOURCE_DIR}/ollama)
endif()
endif()
if(NOT IS_ABSOLUTE "${OLLAMA_GO_OUTPUT}")
set(OLLAMA_GO_OUTPUT "${CMAKE_SOURCE_DIR}/${OLLAMA_GO_OUTPUT}")
endif()
get_filename_component(OLLAMA_GO_OUTPUT "${OLLAMA_GO_OUTPUT}" ABSOLUTE)
set(OLLAMA_GO_OUTPUT "${OLLAMA_GO_OUTPUT}" CACHE FILEPATH "Output path for the local Ollama Go binary")
get_filename_component(OLLAMA_GO_OUTPUT_DIR "${OLLAMA_GO_OUTPUT}" DIRECTORY)
set(OLLAMA_GO_LDFLAGS
"-s -w -X=github.com/ollama/ollama/version.Version=${OLLAMA_VERSION} -X=github.com/ollama/ollama/server.mode=release")
if(GO_EXECUTABLE)
add_custom_target(ollama-go ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${OLLAMA_GO_OUTPUT_DIR}"
COMMAND ${CMAKE_COMMAND} -E env CGO_ENABLED=1
${GO_EXECUTABLE} build -trimpath -ldflags "${OLLAMA_GO_LDFLAGS}" -o "${OLLAMA_GO_OUTPUT}" .
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
BYPRODUCTS ${OLLAMA_GO_OUTPUT}
COMMENT "Building Ollama Go binary"
VERBATIM)
else()
add_custom_target(ollama-go ALL
COMMAND ${CMAKE_COMMAND} -E echo
"Go executable not found. Install Go or set GO_EXECUTABLE to build the local Ollama binary."
COMMAND ${CMAKE_COMMAND} -E false
COMMENT "Building Ollama Go binary"
VERBATIM)
endif()
set(_cpu_args)
if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
list(APPEND _cpu_args
-DBUILD_SHARED_LIBS=OFF
-DGGML_BACKEND_DL=OFF
-DGGML_METAL=ON
-DGGML_METAL_EMBED_LIBRARY=ON)
else()
list(APPEND _cpu_args
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CPU_ALL_VARIANTS=ON)
if(APPLE)
list(APPEND _cpu_args -DGGML_METAL=OFF)
endif()
endif()
ollama_add_llama_server_build(local
RUNNER_DIR ""
TARGETS llama-server llama-quantize
CMAKE_ARGS ${_cpu_args})
add_custom_target(ollama-local ALL
DEPENDS ollama-go ollama-llama-server-local
COMMENT "Building local Ollama payload")
install(PROGRAMS "${OLLAMA_GO_OUTPUT}"
DESTINATION "${CMAKE_INSTALL_BINDIR}"
COMPONENT ollama-local)
set(_backend_targets)
foreach(_backend IN LISTS OLLAMA_LLAMA_SERVER_BACKENDS)
if(_backend STREQUAL "cuda-v12")
set(_cuda_arch "${CMAKE_CUDA_ARCHITECTURES}")
if(NOT _cuda_arch)
set(_cuda_arch "50-virtual;52-virtual;60-virtual;61-virtual;70;75;80;86;89;90;90a;120")
endif()
ollama_escape_cmake_list("${_cuda_arch}" _cuda_arch_arg)
set(_cuda_flags "${CMAKE_CUDA_FLAGS}")
if(NOT _cuda_flags)
set(_cuda_flags "-Wno-deprecated-gpu-targets -t 2")
endif()
ollama_add_llama_server_build(cuda-v12
RUNNER_DIR cuda_v12
TARGETS ggml-cuda
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES=${_cuda_arch_arg}
-DCMAKE_CUDA_FLAGS=${_cuda_flags}
-DOLLAMA_GPU_BACKEND=cuda)
list(APPEND _backend_targets ollama-llama-server-cuda-v12)
elseif(_backend STREQUAL "cuda-v13")
set(_cuda_arch "${CMAKE_CUDA_ARCHITECTURES}")
if(NOT _cuda_arch)
set(_cuda_arch "75-virtual;80-virtual;86-virtual;89-virtual;90-virtual;90a-virtual;100-virtual;103-virtual;110-virtual;120-virtual;121-virtual")
endif()
ollama_escape_cmake_list("${_cuda_arch}" _cuda_arch_arg)
set(_cuda_flags "${CMAKE_CUDA_FLAGS}")
if(NOT _cuda_flags)
set(_cuda_flags "-t 4")
endif()
ollama_add_llama_server_build(cuda-v13
RUNNER_DIR cuda_v13
TARGETS ggml-cuda
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES=${_cuda_arch_arg}
-DCMAKE_CUDA_FLAGS=${_cuda_flags}
-DOLLAMA_GPU_BACKEND=cuda)
list(APPEND _backend_targets ollama-llama-server-cuda-v13)
elseif(_backend STREQUAL "cuda-v13-windows")
set(_cuda_arch "${CMAKE_CUDA_ARCHITECTURES}")
if(NOT _cuda_arch)
set(_cuda_arch "75-virtual;89-virtual;100-virtual;120-virtual")
endif()
ollama_escape_cmake_list("${_cuda_arch}" _cuda_arch_arg)
set(_cuda_flags "${CMAKE_CUDA_FLAGS}")
if(NOT _cuda_flags)
set(_cuda_flags "-t 4")
endif()
ollama_add_llama_server_build(cuda-v13-windows
RUNNER_DIR cuda_v13
TARGETS ggml-cuda
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES=${_cuda_arch_arg}
-DCMAKE_CUDA_FLAGS=${_cuda_flags}
-DOLLAMA_GPU_BACKEND=cuda)
list(APPEND _backend_targets ollama-llama-server-cuda-v13-windows)
elseif(_backend STREQUAL "rocm" OR _backend STREQUAL "rocm-windows")
set(_amd_targets "${AMDGPU_TARGETS}")
if(NOT _amd_targets)
set(_amd_targets "${CMAKE_HIP_ARCHITECTURES}")
endif()
if(NOT _amd_targets)
if(_backend STREQUAL "rocm-windows")
set(_amd_targets "gfx942;gfx950;gfx1010;gfx1012;gfx1030;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1200;gfx1201")
else()
set(_amd_targets "gfx942;gfx950;gfx1010;gfx1012;gfx1030;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1200;gfx1201;gfx908:xnack-;gfx90a:xnack+;gfx90a:xnack-")
endif()
endif()
ollama_escape_cmake_list("${_amd_targets}" _amd_targets_arg)
set(_hip_flags "${CMAKE_HIP_FLAGS}")
if(NOT _hip_flags)
set(_hip_flags "-parallel-jobs=4")
endif()
set(_rocm_args
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_HIP=ON
-DCMAKE_HIP_PLATFORM=amd
-DAMDGPU_TARGETS=${_amd_targets_arg}
-DCMAKE_HIP_FLAGS=${_hip_flags}
-DOLLAMA_GPU_BACKEND=hip)
if(CMAKE_PREFIX_PATH)
ollama_escape_cmake_list("${CMAKE_PREFIX_PATH}" _prefix_path_arg)
list(APPEND _rocm_args -DCMAKE_PREFIX_PATH=${_prefix_path_arg})
endif()
if(_backend STREQUAL "rocm-windows")
list(APPEND _rocm_args
"-DCMAKE_C_FLAGS=-parallel-jobs=4 -Wno-ignored-attributes -Wno-deprecated-pragma"
"-DCMAKE_CXX_FLAGS=-parallel-jobs=4 -Wno-ignored-attributes -Wno-deprecated-pragma")
endif()
ollama_add_llama_server_build(${_backend}
RUNNER_DIR rocm
TARGETS ggml-hip
CMAKE_ARGS ${_rocm_args})
list(APPEND _backend_targets ollama-llama-server-${_backend})
elseif(_backend STREQUAL "vulkan")
ollama_add_llama_server_build(vulkan
RUNNER_DIR vulkan
TARGETS ggml-vulkan
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_VULKAN=ON
-DOLLAMA_GPU_BACKEND=vulkan)
list(APPEND _backend_targets ollama-llama-server-vulkan)
elseif(_backend STREQUAL "jetpack5")
ollama_add_llama_server_build(jetpack5
RUNNER_DIR cuda_jetpack5
TARGETS ggml-cuda
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES=72\;87
-DOLLAMA_GPU_BACKEND=cuda)
list(APPEND _backend_targets ollama-llama-server-jetpack5)
elseif(_backend STREQUAL "jetpack6")
ollama_add_llama_server_build(jetpack6
RUNNER_DIR cuda_jetpack6
TARGETS ggml-cuda
CMAKE_ARGS
-DBUILD_SHARED_LIBS=ON
-DGGML_BACKEND_DL=ON
-DGGML_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES=87
-DOLLAMA_GPU_BACKEND=cuda)
list(APPEND _backend_targets ollama-llama-server-jetpack6)
else()
message(FATAL_ERROR
"Unknown OLLAMA_LLAMA_SERVER_BACKENDS entry '${_backend}'")
endif()
endforeach()
if(_backend_targets)
add_custom_target(ollama-llama-server-backends ALL
DEPENDS ${_backend_targets}
COMMENT "Building llama-server GPU backends")
endif()
install(DIRECTORY "${CMAKE_BINARY_DIR}/lib/ollama/"
DESTINATION "lib/ollama"
COMPONENT ollama-local
USE_SOURCE_PERMISSIONS)

189
cmake/mlx/CMakeLists.txt Normal file
View file

@ -0,0 +1,189 @@
# Ollama MLX runtime build and install rules.
#
# The repository root CMake project is evolving into an orchestration layer.
# Keep MLX-specific target setup here so the root can later coordinate MLX,
# llama-server, and Go builds without carrying backend-specific install logic.
if(MLX_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES)
check_language(CUDA)
endif()
message(STATUS "Setting up MLX (this takes a while...)")
add_subdirectory(${CMAKE_SOURCE_DIR}/x/imagegen/mlx ${CMAKE_BINARY_DIR}/x/imagegen/mlx)
# Find CUDA toolkit if MLX is built with CUDA support.
find_package(CUDAToolkit)
# Build list of directories for runtime dependency resolution.
set(MLX_RUNTIME_DIRS ${CUDAToolkit_BIN_DIR} ${CUDAToolkit_BIN_DIR}/x64 ${CUDAToolkit_LIBRARY_DIR})
# Add cuDNN bin paths for DLLs (Windows MLX CUDA builds).
# CUDNN_ROOT_DIR is the standard CMake variable for cuDNN location.
if(DEFINED ENV{CUDNN_ROOT_DIR})
# cuDNN 9.x has versioned subdirectories under bin/ (e.g., bin/13.0/).
file(GLOB CUDNN_BIN_SUBDIRS "$ENV{CUDNN_ROOT_DIR}/bin/*")
list(APPEND MLX_RUNTIME_DIRS ${CUDNN_BIN_SUBDIRS})
endif()
# Add build output directory and MLX dependency build directories.
list(APPEND MLX_RUNTIME_DIRS ${OLLAMA_BUILD_DIR})
# OpenBLAS DLL location (pre-built zip extracts into openblas-src/bin/).
list(APPEND MLX_RUNTIME_DIRS ${CMAKE_BINARY_DIR}/_deps/openblas-src/bin)
# NCCL: on Linux, if real NCCL is found, cmake bundles libnccl.so via the
# regex below. If NCCL is not found, MLX links a static stub (OBJECT lib)
# so there is no runtime dependency. This path covers the stub build dir
# for windows so we include the DLL in our dependencies.
list(APPEND MLX_RUNTIME_DIRS ${CMAKE_BINARY_DIR}/_deps/mlx-build/mlx/distributed/nccl/nccl_stub-prefix/src/nccl_stub-build/Release)
# Base regexes for runtime dependencies (cross-platform).
set(MLX_INCLUDE_REGEXES cublas cublasLt cudart cufft nvrtc nvrtc-builtins cudnn nccl openblas gfortran)
# On Windows, also include dl.dll (dlfcn-win32 POSIX emulation layer).
if(WIN32)
list(APPEND MLX_INCLUDE_REGEXES "^dl\\.dll$")
endif()
# Keep mlx/mlxc targets separate from runtime dependencies so --strip only
# applies to the binaries we build, not vendor DLLs/libs.
install(TARGETS mlx mlxc
RUNTIME_DEPENDENCY_SET mlx_runtime_deps
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
FRAMEWORK DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
)
install(RUNTIME_DEPENDENCY_SET mlx_runtime_deps
DIRECTORIES ${MLX_RUNTIME_DIRS}
PRE_INCLUDE_REGEXES ${MLX_INCLUDE_REGEXES}
PRE_EXCLUDE_REGEXES ".*"
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX_VENDOR
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX_VENDOR
)
if(TARGET jaccl)
install(TARGETS jaccl
RUNTIME DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
LIBRARY DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
FRAMEWORK DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX
)
endif()
# Install the Metal library for macOS arm64 (must be colocated with the binary).
# Metal backend is only built for arm64, not x86_64.
if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
install(FILES ${CMAKE_BINARY_DIR}/_deps/mlx-build/mlx/backend/metal/kernels/mlx.metallib
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX)
endif()
# Install headers for NVRTC JIT compilation at runtime.
# MLX's own install rules use the default component so they get skipped by
# --component MLX. Headers are installed alongside libmlx in OLLAMA_INSTALL_DIR.
#
# Layout:
# ${OLLAMA_INSTALL_DIR}/include/cccl/{cuda,nv}/ - CCCL headers
# ${OLLAMA_INSTALL_DIR}/include/*.h - CUDA toolkit headers
#
# MLX's jit_module.cpp resolves CCCL via
# current_binary_dir()[.parent_path()] / "include" / "cccl"
# On Linux, MLX's jit_module.cpp resolves CCCL via
# current_binary_dir().parent_path() / "include" / "cccl", so we create a
# symlink from lib/ollama/include -> ${OLLAMA_RUNNER_DIR}/include.
# This will need refinement if we add multiple CUDA versions for MLX in the future.
# CUDA runtime headers are found via CUDA_PATH env var (set by mlxrunner).
if(EXISTS ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/cuda)
install(DIRECTORY ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/cuda
DESTINATION ${OLLAMA_INSTALL_DIR}/include/cccl
COMPONENT MLX)
install(DIRECTORY ${CMAKE_BINARY_DIR}/_deps/cccl-src/include/nv
DESTINATION ${OLLAMA_INSTALL_DIR}/include/cccl
COMPONENT MLX)
endif()
# Install minimal CUDA toolkit headers needed by MLX JIT kernels.
# These are the transitive closure of includes from mlx/backend/cuda/device/*.cuh.
# The Go mlxrunner sets CUDA_PATH to OLLAMA_INSTALL_DIR so MLX finds them at
# $CUDA_PATH/include/*.h via NVRTC --include-path.
if(CUDAToolkit_FOUND)
# CUDAToolkit_INCLUDE_DIRS may be a semicolon-separated list
# (e.g. ".../include;.../include/cccl"). Find the entry that
# contains the CUDA runtime headers we need.
set(_cuda_inc "")
foreach(_dir ${CUDAToolkit_INCLUDE_DIRS})
if(EXISTS "${_dir}/cuda_runtime_api.h")
set(_cuda_inc "${_dir}")
break()
endif()
endforeach()
if(NOT _cuda_inc)
message(WARNING "Could not find cuda_runtime_api.h in CUDAToolkit_INCLUDE_DIRS: ${CUDAToolkit_INCLUDE_DIRS}")
else()
set(_dst "${OLLAMA_INSTALL_DIR}/include")
set(_MLX_JIT_CUDA_HEADERS
builtin_types.h
cooperative_groups.h
cuda_bf16.h
cuda_bf16.hpp
cuda_device_runtime_api.h
cuda_fp16.h
cuda_fp16.hpp
cuda_fp8.h
cuda_fp8.hpp
cuda_runtime_api.h
device_types.h
driver_types.h
math_constants.h
surface_types.h
texture_types.h
vector_functions.h
vector_functions.hpp
vector_types.h
)
foreach(_hdr ${_MLX_JIT_CUDA_HEADERS})
install(FILES "${_cuda_inc}/${_hdr}"
DESTINATION ${_dst}
COMPONENT MLX)
endforeach()
# Subdirectory headers.
install(DIRECTORY "${_cuda_inc}/cooperative_groups"
DESTINATION ${_dst}
COMPONENT MLX
FILES_MATCHING PATTERN "*.h")
install(FILES "${_cuda_inc}/crt/host_defines.h"
DESTINATION "${_dst}/crt"
COMPONENT MLX)
if(NOT WIN32 AND NOT APPLE)
install(CODE "
set(_link \"${CMAKE_INSTALL_PREFIX}/lib/ollama/include\")
set(_target \"${OLLAMA_RUNNER_DIR}/include\")
if(NOT EXISTS \${_link})
execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink \${_target} \${_link})
endif()
" COMPONENT MLX)
endif()
endif()
endif()
# On Windows, explicitly install dl.dll (dlfcn-win32 POSIX dlopen emulation).
# RUNTIME_DEPENDENCIES auto-excludes it via POST_EXCLUDE_FILES_STRICT because
# dlfcn-win32 is a known CMake target with its own install rules (which install
# to the wrong destination). We must install it explicitly here.
if(WIN32)
install(FILES ${OLLAMA_BUILD_DIR}/dl.dll
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX)
endif()
# Manually install CUDA runtime libraries that MLX loads via dlopen
# (not detected by RUNTIME_DEPENDENCIES since they aren't link-time deps).
if(CUDAToolkit_FOUND)
file(GLOB MLX_CUDA_LIBS
"${CUDAToolkit_LIBRARY_DIR}/libcudart.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcublas.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcublasLt.so*"
"${CUDAToolkit_LIBRARY_DIR}/libnvrtc.so*"
"${CUDAToolkit_LIBRARY_DIR}/libnvrtc-builtins.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcufft.so*"
"${CUDAToolkit_LIBRARY_DIR}/libcudnn.so*")
if(MLX_CUDA_LIBS)
install(FILES ${MLX_CUDA_LIBS}
DESTINATION ${OLLAMA_INSTALL_DIR}
COMPONENT MLX_VENDOR)
endif()
endif()

View file

@ -3,9 +3,11 @@
Install prerequisites:
- [Go](https://go.dev/doc/install)
- C/C++ Compiler e.g. Clang on macOS, [TDM-GCC](https://github.com/jmeubank/tdm-gcc/releases/latest) (Windows amd64) or [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) (Windows arm64), GCC/Clang on Linux.
- [CMake](https://cmake.org/download/) 3.24 or newer
- C/C++ compiler: Clang on macOS, Visual Studio 2022 C++ tools on Windows, or GCC/Clang on Linux
- [Ninja](https://github.com/ninja-build/ninja/releases) in `PATH` is recommended, especially on Windows
Then build and run Ollama from the root directory of the repository:
For pure Go iteration against an existing native payload, run Ollama from the repository root:
```shell
go run . serve
@ -14,10 +16,44 @@ go run . serve
> [!NOTE]
> Ollama includes native code compiled with CGO. From time to time these data structures can change and CGO can get out of sync resulting in unexpected crashes. You can force a full build of the native code by running `go clean -cache` first.
## Native build model
For a fresh checkout, or after changing native code, build from the repository root. Use an explicit job count instead of bare `--parallel`; increase `4` only if the machine has enough CPU and memory headroom.
```shell
cmake -B build .
cmake --build build --parallel 4
./ollama serve
```
To build `llama-server` GPU backends through the same root build, select the backend and target explicitly:
```shell
cmake -B build-gpu . -DOLLAMA_LLAMA_SERVER_BACKENDS=vulkan
cmake --build build-gpu --target ollama-llama-server-vulkan --parallel 4
```
Supported backend values are `cuda-v12`, `cuda-v13`, `cuda-v13-windows`, `rocm`, `rocm-windows`, `vulkan`, `jetpack5`, and `jetpack6`.
Use standard CMake architecture overrides to narrow GPU builds for local hardware:
```shell
# CUDA
cmake -B build-gpu . -DOLLAMA_LLAMA_SERVER_BACKENDS=cuda-v13 -DCMAKE_CUDA_ARCHITECTURES=native
# ROCm / HIP
cmake -B build-gpu . -DOLLAMA_LLAMA_SERVER_BACKENDS=rocm -DCMAKE_HIP_ARCHITECTURES=gfx1100
```
`AMDGPU_TARGETS` is also accepted for ROCm when matching llama.cpp-specific target strings is necessary.
## macOS (Apple Silicon)
macOS Apple Silicon supports Metal which is built-in to the Ollama binary. No additional steps are required.
macOS Apple Silicon supports Metal for local native builds. For a release-style payload:
```shell
./scripts/build_darwin.sh -a arm64
```
## macOS (Intel)
@ -25,41 +61,37 @@ Install prerequisites:
- [CMake](https://cmake.org/download/) or `brew install cmake`
Then, configure and build the project:
Then build the Darwin payload:
```shell
cmake -B build
cmake --build build
./scripts/build_darwin.sh -a amd64
```
Lastly, run Ollama:
```shell
go run . serve
dist/darwin-amd64/ollama serve
```
## Windows
Install prerequisites:
- [CMake](https://cmake.org/download/)
- [Ninja](https://github.com/ninja-build/ninja/releases) in `PATH`
- [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) including the Native Desktop Workload
- (Optional) AMD GPU support
- [ROCm](https://rocm.docs.amd.com/en/latest/)
- (Optional) NVIDIA GPU support
- [CUDA SDK](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=11&target_type=exe_network)
- (Optional) VULKAN GPU support
- [VULKAN SDK](https://vulkan.lunarg.com/sdk/home) - useful for AMD/Intel GPUs
- [CUDA SDK](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_type=exe_network)
- (Optional) Vulkan GPU support
- [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) - useful for AMD/Intel GPUs
- (Optional) MLX engine support
- [CUDA 13+ SDK](https://developer.nvidia.com/cuda-downloads)
- [cuDNN 9+](https://developer.nvidia.com/cudnn)
Then, configure and build the project:
Then build a minimal CPU payload and Go binary:
```shell
cmake -B build
cmake --build build --config Release
```powershell
.\scripts\build_windows.ps1 cpu ollama
```
> Building for Vulkan requires VULKAN_SDK environment variable:
@ -74,35 +106,30 @@ cmake --build build --config Release
> ```
> [!IMPORTANT]
> Building for ROCm requires additional flags:
> ```
> cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
> cmake --build build --config Release
> ```
> Prefer the build script for release-style GPU payloads. It wires the platform-specific compiler, SDK, and install layout details.
Lastly, run Ollama:
```shell
go run . serve
```powershell
.\dist\windows-amd64\ollama.exe serve
```
For native CMake iteration, use the repository-root CMake build shown in [Native build model](#native-build-model). Ninja is recommended when available.
## Windows (ARM)
Windows ARM does not support additional acceleration libraries at this time. Do not use cmake, simply `go run` or `go build`.
Windows ARM does not support additional acceleration libraries at this time. The Windows build script can cross-compile the CPU llama-server payload when the ARM64 cross-compile toolchain is installed; otherwise it skips that payload for local developer builds.
## Linux
Install prerequisites:
- [CMake](https://cmake.org/download/) or `sudo apt install cmake` or `sudo dnf install cmake`
- (Optional) AMD GPU support
- [ROCm](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html)
- (Optional) NVIDIA GPU support
- [CUDA SDK](https://developer.nvidia.com/cuda-downloads)
- (Optional) VULKAN GPU support
- [VULKAN SDK](https://vulkan.lunarg.com/sdk/home) - useful for AMD/Intel GPUs
- (Optional) Vulkan GPU support
- [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) - useful for AMD/Intel GPUs
- Or install via package manager: `sudo apt install vulkan-sdk` (Ubuntu/Debian) or `sudo dnf install vulkan-sdk` (Fedora/CentOS)
- (Optional) MLX engine support
- [CUDA 13+ SDK](https://developer.nvidia.com/cuda-downloads)
@ -112,22 +139,17 @@ Install prerequisites:
> Ensure prerequisites are in `PATH` before running CMake.
Then, configure and build the project:
For a release-style Linux payload, use the Docker-backed build script:
```shell
cmake -B build
cmake --build build
./scripts/build_linux.sh
```
Lastly, run Ollama:
```shell
go run . serve
```
For native CMake iteration, use the repository-root CMake build shown in [Native build model](#native-build-model).
## MLX Engine (Optional)
The MLX engine enables running safetensor based models. It requires building the [MLX](https://github.com/ml-explore/mlx) and [MLX-C](https://github.com/ml-explore/mlx-c) shared libraries separately via CMake. On MacOS, MLX leverages the Metal library to run on the GPU, and on Windows and Linux, runs on NVIDIA GPUs via CUDA v13.
The MLX engine enables running safetensor based models. It requires building the [MLX](https://github.com/ml-explore/mlx) and [MLX-C](https://github.com/ml-explore/mlx-c) shared libraries via the repository-root CMake presets. The root project delegates MLX-specific rules to `cmake/mlx`. On macOS, MLX leverages the Metal library to run on the GPU, and on Windows and Linux, runs on NVIDIA GPUs via CUDA v13.
### macOS (Apple Silicon)
@ -146,9 +168,10 @@ xcrun metal
Then build:
```shell
cmake -B build --preset MLX
cmake --build build --preset MLX --parallel
cmake --preset MLX
cmake --build --preset MLX --parallel 4
cmake --install build --component MLX
cmake --install build --component MLX_VENDOR
```
> [!NOTE]
@ -159,9 +182,10 @@ cmake --install build --component MLX
Requires CUDA 13+ and [cuDNN](https://developer.nvidia.com/cudnn) 9+.
```shell
cmake -B build --preset "MLX CUDA 13"
cmake --build build --target mlx --target mlxc --config Release --parallel
cmake --preset "MLX CUDA 13"
cmake --build --preset "MLX CUDA 13" --parallel 4
cmake --install build --component MLX --strip
cmake --install build --component MLX_VENDOR
```
### Local MLX source overrides
@ -183,7 +207,7 @@ OLLAMA_MLX_SOURCE=../mlx OLLAMA_MLX_C_SOURCE=../mlx-c ./scripts/build_darwin.sh
```powershell
$env:OLLAMA_MLX_SOURCE="../mlx"
$env:OLLAMA_MLX_C_SOURCE="../mlx-c"
./scripts/build_darwin.ps1
./scripts/build_windows.ps1
```
## Docker
@ -208,11 +232,11 @@ go test ./...
## Library detection
Ollama looks for acceleration libraries in the following paths relative to the `ollama` executable:
Ollama looks for native helper binaries and acceleration libraries in installed and local development layouts:
* `./lib/ollama` (Windows)
* `../lib/ollama` (Linux)
* `.` (macOS)
* `build/lib/ollama` (for development)
* `../lib/ollama` for standard installs where `ollama` is under `bin/`
* `./lib/ollama` for Windows release-style payloads and local dist output
* `.` for macOS release artifacts that colocate helpers with `ollama`
* `build/lib/ollama` and `dist/<platform>/lib/ollama` for local development builds
If the libraries are not found, Ollama will not run with any acceleration libraries.

View file

@ -151,12 +151,12 @@
{
"name": "cpu",
"configurePreset": "cpu",
"targets": ["llama-server", "llama-quantize", "ggml-cpu"]
"targets": ["llama-server", "llama-quantize"]
},
{
"name": "cpu-arm64",
"configurePreset": "cpu-arm64",
"targets": ["llama-server", "llama-quantize", "ggml-cpu"]
"targets": ["llama-server", "llama-quantize"]
},
{
"name": "darwin",

View file

@ -94,12 +94,16 @@ func llamaCppBinaryCandidates(name string, search llamaCppBinarySearch) []string
add(base)
// Per-architecture local dist output keeps helpers under lib/ollama.
add(filepath.Join(base, "lib", "ollama"))
// Standard CMake installs put ollama in bin/ and helpers in ../lib/ollama/.
add(filepath.Join(base, "..", "lib", "ollama"))
case "linux":
// Linux packages install ollama in bin/ and helpers in ../lib/ollama/.
add(filepath.Join(base, "..", "lib", "ollama"))
case "windows":
// Windows packages keep ollama.exe at top level with lib/ as a peer.
add(filepath.Join(base, "lib", "ollama"))
// Standard CMake installs put ollama.exe in bin/ and helpers in ../lib/ollama/.
add(filepath.Join(base, "..", "lib", "ollama"))
default:
add(filepath.Join(base, "lib", "ollama"))
add(filepath.Join(base, "..", "lib", "ollama"))

View file

@ -12,9 +12,10 @@ func TestLlamaCppBinaryCandidates(t *testing.T) {
root := t.TempDir()
tests := []struct {
name string
search llamaCppBinarySearch
want []string
name string
search llamaCppBinarySearch
want []string
wantFirst string
}{
{
name: "linux production layout",
@ -24,6 +25,7 @@ func TestLlamaCppBinaryCandidates(t *testing.T) {
goarch: "amd64",
},
want: []string{filepath.Join(root, "linux", "lib", "ollama", "llama-server")},
wantFirst: filepath.Join(root, "linux", "lib", "ollama", "llama-server"),
},
{
name: "windows production layout",
@ -33,6 +35,7 @@ func TestLlamaCppBinaryCandidates(t *testing.T) {
goarch: "amd64",
},
want: []string{filepath.Join(root, "windows", "lib", "ollama", "llama-server.exe")},
wantFirst: filepath.Join(root, "windows", "lib", "ollama", "llama-server.exe"),
},
{
name: "darwin production layout",
@ -42,6 +45,25 @@ func TestLlamaCppBinaryCandidates(t *testing.T) {
goarch: "arm64",
},
want: []string{filepath.Join(root, "Ollama.app", "Contents", "Resources", "llama-server")},
wantFirst: filepath.Join(root, "Ollama.app", "Contents", "Resources", "llama-server"),
},
{
name: "darwin standard install layout",
search: llamaCppBinarySearch{
executable: filepath.Join(root, "darwin", "bin", "ollama"),
goos: "darwin",
goarch: "arm64",
},
want: []string{filepath.Join(root, "darwin", "lib", "ollama", "llama-server")},
},
{
name: "windows standard install layout",
search: llamaCppBinarySearch{
executable: filepath.Join(root, "windows", "bin", "ollama.exe"),
goos: "windows",
goarch: "amd64",
},
want: []string{filepath.Join(root, "windows", "lib", "ollama", "llama-server.exe")},
},
{
name: "local per-architecture dist layout",
@ -109,6 +131,9 @@ func TestLlamaCppBinaryCandidates(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
candidates := llamaCppBinaryCandidates("llama-server", tt.search)
if tt.wantFirst != "" && candidates[0] != tt.wantFirst {
t.Fatalf("first candidate = %q, want %q; all candidates: %v", candidates[0], tt.wantFirst, candidates)
}
if tt.search.libOllamaPath != "" && candidates[0] != tt.want[0] {
t.Fatalf("first candidate = %q, want %q; all candidates: %v", candidates[0], tt.want[0], candidates)
}

View file

@ -6,56 +6,118 @@ import (
"runtime"
)
// LibPath is a path to lookup dynamic libraries
// in development it's usually 'build/lib/ollama'
// in distribution builds it's 'lib/ollama' on Windows
// '../lib/ollama' on Linux and the executable's directory on macOS
// note: distribution builds, additional GPU-specific libraries are
// found in subdirectories of the returned path, such as
// 'cuda_v12', 'rocm', etc.
var LibOllamaPath string = func() string {
type libOllamaPathSearch struct {
executable string
workingDir string
goos string
goarch string
}
// LibOllamaPath is the root used to find bundled llama.cpp and MLX runtime
// libraries. GPU-specific libraries live in backend subdirectories such as
// cuda_v12, rocm, vulkan, and mlx_cuda_v13.
var LibOllamaPath = func() string {
exe, err := os.Executable()
if err != nil {
return ""
}
if eval, err := filepath.EvalSymlinks(exe); err == nil {
exe = eval
}
var libPath string
switch runtime.GOOS {
case "windows":
libPath = filepath.Join(filepath.Dir(exe), "lib", "ollama")
case "linux":
libPath = filepath.Join(filepath.Dir(exe), "..", "lib", "ollama")
case "darwin":
libPath = filepath.Dir(exe)
}
cwd, err := os.Getwd()
if err != nil {
return ""
cwd = ""
}
distPath := filepath.Join("dist", runtime.GOOS+"-"+runtime.GOARCH, "lib", "ollama")
paths := []string{
libPath,
return findLibOllamaPath(libOllamaPathSearch{
executable: exe,
workingDir: cwd,
goos: runtime.GOOS,
goarch: runtime.GOARCH,
})
}()
// release build paths for local development
filepath.Join(filepath.Dir(exe), distPath),
filepath.Join(cwd, distPath),
// build paths for development
filepath.Join(filepath.Dir(exe), "build", "lib", "ollama"),
filepath.Join(cwd, "build", "lib", "ollama"),
}
for _, p := range paths {
if _, err := os.Stat(p); err == nil {
return p
func findLibOllamaPath(search libOllamaPathSearch) string {
candidates := libOllamaPathCandidates(search)
for _, path := range candidates {
if libOllamaPathExists(path) {
return path
}
}
return filepath.Dir(exe)
}()
if search.executable != "" {
return filepath.Dir(search.executable)
}
return ""
}
func libOllamaPathCandidates(search libOllamaPathSearch) []string {
goos := search.goos
if goos == "" {
goos = runtime.GOOS
}
goarch := search.goarch
if goarch == "" {
goarch = runtime.GOARCH
}
seen := map[string]bool{}
var candidates []string
add := func(path string) {
if path == "" {
return
}
path = filepath.Clean(path)
if !seen[path] {
seen[path] = true
candidates = append(candidates, path)
}
}
if search.executable != "" {
exeDir := filepath.Dir(search.executable)
switch goos {
case "darwin":
// Local dist output and standard installs keep helpers under lib/ollama.
add(filepath.Join(exeDir, "lib", "ollama"))
add(filepath.Join(exeDir, "..", "lib", "ollama"))
case "linux":
add(filepath.Join(exeDir, "..", "lib", "ollama"))
add(filepath.Join(exeDir, "lib", "ollama"))
case "windows":
add(filepath.Join(exeDir, "lib", "ollama"))
add(filepath.Join(exeDir, "..", "lib", "ollama"))
default:
add(filepath.Join(exeDir, "lib", "ollama"))
add(filepath.Join(exeDir, "..", "lib", "ollama"))
}
addLocalLibOllamaPaths(add, exeDir, goos, goarch)
if goos == "darwin" {
// macOS release artifacts colocate native helpers with ollama.
add(exeDir)
}
}
addLocalLibOllamaPaths(add, search.workingDir, goos, goarch)
return candidates
}
func addLocalLibOllamaPaths(add func(string), base, goos, goarch string) {
if base == "" {
return
}
add(filepath.Join(base, "build", "lib", "ollama"))
add(filepath.Join(base, "dist", goos+"-"+goarch, "lib", "ollama"))
if goos+"_"+goarch != goos+"-"+goarch {
add(filepath.Join(base, "dist", goos+"_"+goarch, "lib", "ollama"))
}
if goos == "darwin" {
add(filepath.Join(base, "dist", "darwin"))
}
}
func libOllamaPathExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}

116
ml/path_test.go Normal file
View file

@ -0,0 +1,116 @@
package ml
import (
"os"
"path/filepath"
"testing"
)
func TestFindLibOllamaPath(t *testing.T) {
root := t.TempDir()
tests := []struct {
name string
search libOllamaPathSearch
dirs []string
want string
}{
{
name: "darwin release layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "darwin-app", "Ollama.app", "Contents", "Resources", "ollama"),
goos: "darwin",
goarch: "arm64",
},
dirs: []string{filepath.Join(root, "darwin-app", "Ollama.app", "Contents", "Resources")},
want: filepath.Join(root, "darwin-app", "Ollama.app", "Contents", "Resources"),
},
{
name: "darwin standard install layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "darwin-install", "bin", "ollama"),
goos: "darwin",
goarch: "arm64",
},
dirs: []string{filepath.Join(root, "darwin-install", "lib", "ollama")},
want: filepath.Join(root, "darwin-install", "lib", "ollama"),
},
{
name: "windows release layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "windows-release", "ollama.exe"),
goos: "windows",
goarch: "amd64",
},
dirs: []string{filepath.Join(root, "windows-release", "lib", "ollama")},
want: filepath.Join(root, "windows-release", "lib", "ollama"),
},
{
name: "windows standard install layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "windows-install", "bin", "ollama.exe"),
goos: "windows",
goarch: "amd64",
},
dirs: []string{filepath.Join(root, "windows-install", "lib", "ollama")},
want: filepath.Join(root, "windows-install", "lib", "ollama"),
},
{
name: "linux standard install layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "linux-install", "bin", "ollama"),
goos: "linux",
goarch: "amd64",
},
dirs: []string{filepath.Join(root, "linux-install", "lib", "ollama")},
want: filepath.Join(root, "linux-install", "lib", "ollama"),
},
{
name: "local linux underscore dist layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "linux-dev", "ollama"),
workingDir: filepath.Join(root, "linux-dev"),
goos: "linux",
goarch: "amd64",
},
dirs: []string{filepath.Join(root, "linux-dev", "dist", "linux_amd64", "lib", "ollama")},
want: filepath.Join(root, "linux-dev", "dist", "linux_amd64", "lib", "ollama"),
},
{
name: "mlx-only standard install layout",
search: libOllamaPathSearch{
executable: filepath.Join(root, "mlx-install", "bin", "ollama"),
goos: "linux",
goarch: "amd64",
},
dirs: []string{filepath.Join(root, "mlx-install", "lib", "ollama")},
want: filepath.Join(root, "mlx-install", "lib", "ollama"),
},
{
name: "darwin local build layout before executable directory fallback",
search: libOllamaPathSearch{
executable: filepath.Join(root, "darwin-dev", "ollama"),
workingDir: filepath.Join(root, "darwin-dev"),
goos: "darwin",
goarch: "arm64",
},
dirs: []string{filepath.Join(root, "darwin-dev", "build", "lib", "ollama")},
want: filepath.Join(root, "darwin-dev", "build", "lib", "ollama"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for _, dir := range tt.dirs {
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
}
got := findLibOllamaPath(tt.search)
if got != tt.want {
t.Fatalf("findLibOllamaPath() = %q, want %q; candidates: %v", got, tt.want, libOllamaPathCandidates(tt.search))
}
})
}
}

View file

@ -1719,6 +1719,28 @@ func mlxLibName() string {
}
}
func findMLXLibraryInDir(dir, libName string) string {
if dir == "" {
return ""
}
candidate := filepath.Join(dir, libName)
if _, err := os.Stat(candidate); err == nil {
return candidate
}
if mlxDirs, err := filepath.Glob(filepath.Join(dir, "mlx*")); err == nil {
for _, mlxDir := range mlxDirs {
candidate = filepath.Join(mlxDir, libName)
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
}
return ""
}
// findMLXLibrary searches for the MLX shared library in standard locations.
// Returns the path to the library, or empty string if not found.
func findMLXLibrary() string {
@ -1727,18 +1749,9 @@ func findMLXLibrary() string {
// 1. OLLAMA_LIBRARY_PATH — check each dir and mlx_* subdirs
if paths, ok := os.LookupEnv("OLLAMA_LIBRARY_PATH"); ok {
for _, dir := range filepath.SplitList(paths) {
candidate := filepath.Join(dir, libName)
if _, err := os.Stat(candidate); err == nil {
if candidate := findMLXLibraryInDir(dir, libName); candidate != "" {
return candidate
}
if mlxDirs, err := filepath.Glob(filepath.Join(dir, "mlx*")); err == nil {
for _, mlxDir := range mlxDirs {
candidate = filepath.Join(mlxDir, libName)
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
}
}
}
@ -1750,8 +1763,7 @@ func findMLXLibrary() string {
exeDir := filepath.Dir(exe)
// Check exe dir directly (macOS copies dylib here)
candidate := filepath.Join(exeDir, libName)
if _, err := os.Stat(candidate); err == nil {
if candidate := findMLXLibraryInDir(exeDir, libName); candidate != "" {
return candidate
}
@ -1761,22 +1773,27 @@ func findMLXLibrary() string {
filepath.Join(exeDir, "lib", "ollama"),
filepath.Join(exeDir, "..", "lib", "ollama"),
} {
if mlxDirs, err := filepath.Glob(filepath.Join(libOllamaDir, "mlx*")); err == nil {
for _, mlxDir := range mlxDirs {
candidate = filepath.Join(mlxDir, libName)
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
if candidate := findMLXLibraryInDir(libOllamaDir, libName); candidate != "" {
return candidate
}
}
}
// 3. Build directory (for tests run from repo root)
if cwd, err := os.Getwd(); err == nil {
candidate := filepath.Join(cwd, "build", "lib", "ollama", libName)
if _, err := os.Stat(candidate); err == nil {
return candidate
for _, dir := range []string{
filepath.Join(cwd, "build", "lib", "ollama"),
filepath.Join(cwd, "dist", runtime.GOOS+"-"+runtime.GOARCH, "lib", "ollama"),
filepath.Join(cwd, "dist", runtime.GOOS+"_"+runtime.GOARCH, "lib", "ollama"),
} {
if candidate := findMLXLibraryInDir(dir, libName); candidate != "" {
return candidate
}
}
if runtime.GOOS == "darwin" {
if candidate := findMLXLibraryInDir(filepath.Join(cwd, "dist", "darwin"), libName); candidate != "" {
return candidate
}
}
}

View file

@ -70,7 +70,8 @@ func tryLoadFromDir(dir string) bool {
}
// libOllamaRoots returns candidate directories for MLX dynamic libraries.
// Production: exe_dir/lib/ollama (dist tarball) and exe_dir (app bundle).
// Production: exe_dir/lib/ollama (Windows release layout),
// exe_dir/../lib/ollama (standard bin/lib layout), and exe_dir (macOS bundle).
// Development: build/lib/ollama and build/*/lib/ollama.
func libOllamaRoots() []string {
var roots []string
@ -84,11 +85,13 @@ func libOllamaRoots() []string {
switch runtime.GOOS {
case "darwin":
roots = append(roots, filepath.Join(exeDir, "lib", "ollama"))
roots = append(roots, filepath.Join(exeDir, "..", "lib", "ollama"))
roots = append(roots, exeDir) // app bundle: Contents/Resources/
case "linux":
roots = append(roots, filepath.Join(exeDir, "..", "lib", "ollama"))
case "windows":
roots = append(roots, filepath.Join(exeDir, "lib", "ollama"))
roots = append(roots, filepath.Join(exeDir, "..", "lib", "ollama"))
}
}