From af7ea6e96e5272f7b5ccb2bf3fe9dadd92fc763d Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 12 Jan 2026 19:03:11 -0800 Subject: [PATCH] x/imagegen: install mlx.metallib and fix macOS rpath handling, add mlx library directories to LD_LIBRARY_PATH (#13695) - Install mlx.metallib for arm64 builds (required for Metal GPU acceleration) - Apply rpath settings to all macOS builds, not just x86_64 - Add CMAKE_BUILD_WITH_INSTALL_RPATH to avoid install_name_tool errors - Update build_darwin.sh to copy, sign, and package the metallib --- CMakeLists.txt | 11 ++++++++++- scripts/build_darwin.sh | 6 ++++-- x/imagegen/server.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ea95f7d3..7724faa03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,9 +48,10 @@ if((CMAKE_OSX_ARCHITECTURES AND NOT CMAKE_OSX_ARCHITECTURES MATCHES "arm64") set(GGML_CPU_ALL_VARIANTS ON) endif() -if (CMAKE_OSX_ARCHITECTURES MATCHES "x86_64") +if(APPLE) set(CMAKE_BUILD_RPATH "@loader_path") set(CMAKE_INSTALL_RPATH "@loader_path") + set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) endif() set(OLLAMA_BUILD_DIR ${CMAKE_BINARY_DIR}/lib/ollama) @@ -196,6 +197,14 @@ if(MLX_ENGINE) FRAMEWORK DESTINATION ${OLLAMA_INSTALL_DIR} COMPONENT MLX ) + # 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_OSX_ARCHITECTURES MATCHES "arm64") + install(FILES ${CMAKE_BINARY_DIR}/_deps/mlx-build/mlx/backend/metal/kernels/mlx.metallib + DESTINATION ${OLLAMA_INSTALL_DIR} + COMPONENT MLX) + endif() + # Manually install cudart and cublas since they might not be picked up as direct dependencies if(CUDAToolkit_FOUND) file(GLOB CUDART_LIBS diff --git a/scripts/build_darwin.sh b/scripts/build_darwin.sh index 7b9937aa0..d5c5ee17e 100755 --- a/scripts/build_darwin.sh +++ b/scripts/build_darwin.sh @@ -160,6 +160,8 @@ _build_macapp() { done cp dist/darwin-*/lib/ollama/*.so dist/darwin-*/lib/ollama/*.dylib dist/Ollama.app/Contents/Resources/ cp dist/darwin/*.dylib dist/Ollama.app/Contents/Resources/ + # Copy MLX metallib (architecture-independent, just use arm64 version) + cp dist/darwin-arm64/lib/ollama/*.metallib dist/Ollama.app/Contents/Resources/ 2>/dev/null || true else cp -a dist/darwin/ollama dist/Ollama.app/Contents/Resources/ollama cp dist/darwin/*.so dist/darwin/*.dylib dist/Ollama.app/Contents/Resources/ @@ -170,7 +172,7 @@ _build_macapp() { # Sign if [ -n "$APPLE_IDENTITY" ]; then codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime dist/Ollama.app/Contents/Resources/ollama - for lib in dist/Ollama.app/Contents/Resources/*.so dist/Ollama.app/Contents/Resources/*.dylib dist/Ollama.app/Contents/Resources/ollama-mlx ; do + for lib in dist/Ollama.app/Contents/Resources/*.so dist/Ollama.app/Contents/Resources/*.dylib dist/Ollama.app/Contents/Resources/*.metallib dist/Ollama.app/Contents/Resources/ollama-mlx ; do codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime ${lib} done codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier com.electron.ollama --deep --options=runtime dist/Ollama.app @@ -178,7 +180,7 @@ _build_macapp() { rm -f dist/Ollama-darwin.zip ditto -c -k --keepParent dist/Ollama.app dist/Ollama-darwin.zip - (cd dist/Ollama.app/Contents/Resources/; tar -cf - ollama ollama-mlx *.so *.dylib) | gzip -9vc > dist/ollama-darwin.tgz + (cd dist/Ollama.app/Contents/Resources/; tar -cf - ollama ollama-mlx *.so *.dylib *.metallib 2>/dev/null) | gzip -9vc > dist/ollama-darwin.tgz # Notarize and Staple if [ -n "$APPLE_IDENTITY" ]; then diff --git a/x/imagegen/server.go b/x/imagegen/server.go index e980b4944..f9deb11d7 100644 --- a/x/imagegen/server.go +++ b/x/imagegen/server.go @@ -14,7 +14,9 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" + "strings" "sync" "time" @@ -84,6 +86,36 @@ func NewServer(modelName string) (*Server, error) { cmd := exec.Command(mlxExe, "runner", "--image-engine", "--model", modelName, "--port", strconv.Itoa(port)) cmd.Env = os.Environ() + // On Linux, set LD_LIBRARY_PATH to include MLX library directories + if runtime.GOOS == "linux" { + // Build library paths: start with LibOllamaPath, then add any mlx_* subdirectories + libraryPaths := []string{ml.LibOllamaPath} + if mlxDirs, err := filepath.Glob(filepath.Join(ml.LibOllamaPath, "mlx_*")); err == nil { + libraryPaths = append(libraryPaths, mlxDirs...) + } + + // Append existing LD_LIBRARY_PATH if set + if existingPath, ok := os.LookupEnv("LD_LIBRARY_PATH"); ok { + libraryPaths = append(libraryPaths, filepath.SplitList(existingPath)...) + } + + pathEnvVal := strings.Join(libraryPaths, string(filepath.ListSeparator)) + + // Update or add LD_LIBRARY_PATH in cmd.Env + found := false + for i := range cmd.Env { + if strings.HasPrefix(cmd.Env[i], "LD_LIBRARY_PATH=") { + cmd.Env[i] = "LD_LIBRARY_PATH=" + pathEnvVal + found = true + break + } + } + if !found { + cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+pathEnvVal) + } + slog.Debug("mlx subprocess library path", "LD_LIBRARY_PATH", pathEnvVal) + } + s := &Server{ cmd: cmd, port: port,