Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/backend-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# CUDA / ROCm / SYCL / Vulkan variants).
# - macOS -> the `includeDarwin:` matrix (Apple Silicon / arm64; Metal where
# the engine supports it, otherwise a native arm64 CPU build).
# - Windows -> the `includeWindows:` matrix (x86_64 / amd64; native builds
# under MSYS2, no WSL/Docker — see backend_build_windows.yml).
#
# New backends must target EVERY OS they can build for, not just Linux. A backend
# listed only under `include:` is silently unavailable on macOS even when its code
Expand All @@ -24,6 +26,12 @@
# `metal:` capability + `metal-<backend>` image entries, a `run.sh` Darwin/DYLD
# branch for C/C++ backends, and the inferBackendPathDarwin case in
# scripts/lib/backend-filter.mjs so the path filter actually builds it).
#
# Windows builds are bespoke for now: every entry builds via a per-backend make
# target + MSYS2 build script (see scripts/build/llama-cpp-windows.sh), the
# index.yaml `windows:` capability + `windows-<backend>` image entries, a
# run-windows launcher next to run.sh, and the inferBackendPathWindows case in
# scripts/lib/backend-filter.mjs.

# Linux matrix (consumed by backend-jobs).
include:
Expand Down Expand Up @@ -6540,3 +6548,11 @@ includeDarwin:
- backend: "ds4"
tag-suffix: "-metal-darwin-arm64-ds4"
lang: "go"

# Windows matrix (consumed by backend-jobs-windows).
# Native windows/amd64 builds under MSYS2 — no WSL, no Docker. Each entry builds
# via a bespoke make target + scripts/build/<backend>-windows.sh.
includeWindows:
- backend: "llama-cpp"
tag-suffix: "-windows-amd64-llama-cpp"
lang: "go"
22 changes: 22 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ jobs:
outputs:
matrix-multiarch: ${{ steps.set-matrix.outputs['matrix-multiarch'] }}
matrix-darwin: ${{ steps.set-matrix.outputs['matrix-darwin'] }}
matrix-windows: ${{ steps.set-matrix.outputs['matrix-windows'] }}
merge-matrix-multiarch: ${{ steps.set-matrix.outputs['merge-matrix-multiarch'] }}
has-backends-multiarch: ${{ steps.set-matrix.outputs['has-backends-multiarch'] }}
has-backends-darwin: ${{ steps.set-matrix.outputs['has-backends-darwin'] }}
has-backends-windows: ${{ steps.set-matrix.outputs['has-backends-windows'] }}
has-merges-multiarch: ${{ steps.set-matrix.outputs['has-merges-multiarch'] }}
# Single-arch backends are sharded across SINGLEARCH_SHARDS matrix jobs to
# stay under GitHub's 256-jobs-per-matrix limit (see changed-backends.js).
Expand Down Expand Up @@ -368,3 +370,23 @@ jobs:
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-darwin) }}

backend-jobs-windows:
needs: generate-matrix
if: needs.generate-matrix.outputs.has-backends-windows == 'true'
uses: ./.github/workflows/backend_build_windows.yml
with:
backend: ${{ matrix.backend }}
build-type: ${{ matrix.build-type }}
go-version: "1.25.x"
tag-suffix: ${{ matrix.tag-suffix }}
lang: ${{ matrix.lang || 'go' }}
runs-on: "windows-latest"
secrets:
dockerUsername: ${{ secrets.DOCKERHUB_USERNAME }}
dockerPassword: ${{ secrets.DOCKERHUB_PASSWORD }}
quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }}
quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-windows) }}
256 changes: 256 additions & 0 deletions .github/workflows/backend_build_windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
---
name: 'build windows backend container images (reusable)'

on:
workflow_call:
inputs:
backend:
description: 'Backend to build'
required: true
type: string
build-type:
description: 'Build type'
default: ''
type: string
lang:
description: 'Programming language (e.g. go)'
default: 'python'
type: string
go-version:
description: 'Go version to use'
default: '1.25.x'
type: string
tag-suffix:
description: 'Tag suffix for the built image'
required: true
type: string
runs-on:
description: 'Runner to use'
default: 'windows-latest'
type: string
secrets:
dockerUsername:
required: false
dockerPassword:
required: false
quayUsername:
required: true
quayPassword:
required: true

