diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 10e3bda90..103600db4 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -230,7 +230,7 @@ jobs: runs-on: 'ubuntu-latest' base-image: "ubuntu:22.04" skip-drivers: 'false' - backend: "diffusers" + backend: "diffusers" dockerfile: "./backend/Dockerfile.python" context: "./backend" # CUDA 12 additional backends @@ -957,53 +957,38 @@ jobs: backend: "kitten-tts" dockerfile: "./backend/Dockerfile.python" context: "./backend" - diffusers-darwin: + backend-jobs-darwin: uses: ./.github/workflows/backend_build_darwin.yml + strategy: + matrix: + include: + - backend: "diffusers" + tag-suffix: "-metal-darwin-arm64-diffusers" + build-type: "mps" + - backend: "mlx" + tag-suffix: "-metal-darwin-arm64-mlx" + build-type: "mps" + - backend: "mlx-vlm" + tag-suffix: "-metal-darwin-arm64-mlx-vlm" + build-type: "mps" + - backend: "mlx-audio" + tag-suffix: "-metal-darwin-arm64-mlx-audio" + build-type: "mps" + - backend: "stablediffusion-ggml" + tag-suffix: "-metal-darwin-arm64-stablediffusion-ggml" + build-type: "metal" + lang: "go" + - backend: "whisper" + tag-suffix: "-metal-darwin-arm64-whisper" + build-type: "metal" + lang: "go" with: - backend: "diffusers" - build-type: "mps" + backend: ${{ matrix.backend }} + build-type: ${{ matrix.build-type }} go-version: "1.24.x" - tag-suffix: "-metal-darwin-arm64-diffusers" - use-pip: true - runs-on: "macOS-14" - secrets: - dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} - dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} - quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} - quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} - mlx-darwin: - uses: ./.github/workflows/backend_build_darwin.yml - with: - backend: "mlx" - build-type: "mps" - go-version: "1.24.x" - tag-suffix: "-metal-darwin-arm64-mlx" - runs-on: "macOS-14" - secrets: - dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} - dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} - quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} - quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} - mlx-vlm-darwin: - uses: ./.github/workflows/backend_build_darwin.yml - with: - backend: "mlx-vlm" - build-type: "mps" - go-version: "1.24.x" - tag-suffix: "-metal-darwin-arm64-mlx-vlm" - runs-on: "macOS-14" - secrets: - dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} - dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }} - quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} - quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} - mlx-audio-darwin: - uses: ./.github/workflows/backend_build_darwin.yml - with: - backend: "mlx-audio" - build-type: "mps" - go-version: "1.24.x" - tag-suffix: "-metal-darwin-arm64-mlx-audio" + tag-suffix: ${{ matrix.tag-suffix }} + lang: ${{ matrix.lang || '' }} + use-pip: ${{ matrix.backend == 'diffusers' }} runs-on: "macOS-14" secrets: dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/backend_build_darwin.yml b/.github/workflows/backend_build_darwin.yml index 5ec2fa4a1..c7433f571 100644 --- a/.github/workflows/backend_build_darwin.yml +++ b/.github/workflows/backend_build_darwin.yml @@ -16,6 +16,10 @@ on: description: 'Use pip to install dependencies' default: false type: boolean + lang: + description: 'Programming language (e.g. go)' + default: 'python' + type: string go-version: description: 'Go version to use' default: '1.24.x' @@ -49,26 +53,26 @@ jobs: uses: actions/checkout@v5 with: submodules: true - + - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: false - + # You can test your matrix by printing the current Go version - name: Display Go version run: go version - + - name: Dependencies run: | brew install protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm - + - name: Build ${{ inputs.backend }}-darwin run: | make protogen-go - BACKEND=${{ inputs.backend }} BUILD_TYPE=${{ inputs.build-type }} USE_PIP=${{ inputs.use-pip }} make build-darwin-python-backend - + BACKEND=${{ inputs.backend }} BUILD_TYPE=${{ inputs.build-type }} USE_PIP=${{ inputs.use-pip }} make build-darwin-${{ inputs.lang }}-backend + - name: Upload ${{ inputs.backend }}.tar uses: actions/upload-artifact@v4 with: @@ -85,20 +89,20 @@ jobs: with: name: ${{ inputs.backend }}-tar path: . - + - name: Install crane run: | curl -L https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz | tar -xz sudo mv crane /usr/local/bin/ - + - name: Log in to DockerHub run: | echo "${{ secrets.dockerPassword }}" | crane auth login docker.io -u "${{ secrets.dockerUsername }}" --password-stdin - + - name: Log in to quay.io run: | echo "${{ secrets.quayPassword }}" | crane auth login quay.io -u "${{ secrets.quayUsername }}" --password-stdin - + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -112,7 +116,7 @@ jobs: flavor: | latest=auto suffix=${{ inputs.tag-suffix }},onlatest=true - + - name: Docker meta id: quaymeta uses: docker/metadata-action@v5 @@ -126,13 +130,13 @@ jobs: flavor: | latest=auto suffix=${{ inputs.tag-suffix }},onlatest=true - + - name: Push Docker image (DockerHub) run: | for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n'); do crane push ${{ inputs.backend }}.tar $tag done - + - name: Push Docker image (Quay) run: | for tag in $(echo "${{ steps.quaymeta.outputs.tags }}" | tr ',' '\n'); do diff --git a/.github/workflows/backend_pr.yml b/.github/workflows/backend_pr.yml index c11e94c98..04b928372 100644 --- a/.github/workflows/backend_pr.yml +++ b/.github/workflows/backend_pr.yml @@ -12,7 +12,9 @@ jobs: runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix-darwin: ${{ steps.set-matrix.outputs.matrix-darwin }} has-backends: ${{ steps.set-matrix.outputs.has-backends }} + has-backends-darwin: ${{ steps.set-matrix.outputs.has-backends-darwin }} steps: - name: Checkout repository uses: actions/checkout@v5 @@ -56,3 +58,21 @@ jobs: strategy: fail-fast: true matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + backend-jobs-darwin: + needs: generate-matrix + uses: ./.github/workflows/backend_build_darwin.yml + if: needs.generate-matrix.outputs.has-backends-darwin == 'true' + with: + backend: ${{ matrix.backend }} + build-type: ${{ matrix.build-type }} + go-version: "1.24.x" + tag-suffix: ${{ matrix.tag-suffix }} + lang: ${{ matrix.lang || '' }} + use-pip: ${{ matrix.backend == 'diffusers' }} + runs-on: "macOS-14" + secrets: + quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }} + quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }} + strategy: + fail-fast: true + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-darwin) }} diff --git a/Makefile b/Makefile index 819104c84..46b3bcdec 100644 --- a/Makefile +++ b/Makefile @@ -376,6 +376,9 @@ backends/llama-cpp-darwin: build build-darwin-python-backend: build bash ./scripts/build/python-darwin.sh +build-darwin-go-backend: build + bash ./scripts/build/golang-darwin.sh + backends/mlx: BACKEND=mlx $(MAKE) build-darwin-python-backend ./local-ai backends install "ocifile://$(abspath ./backend-images/mlx.tar)" @@ -392,6 +395,10 @@ backends/mlx-audio: BACKEND=mlx-audio $(MAKE) build-darwin-python-backend ./local-ai backends install "ocifile://$(abspath ./backend-images/mlx-audio.tar)" +backends/stablediffusion-ggml-darwin: + BACKEND=stablediffusion-ggml BUILD_TYPE=metal $(MAKE) build-darwin-go-backend + ./local-ai backends install "ocifile://$(abspath ./backend-images/stablediffusion-ggml.tar)" + backend-images: mkdir -p backend-images @@ -537,4 +544,4 @@ build-launcher-darwin: build-launcher --outputDir "dist/" build-launcher-linux: - cd cmd/launcher && go run fyne.io/tools/cmd/fyne@latest package -os linux -icon ../../core/http/static/logo.png --executable $(LAUNCHER_BINARY_NAME)-linux && mv launcher.tar.xz ../../$(LAUNCHER_BINARY_NAME)-linux.tar.xz \ No newline at end of file + cd cmd/launcher && go run fyne.io/tools/cmd/fyne@latest package -os linux -icon ../../core/http/static/logo.png --executable $(LAUNCHER_BINARY_NAME)-linux && mv launcher.tar.xz ../../$(LAUNCHER_BINARY_NAME)-linux.tar.xz diff --git a/backend/go/stablediffusion-ggml/.gitignore b/backend/go/stablediffusion-ggml/.gitignore new file mode 100644 index 000000000..fb79b68be --- /dev/null +++ b/backend/go/stablediffusion-ggml/.gitignore @@ -0,0 +1,4 @@ +package/ +sources/ +libgosd.so +stablediffusion-ggml diff --git a/backend/go/stablediffusion-ggml/CMakeLists.txt b/backend/go/stablediffusion-ggml/CMakeLists.txt index f80aa2702..0d1d003e1 100644 --- a/backend/go/stablediffusion-ggml/CMakeLists.txt +++ b/backend/go/stablediffusion-ggml/CMakeLists.txt @@ -5,7 +5,11 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_subdirectory(./sources/stablediffusion-ggml.cpp) add_library(gosd MODULE gosd.cpp) -target_link_libraries(gosd PRIVATE stable-diffusion ggml stdc++fs) +target_link_libraries(gosd PRIVATE stable-diffusion ggml) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + target_link_libraries(gosd PRIVATE stdc++fs) +endif() target_include_directories(gosd PUBLIC stable-diffusion.cpp diff --git a/backend/go/stablediffusion-ggml/Makefile b/backend/go/stablediffusion-ggml/Makefile index 816a1ab9d..20eaf05b3 100644 --- a/backend/go/stablediffusion-ggml/Makefile +++ b/backend/go/stablediffusion-ggml/Makefile @@ -29,8 +29,6 @@ else ifeq ($(BUILD_TYPE),clblas) # If it's hipblas we do have also to set CC=/opt/rocm/llvm/bin/clang CXX=/opt/rocm/llvm/bin/clang++ else ifeq ($(BUILD_TYPE),hipblas) CMAKE_ARGS+=-DSD_HIPBLAS=ON -DGGML_HIPBLAS=ON -# If it's OSX, DO NOT embed the metal library - -DGGML_METAL_EMBED_LIBRARY=ON requires further investigation -# But if it's OSX without metal, disable it here else ifeq ($(BUILD_TYPE),vulkan) CMAKE_ARGS+=-DSD_VULKAN=ON -DGGML_VULKAN=ON else ifeq ($(OS),Darwin) @@ -74,10 +72,10 @@ libgosd.so: sources/stablediffusion-ggml.cpp CMakeLists.txt gosd.cpp gosd.h stablediffusion-ggml: main.go gosd.go libgosd.so CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o stablediffusion-ggml ./ -package: +package: stablediffusion-ggml bash package.sh -build: stablediffusion-ggml package +build: package clean: - rm -rf libgosd.o build stablediffusion-ggml + rm -rf libgosd.so build stablediffusion-ggml package sources diff --git a/backend/go/stablediffusion-ggml/package.sh b/backend/go/stablediffusion-ggml/package.sh index 6e56dcd95..f8cda2f41 100755 --- a/backend/go/stablediffusion-ggml/package.sh +++ b/backend/go/stablediffusion-ggml/package.sh @@ -10,9 +10,9 @@ CURDIR=$(dirname "$(realpath $0)") # Create lib directory mkdir -p $CURDIR/package/lib -cp -avrf $CURDIR/libgosd.so $CURDIR/package/ -cp -avrf $CURDIR/stablediffusion-ggml $CURDIR/package/ -cp -rfv $CURDIR/run.sh $CURDIR/package/ +cp -avf $CURDIR/libgosd.so $CURDIR/package/ +cp -avf $CURDIR/stablediffusion-ggml $CURDIR/package/ +cp -fv $CURDIR/run.sh $CURDIR/package/ # Detect architecture and copy appropriate libraries if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then @@ -43,6 +43,8 @@ elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2 cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1 cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0 +elif [ $(uname -s) = "Darwin" ]; then + echo "Detected Darwin" else echo "Error: Could not detect architecture" exit 1 diff --git a/backend/go/whisper/CMakeLists.txt b/backend/go/whisper/CMakeLists.txt index 7a1a773e3..60cc178f2 100644 --- a/backend/go/whisper/CMakeLists.txt +++ b/backend/go/whisper/CMakeLists.txt @@ -6,7 +6,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_subdirectory(./sources/whisper.cpp) add_library(gowhisper MODULE gowhisper.cpp) -target_link_libraries(gowhisper PRIVATE whisper ggml stdc++fs) +target_link_libraries(gowhisper PRIVATE whisper ggml) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + target_link_libraries(gosd PRIVATE stdc++fs) +endif() set_property(TARGET gowhisper PROPERTY CXX_STANDARD 17) set_target_properties(gowhisper PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/backend/go/whisper/Makefile b/backend/go/whisper/Makefile index a976b3ae8..75beeb08d 100644 --- a/backend/go/whisper/Makefile +++ b/backend/go/whisper/Makefile @@ -68,10 +68,10 @@ libgowhisper.so: sources/whisper.cpp CMakeLists.txt gowhisper.cpp gowhisper.h whisper: main.go gowhisper.go libgowhisper.so CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o whisper ./ -package: +package: whisper bash package.sh -build: whisper package +build: package clean: rm -rf libgowhisper.o build whisper diff --git a/backend/go/whisper/package.sh b/backend/go/whisper/package.sh index 48e03e8e5..4e5c4fee2 100755 --- a/backend/go/whisper/package.sh +++ b/backend/go/whisper/package.sh @@ -10,8 +10,8 @@ CURDIR=$(dirname "$(realpath $0)") # Create lib directory mkdir -p $CURDIR/package/lib -cp -avrf $CURDIR/whisper $CURDIR/libgowhisper.so $CURDIR/package/ -cp -rfv $CURDIR/run.sh $CURDIR/package/ +cp -avf $CURDIR/whisper $CURDIR/libgowhisper.so $CURDIR/package/ +cp -fv $CURDIR/run.sh $CURDIR/package/ # Detect architecture and copy appropriate libraries if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then @@ -42,6 +42,8 @@ elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2 cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1 cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0 +elif [ $(uname -s) = "Darwin" ]; then + echo "Detected Darwin" else echo "Error: Could not detect architecture" exit 1 diff --git a/scripts/build/golang-darwin.sh b/scripts/build/golang-darwin.sh new file mode 100644 index 000000000..02d502d23 --- /dev/null +++ b/scripts/build/golang-darwin.sh @@ -0,0 +1,17 @@ +#!/bin/bash -eux + +export BUILD_TYPE="${BUILD_TYPE:-metal}" + +mkdir -p backend-images +make -C backend/go/${BACKEND} build + +PLATFORMARCH="${PLATFORMARCH:-darwin/arm64}" +IMAGE_NAME="${IMAGE_NAME:-localai/${BACKEND}-darwin}" + +./local-ai util create-oci-image \ + backend/go/${BACKEND}/. \ + --output ./backend-images/${BACKEND}.tar \ + --image-name $IMAGE_NAME \ + --platform $PLATFORMARCH + +make -C backend/go/${BACKEND} clean diff --git a/scripts/changed-backends.js b/scripts/changed-backends.js index 717d713dc..a7083e584 100644 --- a/scripts/changed-backends.js +++ b/scripts/changed-backends.js @@ -5,10 +5,10 @@ import { Octokit } from "@octokit/core"; // Load backend.yml and parse matrix.include const backendYml = yaml.load(fs.readFileSync(".github/workflows/backend.yml", "utf8")); const jobs = backendYml.jobs; -const backendJob = jobs["backend-jobs"]; -const strategy = backendJob.strategy; -const matrix = strategy.matrix; -const includes = matrix.include; +const backendJobs = jobs["backend-jobs"]; +const backendJobsDarwin = jobs["backend-jobs-darwin"]; +const includes = backendJobs.strategy.matrix.include; +const includesDarwin = backendJobsDarwin.strategy.matrix.include; // Set up Octokit for PR changed files const token = process.env.GITHUB_TOKEN; @@ -58,6 +58,14 @@ function inferBackendPath(item) { return null; } +function inferBackendPathDarwin(item) { + if (!item.lang) { + return `backend/python/${item.backend}/`; + } + + return `backend/${item.lang}/${item.backend}/`; +} + (async () => { const changedFiles = await getChangedFiles(); @@ -69,11 +77,21 @@ function inferBackendPath(item) { return changedFiles.some(file => file.startsWith(backendPath)); }); + const filteredDarwin = includesDarwin.filter(item => { + const backendPath = inferBackendPathDarwin(item); + return changedFiles.some(file => file.startsWith(backendPath)); + }) + console.log("Filtered files:", filtered); + console.log("Filtered files Darwin:", filteredDarwin); const hasBackends = filtered.length > 0 ? 'true' : 'false'; + const hasBackendsDarwin = filteredDarwin.length > 0 ? 'true' : 'false'; console.log("Has backends?:", hasBackends); + console.log("Has Darwin backends?:", hasBackendsDarwin); fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends=${hasBackends}\n`); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends-darwin=${hasBackendsDarwin}\n`); fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify({ include: filtered })}\n`); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix-darwin=${JSON.stringify({ include: filteredDarwin })}\n`); })();