jobs:
windows-backend-build:
runs-on: ${{ inputs.runs-on }}
strategy:
matrix:
go-version: ['${{ inputs.go-version }}']
env:
# Every CMake variant below (gRPC + the three llama.cpp variants) compiles
# the same source trees with overlapping flags; ccache dedupes them.
# CCACHE_DIR is set in a run step (below), not here: a job-level env value
# is used verbatim, and MSYS2's $HOME must be expanded at runtime so it
# matches the ~/.cache/ccache path handed to actions/cache.
CMAKE_ARGS: "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
steps:
- name: Clone
uses: actions/checkout@v7
with:
submodules: true

- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
# Caches ~/go/pkg/mod and %LOCALAPPDATA%\go-build keyed on go.sum.
cache: true

- name: Display Go version
run: go version

# ---- MSYS2 toolchain ----
# Native windows builds need a mingw gcc toolchain; windows-latest ships
# none by default (build-test-windows works only because LocalAI itself
# builds with CGO_ENABLED=0). UCRT64 matches the /ucrt64/bin DLL bundling
# in scripts/build/llama-cpp-windows.sh. `perl` is required by gRPC's
# third_party/openssl build (openssl's Configure is a perl script).
# `install` is a folded scalar (>-): every line folds into the pacman
# command, so no `#` comments may live inside it - they would become
# literal package names ("target not found: Vulkan:"). Vulkan packages:
# headers + loader (ucrt64) for FindVulkan, and the mingw64 shaderc
# build for glslc (no ucrt64 shaderc exists; glslc is a standalone tool
# so the prefix split is irrelevant). The loader's vulkan-1.dll is
# bundled next to the image's DLLs; ggml-vulkan loads it dynamically, so
# it is never a hard import. spirv-headers provides the cmake config
# ggml-vulkan requires (find_package(SPIRV-Headers CONFIG REQUIRED)).
- name: Set up MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: false
install: >-
git
make
cmake
mingw-w64-ucrt-x86_64-cmake
ninja
pkg-config
perl
patch
unzip
curl
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-gcc-libs
mingw-w64-ucrt-x86_64-binutils
mingw-w64-ucrt-x86_64-ccache
mingw-w64-ucrt-x86_64-vulkan-headers
mingw-w64-ucrt-x86_64-vulkan-loader
mingw-w64-ucrt-x86_64-spirv-headers
mingw-w64-x86_64-shaderc

# Run steps that need bash/msys2 tools (make, ccache, grep) declare
# `shell: msys2 {0}` individually instead of a job-level defaults block:
# the msys2 shell only exists once the Set up MSYS2 step above has run.
- name: Set CCACHE_DIR
shell: msys2 {0}
run: echo "CCACHE_DIR=$HOME/.cache/ccache" >> "$GITHUB_ENV"

- name: Display toolchain versions
shell: msys2 {0}
run: |
gcc --version | head -1
cmake --version | head -1
make --version | head -1
ccache --version | head -1

# ---- ccache for llama.cpp CMake builds ----
# Same shape as the Darwin workflow: key on the pinned LLAMA_VERSION so a
# pin bump invalidates cleanly; restore-keys fall back to the latest entry
# for the same pin so unchanged TUs stay warm.
- name: Compute llama.cpp version
if: inputs.backend == 'llama-cpp'
id: llama-version
shell: msys2 {0}
run: |
version=$(grep '^LLAMA_VERSION' backend/cpp/llama-cpp/Makefile | head -1 | cut -d= -f2 | cut -d'?' -f1 | tr -d ' ')
echo "version=${version}" >> "$GITHUB_OUTPUT"

- name: Restore ccache
if: inputs.backend == 'llama-cpp'
id: ccache-cache
uses: actions/cache/restore@v6
with:
path: ~/.cache/ccache
key: ccache-llama-windows-amd64-${{ steps.llama-version.outputs.version }}-${{ github.run_id }}
restore-keys: |
ccache-llama-windows-amd64-${{ steps.llama-version.outputs.version }}-

# Only llama-cpp has a windows build path today - the matrix's
# includeWindows section lists exactly this backend. Fail loudly rather
# than upload an empty tar if a future entry dispatches here without a
# build step of its own. Keep in sync with WINDOWS_BESPOKE_BUILDERS in
# scripts/lib/backend-filter.mjs.
- name: Check backend is supported
if: inputs.backend != 'llama-cpp'
run: |
echo "::error::no windows build path for backend '${{ inputs.backend }}'"
exit 1

# The msys2 shell below resets PATH, so the Go toolchain setup-go put on
# the runner PATH is invisible to it (and setup-go only exports GOROOT
# for Go < 1.9). Resolve the install dir with the default shell, where
# `go` is reachable, and hand it to the script to prepend.
- name: Resolve Go toolchain path
id: go-toolchain
if: inputs.backend == 'llama-cpp'
shell: bash
run: echo "root=$(go env GOROOT)" >> "$GITHUB_OUTPUT"

- name: Build ${{ inputs.backend }} (llama-cpp)
if: inputs.backend == 'llama-cpp'
shell: msys2 {0}
env:
GO_TOOLCHAIN_ROOT: ${{ steps.go-toolchain.outputs.root }}
run: |
make backends/llama-cpp-windows

- name: ccache stats
if: inputs.backend == 'llama-cpp'
shell: msys2 {0}
run: ccache -s

- name: Save ccache
if: inputs.backend == 'llama-cpp' && github.event_name != 'pull_request'
uses: actions/cache/save@v6
with:
path: ~/.cache/ccache
key: ccache-llama-windows-amd64-${{ steps.llama-version.outputs.version }}-${{ github.run_id }}

- name: Upload ${{ inputs.backend }}.tar
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.backend }}-tar
path: backend-images/${{ inputs.backend }}.tar

windows-backend-publish:
needs: windows-backend-build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Download ${{ inputs.backend }}.tar
uses: actions/download-artifact@v8
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@v6
with:
images: |
localai/localai-backends
tags: |
type=ref,event=branch
type=semver,pattern={{raw}}
type=sha
flavor: |
latest=auto
suffix=${{ inputs.tag-suffix }},onlatest=true

- name: Docker meta
id: quaymeta
uses: docker/metadata-action@v6
with:
images: |
quay.io/go-skynet/local-ai-backends
tags: |
type=ref,event=branch
type=semver,pattern={{raw}}
type=sha
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
crane push ${{ inputs.backend }}.tar $tag
done
20 changes: 20 additions & 0 deletions .github/workflows/backend_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ jobs:
outputs:
matrix-multiarch: ${{ steps.set-matrix.outputs['matrix-multiarch'] }}
matrix-darwin: ${{ steps.set-matrix.outputs['matrix-darwin'] }}
matrix-windows: ${{ steps.set-matrix.outputs['matrix-windows'] }}
merge-matrix-multiarch: ${{ steps.set-matrix.outputs['merge-matrix-multiarch'] }}
has-backends-multiarch: ${{ steps.set-matrix.outputs['has-backends-multiarch'] }}
has-backends-darwin: ${{ steps.set-matrix.outputs['has-backends-darwin'] }}
has-backends-windows: ${{ steps.set-matrix.outputs['has-backends-windows'] }}
has-merges-multiarch: ${{ steps.set-matrix.outputs['has-merges-multiarch'] }}
# Single-arch backends are sharded across SINGLEARCH_SHARDS matrix jobs to
# stay under GitHub's 256-jobs-per-matrix limit (see changed-backends.js).
Expand Down Expand Up @@ -292,3 +294,21 @@ jobs:
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-darwin) }}

backend-jobs-windows:
needs: generate-matrix
uses: ./.github/workflows/backend_build_windows.yml
if: needs.generate-matrix.outputs.has-backends-windows == 'true'
with:
backend: ${{ matrix.backend }}
build-type: ${{ matrix.build-type }}
go-version: "1.25.x"
tag-suffix: ${{ matrix.tag-suffix }}
lang: ${{ matrix.lang || 'go' }}
runs-on: "windows-latest"
secrets:
quayUsername: ${{ secrets.LOCALAI_REGISTRY_USERNAME }}
quayPassword: ${{ secrets.LOCALAI_REGISTRY_PASSWORD }}
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix-windows) }}
29 changes: 29 additions & 0 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ jobs:
- name: Run GoReleaser
run: |
make ${{ github.event_name == 'pull_request' && 'dev-dist-single' || 'dev-dist' }}
# Windows ships no GNU toolchain on the default runner: make is not
# installed there and Git for Windows does not ship it (it bundles sh, uname,
# unzip, grep, awk, sed only), so it is installed via Chocolatey below. The
# Makefile finds Git for Windows' sh itself and runs recipes through it (see
# the SHELL setup at the top of the Makefile), so the explicit usr/bin PATH
# step below is belt-and-suspenders for environments where make must locate
# sh before the makefile is read. The server builds with CGO_ENABLED=0, so no
# mingw/gcc is needed; the Makefile downloads protoc and installs the Go
# protobuf plugins itself (see the protoc/protogen-go targets).
build-test-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.25
- name: Add Git usr/bin to PATH
run: |
echo "$env:ProgramFiles\Git\usr\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH
- name: Install GNU Make
run: choco install make -y
- name: Build LocalAI (CGO disabled, mirrors release config)
env:
CGO_ENABLED: '0'
run: make build
launcher-build-darwin:
runs-on: macos-latest
steps:
Expand Down
Loading