llama-server followups (#16353)

* llama-server followups

Misc fixes for #16031
- Add back dropped ROCm build flag for multi-GPU support on windows
- Fix amdhip64_*.dll version detection for "latest" selection
- Fix embeddings API for consistent normalize behavior with prior versions

* ci: set up for automated llama.cpp update testing

* reduce batch for fa-disabled, and constrained vram

* mlx: fix v3 load bug on m5

Imagegen was incorrectly loading v3 first.  This DRYs out the loading code so imagegen gets the same new v4/v3 selection logic.

* fix reload bug on embedding models

* bump version

* steer user how to enable iGPU when disabled
This commit is contained in:
Daniel Hiltgen
2026-06-01 10:44:21 -07:00
committed by GitHub
parent 0e93ccc2cd
commit 630882621b
15 changed files with 967 additions and 158 deletions
+596
View File
@@ -0,0 +1,596 @@
name: test-llamacpp-update
# PR validation artifacts from this workflow are intentionally unsigned and not
# notarized. They are for llama.cpp update testing only and must not be
# published as release artifacts.
on:
pull_request:
paths:
- 'LLAMA_CPP_VERSION'
permissions:
contents: read
env:
CGO_CFLAGS: '-O3'
CGO_CXXFLAGS: '-O3'
jobs:
setup-environment:
runs-on: ubuntu-latest
outputs:
GOFLAGS: ${{ steps.goflags.outputs.GOFLAGS }}
VERSION: ${{ steps.goflags.outputs.VERSION }}
vendorsha: ${{ steps.goflags.outputs.vendorsha }}
steps:
- uses: actions/checkout@v4
- name: Set environment
id: goflags
shell: bash
run: |
set -euo pipefail
VERSION="0.0.0-llamacpp-${GITHUB_SHA::7}"
{
echo "GOFLAGS='-ldflags=-w -s \"-X=github.com/ollama/ollama/version.Version=${VERSION}\" \"-X=github.com/ollama/ollama/server.mode=release\"'"
echo "VERSION=${VERSION}"
echo "vendorsha=$(cat LLAMA_CPP_VERSION)-$(cat MLX_VERSION)-$(cat MLX_C_VERSION)"
} >>"${GITHUB_OUTPUT}"
darwin-build:
runs-on: macos-26-xlarge
needs: setup-environment
env:
GOFLAGS: ${{ needs.setup-environment.outputs.GOFLAGS }}
VERSION: ${{ needs.setup-environment.outputs.VERSION }}
CGO_CFLAGS: '-mmacosx-version-min=14.0 -O3'
CGO_CXXFLAGS: '-mmacosx-version-min=14.0 -O3'
CGO_LDFLAGS: '-mmacosx-version-min=14.0 -O3'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
LLAMA_CPP_VERSION
MLX_VERSION
MLX_C_VERSION
- name: Build unsigned Darwin runtime
run: ./scripts/build_darwin.sh build package
- name: Log build results
run: ls -l dist/
- uses: actions/upload-artifact@v4
with:
name: ollama-darwin.tgz
path: dist/ollama-darwin.tgz
compression-level: 0
# Build payload export stages independently and combine the exported
# filesystem artifacts below. This preserves parallelism without Docker
# registry credentials or oversized GitHub layer caches.
linux-payloads:
runs-on: ${{ matrix.arch == 'arm64' && 'linux-arm64' || 'linux' }}
needs: setup-environment
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
target: publish-llama-server-cpu
payload: cpu
- arch: amd64
target: publish-llama-server-cuda_v12
payload: cuda_v12
- arch: amd64
target: publish-llama-server-cuda_v13
payload: cuda_v13
- arch: amd64
target: publish-llama-server-rocm_v7_2
payload: rocm_v7_2
- arch: amd64
target: publish-llama-server-vulkan
payload: vulkan
- arch: arm64
target: publish-llama-server-cpu
payload: cpu
- arch: arm64
target: publish-llama-server-cuda_v12
payload: cuda_v12
- arch: arm64
target: publish-llama-server-cuda_v13
payload: cuda_v13
- arch: arm64
target: publish-llama-server-cuda_jetpack5
payload: cuda_jetpack5
- arch: arm64
target: publish-llama-server-cuda_jetpack6
payload: cuda_jetpack6
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/${{ matrix.arch }}
target: ${{ matrix.target }}
provenance: false
sbom: false
build-args: |
GOFLAGS=${{ needs.setup-environment.outputs.GOFLAGS }}
CGO_CFLAGS=${{ env.CGO_CFLAGS }}
CGO_CXXFLAGS=${{ env.CGO_CXXFLAGS }}
APT_MIRROR=http://azure.archive.ubuntu.com/ubuntu
APT_PORTS_MIRROR=http://azure.ports.ubuntu.com/ubuntu-ports
outputs: type=local,dest=${{ runner.temp }}/payload
- name: Pack Linux payload
shell: bash
run: |
set -euo pipefail
tar -C "${{ runner.temp }}/payload" -cf - . | zstd -9 -T0 >"${{ runner.temp }}/linux-payload-${{ matrix.arch }}-${{ matrix.payload }}.tar.zst"
- uses: actions/upload-artifact@v4
with:
name: linux-payload-${{ matrix.arch }}-${{ matrix.payload }}
path: ${{ runner.temp }}/linux-payload-${{ matrix.arch }}-${{ matrix.payload }}.tar.zst
compression-level: 0
linux-go:
runs-on: ${{ matrix.arch == 'arm64' && 'linux-arm64' || 'linux' }}
needs: setup-environment
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/${{ matrix.arch }}
target: publish-go
provenance: false
sbom: false
build-args: |
GOFLAGS=${{ needs.setup-environment.outputs.GOFLAGS }}
CGO_CFLAGS=${{ env.CGO_CFLAGS }}
CGO_CXXFLAGS=${{ env.CGO_CXXFLAGS }}
APT_MIRROR=http://azure.archive.ubuntu.com/ubuntu
APT_PORTS_MIRROR=http://azure.ports.ubuntu.com/ubuntu-ports
outputs: type=local,dest=${{ runner.temp }}/payload
- name: Pack Linux Go payload
shell: bash
run: |
set -euo pipefail
tar -C "${{ runner.temp }}/payload" -cf - . | zstd -9 -T0 >"${{ runner.temp }}/linux-payload-${{ matrix.arch }}-go.tar.zst"
- uses: actions/upload-artifact@v4
with:
name: linux-payload-${{ matrix.arch }}-go
path: ${{ runner.temp }}/linux-payload-${{ matrix.arch }}-go.tar.zst
compression-level: 0
# MLX payloads are intentionally excluded from this workflow; the Dockerfile
# still exposes publish-mlx for a separate MLX-specific workflow.
linux-bundles:
runs-on: ${{ matrix.arch == 'arm64' && 'linux-arm64' || 'linux' }}
needs: [linux-payloads, linux-go]
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: linux-payload-${{ matrix.arch }}-*
path: ${{ runner.temp }}/payloads
merge-multiple: true
- name: Assemble Linux payload tree
shell: bash
run: |
set -euo pipefail
src="${{ runner.temp }}/payloads"
arch="${{ matrix.arch }}"
dest="dist/linux-${arch}"
copy_payload() {
local name="$1"
local payload="${src}/linux-payload-${arch}-${name}.tar.zst"
if [ ! -f "${payload}" ]; then
echo "missing payload ${payload}"
exit 1
fi
zstd -d <"${payload}" | tar -C "${dest}" -xf -
}
mkdir -p "${dest}"
copy_payload go
copy_payload cpu
copy_payload cuda_v12
copy_payload cuda_v13
if [ "${arch}" = "amd64" ]; then
copy_payload vulkan
copy_payload rocm_v7_2
else
copy_payload cuda_jetpack5
copy_payload cuda_jetpack6
fi
./scripts/deduplicate_cuda_libs.sh "${dest}"
- name: Verify Linux build payloads
shell: bash
run: |
set -euo pipefail
base="dist/linux-${{ matrix.arch }}"
for payload in \
"${base}/bin/ollama" \
"${base}/lib/ollama/llama-server"
do
[ -f "${payload}" ] || { echo "missing ${payload}"; exit 1; }
done
- name: Create archive input lists
shell: bash
run: |
set -euo pipefail
for COMPONENT in bin/* lib/ollama/*; do
case "${COMPONENT}" in
bin/ollama*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}.tar.in ;;
lib/ollama/*.so*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}.tar.in ;;
lib/ollama/llama-server*|lib/ollama/llama-quantize*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}.tar.in ;;
lib/ollama/cuda_v*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}.tar.in ;;
lib/ollama/vulkan*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}.tar.in ;;
lib/ollama/mlx*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}-mlx.tar.in ;;
lib/ollama/include*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}-mlx.tar.in ;;
lib/ollama/cuda_jetpack5) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}-jetpack5.tar.in ;;
lib/ollama/cuda_jetpack6) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}-jetpack6.tar.in ;;
lib/ollama/rocm_v*) echo "${COMPONENT}" >>ollama-linux-${{ matrix.arch }}-rocm.tar.in ;;
esac
done
working-directory: dist/linux-${{ matrix.arch }}
- name: Log archive input lists
shell: bash
run: |
set -euo pipefail
for ARCHIVE in dist/linux-${{ matrix.arch }}/*.tar.in; do
echo "${ARCHIVE}"
cat "${ARCHIVE}"
done
- name: Create Linux archives
shell: bash
run: |
set -euo pipefail
for ARCHIVE in dist/linux-${{ matrix.arch }}/*.tar.in; do
tar c -C dist/linux-${{ matrix.arch }} -T "${ARCHIVE}" --owner 0 --group 0 | zstd -19 -T0 >"$(basename "${ARCHIVE//.*/}.tar.zst")" &
done
wait
- uses: actions/upload-artifact@v4
with:
name: ollama-linux-${{ matrix.arch }}.tar.zst
path: ollama-linux-${{ matrix.arch }}.tar.zst
compression-level: 0
- if: matrix.arch == 'amd64'
uses: actions/upload-artifact@v4
with:
name: ollama-linux-amd64-rocm.tar.zst
path: ollama-linux-amd64-rocm.tar.zst
compression-level: 0
- if: matrix.arch == 'arm64'
uses: actions/upload-artifact@v4
with:
name: ollama-linux-arm64-jetpack5.tar.zst
path: ollama-linux-arm64-jetpack5.tar.zst
compression-level: 0
- if: matrix.arch == 'arm64'
uses: actions/upload-artifact@v4
with:
name: ollama-linux-arm64-jetpack6.tar.zst
path: ollama-linux-arm64-jetpack6.tar.zst
compression-level: 0
windows-depends:
needs: setup-environment
strategy:
fail-fast: false
matrix:
os: [windows]
arch: [amd64]
preset: ['CPU']
build-steps: ['cpu cpuArm64']
include:
- os: windows
arch: amd64
preset: 'CUDA 12'
build-steps: cuda12
install: https://developer.download.nvidia.com/compute/cuda/12.8.0/local_installers/cuda_12.8.0_571.96_windows.exe
cuda-components:
- '"cudart"'
- '"nvcc"'
- '"cublas"'
- '"cublas_dev"'
cuda-version: '12.8'
- os: windows
arch: amd64
preset: 'CUDA 13'
build-steps: cuda13
install: https://developer.download.nvidia.com/compute/cuda/13.0.0/local_installers/cuda_13.0.0_windows.exe
cuda-components:
- '"cudart"'
- '"nvcc"'
- '"cublas"'
- '"cublas_dev"'
- '"crt"'
- '"nvvm"'
- '"nvptxcompiler"'
cuda-version: '13.0'
- os: windows
arch: amd64
preset: 'ROCm 7'
build-steps: rocm7
install: https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-26.Q1-Win11-For-HIP.exe
rocm-version: '7.1'
- os: windows
arch: amd64
preset: Vulkan
build-steps: vulkan
install: https://sdk.lunarg.com/sdk/download/1.4.321.1/windows/vulkansdk-windows-X64-1.4.321.1.exe
runs-on: ${{ matrix.arch == 'arm64' && format('{0}-{1}', matrix.os, matrix.arch) || matrix.os }}
env:
GOFLAGS: ${{ needs.setup-environment.outputs.GOFLAGS }}
VERSION: ${{ needs.setup-environment.outputs.VERSION }}
steps:
- name: Install system dependencies
run: |
choco install -y --no-progress ccache ninja
if (Get-Command ccache -ErrorAction SilentlyContinue) {
ccache -o cache_dir=${{ github.workspace }}\.ccache
}
- if: matrix.preset == 'CPU'
name: Install Windows ARM64 cross compiler
run: |
Invoke-WebRequest -Uri "https://github.com/mstorsjo/llvm-mingw/releases/download/20240619/llvm-mingw-20240619-ucrt-x86_64.zip" -OutFile "${{ runner.temp }}\llvm-mingw-ucrt.zip"
Expand-Archive -Path ${{ runner.temp }}\llvm-mingw-ucrt.zip -DestinationPath "C:\Program Files\"
$installPath=(Resolve-Path -Path "C:\Program Files\llvm-mingw-*-ucrt-x86_64").path
if (!(Test-Path "$installPath\bin\aarch64-w64-mingw32-gcc.exe")) {
throw "llvm-mingw x86_64 package is missing the aarch64 cross compiler"
}
- if: startsWith(matrix.preset, 'CUDA ') || startsWith(matrix.preset, 'ROCm ') || startsWith(matrix.preset, 'Vulkan')
id: cache-install
uses: actions/cache/restore@v4
with:
path: |
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA
C:\Program Files\AMD\ROCm
C:\VulkanSDK
key: ${{ matrix.install }}
- if: startsWith(matrix.preset, 'CUDA ')
name: Install CUDA ${{ matrix.cuda-version }}
run: |
$ErrorActionPreference = "Stop"
if ("${{ steps.cache-install.outputs.cache-hit }}" -ne 'true') {
Invoke-WebRequest -Uri "${{ matrix.install }}" -OutFile "install.exe"
$subpackages = @(${{ join(matrix.cuda-components, ', ') }}) | Foreach-Object {"${_}_${{ matrix.cuda-version }}"}
Start-Process -FilePath .\install.exe -ArgumentList (@("-s") + $subpackages) -NoNewWindow -Wait
}
$cudaPath = (Resolve-Path "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\*").path
echo "$cudaPath\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- if: startsWith(matrix.preset, 'ROCm')
name: Install ROCm ${{ matrix.rocm-version }}
run: |
$ErrorActionPreference = "Stop"
if ("${{ steps.cache-install.outputs.cache-hit }}" -ne 'true') {
Invoke-WebRequest -Uri "${{ matrix.install }}" -OutFile "install.exe"
Start-Process -FilePath .\install.exe -ArgumentList '-install' -NoNewWindow -Wait
}
$hipPath = (Resolve-Path "C:\Program Files\AMD\ROCm\*").path
echo "$hipPath\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
echo "CC=$hipPath\bin\clang.exe" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "CXX=$hipPath\bin\clang++.exe" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "HIPCXX=$hipPath\bin\clang++.exe" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "HIP_PLATFORM=amd" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "CMAKE_PREFIX_PATH=$hipPath" | Out-File -FilePath $env:GITHUB_ENV -Append
- if: matrix.preset == 'Vulkan'
name: Install Vulkan
run: |
$ErrorActionPreference = "Stop"
if ("${{ steps.cache-install.outputs.cache-hit }}" -ne 'true') {
Invoke-WebRequest -Uri "${{ matrix.install }}" -OutFile "install.exe"
Start-Process -FilePath .\install.exe -ArgumentList "-c","--am","--al","in" -NoNewWindow -Wait
}
$vulkanPath = (Resolve-Path "C:\VulkanSDK\*").path
$vulkanRuntime = Join-Path $vulkanPath "Helpers\VulkanRT.exe"
if (Test-Path $vulkanRuntime) {
Start-Process -FilePath $vulkanRuntime -ArgumentList "/s" -NoNewWindow -Wait
}
echo "$vulkanPath\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
echo "VULKAN_SDK=$vulkanPath" >> $env:GITHUB_ENV
- if: ${{ !cancelled() && matrix.preset != 'CPU' && steps.cache-install.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: |
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA
C:\Program Files\AMD\ROCm
C:\VulkanSDK
key: ${{ matrix.install }}
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}\.ccache
key: ccache-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.preset }}-${{ needs.setup-environment.outputs.vendorsha }}
- name: Build Windows dependencies
run: |
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'
$steps = "${{ matrix.build-steps }}".Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)
./scripts/build_windows.ps1 @steps
env:
CMAKE_GENERATOR: Ninja
- name: Log build results
run: |
gci -path .\dist -Recurse -File | ForEach-Object { get-filehash -path $_.FullName -Algorithm SHA256 } | format-list
- if: matrix.preset == 'CPU'
name: Verify Windows CPU payloads
shell: bash
run: |
set -euo pipefail
for payload in \
dist/windows-amd64/lib/ollama/llama-server.exe \
dist/windows-arm64/lib/ollama/llama-server.exe
do
[ -f "$payload" ] || { echo "missing $payload"; exit 1; }
done
- uses: actions/upload-artifact@v4
with:
name: depends-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.preset }}
path: dist\*
compression-level: 0
windows-build:
runs-on: windows
needs: setup-environment
env:
GOFLAGS: ${{ needs.setup-environment.outputs.GOFLAGS }}
VERSION: ${{ needs.setup-environment.outputs.VERSION }}
steps:
- name: Install clang and gcc-compat
run: |
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy Bypass -Scope Process -Force
Invoke-WebRequest -Uri "https://github.com/mstorsjo/llvm-mingw/releases/download/20240619/llvm-mingw-20240619-ucrt-x86_64.zip" -OutFile "${{ runner.temp }}\llvm-mingw-ucrt.zip"
Expand-Archive -Path ${{ runner.temp }}\llvm-mingw-ucrt.zip -DestinationPath "C:\Program Files\"
$installPath=(Resolve-Path -Path "C:\Program Files\llvm-mingw-*-ucrt-x86_64").path
echo "$installPath\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
if (!(Test-Path "$installPath\bin\aarch64-w64-mingw32-gcc.exe")) {
throw "llvm-mingw x86_64 package is missing the aarch64 cross compiler"
}
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
LLAMA_CPP_VERSION
MLX_VERSION
MLX_C_VERSION
- name: Verify gcc is actually clang
run: |
$ErrorActionPreference='Continue'
$version=& gcc -v 2>&1
$version=$version -join "`n"
echo "gcc is $version"
if ($version -notmatch 'clang') {
echo "ERROR: GCC must be clang for proper utf16 handling"
exit 1
}
$ErrorActionPreference='Stop'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Build Windows binaries and app launchers
run: ./scripts/build_windows.ps1 ollama ollamaArm64 app appArm64
- name: Verify Windows build payloads
shell: bash
run: |
set -euo pipefail
for payload in \
dist/windows-amd64/ollama.exe \
dist/windows-arm64/ollama.exe
do
[ -f "$payload" ] || { echo "missing $payload"; exit 1; }
done
- name: Log build results
run: |
gci -path .\dist -Recurse -File | ForEach-Object { get-filehash -path $_.FullName -Algorithm SHA256 } | format-list
- uses: actions/upload-artifact@v4
with:
name: build-windows-amd64
path: dist\*
compression-level: 0
windows-package:
runs-on: windows
needs: [setup-environment, windows-build, windows-depends]
env:
GOFLAGS: ${{ needs.setup-environment.outputs.GOFLAGS }}
VERSION: ${{ needs.setup-environment.outputs.VERSION }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
LLAMA_CPP_VERSION
MLX_VERSION
MLX_C_VERSION
- uses: actions/download-artifact@v4
with:
pattern: depends-windows*
path: dist
merge-multiple: true
- uses: actions/download-artifact@v4
with:
pattern: build-windows*
path: dist
merge-multiple: true
- name: Copy unsigned install script
run: Copy-Item -Path .\scripts\install.ps1 -Destination .\dist\install.ps1 -ErrorAction Stop
- name: Log dist contents after download
run: gci -path .\dist -recurse
- name: Verify Windows package inputs
shell: bash
run: |
set -euo pipefail
for payload in \
dist/windows-amd64/ollama.exe \
dist/windows-amd64/lib/ollama/llama-server.exe \
dist/windows-arm64/ollama.exe \
dist/windows-arm64/lib/ollama/llama-server.exe
do
[ -f "$payload" ] || { echo "missing $payload"; exit 1; }
done
- name: Build unsigned Windows installer and zips
run: ./scripts/build_windows.ps1 deps installer zip
- name: Log contents after build
run: |
gci -path .\dist -Recurse -File | ForEach-Object { get-filehash -path $_.FullName -Algorithm SHA256 } | format-list
- name: Verify Windows package outputs
shell: bash
run: |
set -euo pipefail
for payload in \
dist/ollama-windows-amd64.zip \
dist/ollama-windows-arm64.zip \
dist/ollama-windows-amd64-rocm.zip \
dist/OllamaSetup.exe \
dist/install.ps1
do
[ -f "$payload" ] || { echo "missing $payload"; exit 1; }
done
- uses: actions/upload-artifact@v4
with:
name: ollama-windows-amd64.zip
path: dist/ollama-windows-amd64.zip
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: ollama-windows-arm64.zip
path: dist/ollama-windows-arm64.zip
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: ollama-windows-amd64-rocm.zip
path: dist/ollama-windows-amd64-rocm.zip
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: OllamaSetup.exe
path: dist/OllamaSetup.exe
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: install.ps1
path: dist/install.ps1
compression-level: 0
+27
View File
@@ -96,6 +96,9 @@ RUN --mount=type=cache,target=/root/.ccache \
[ -e "$lib" ] && cp -a "$lib" dist/lib/ollama/ || true; \
done
FROM scratch AS publish-llama-server-cpu
COPY --from=llama-server-cpu dist/lib/ollama /lib/ollama/
FROM cuda-12-deps AS llama-server-cuda_v12
COPY LLAMA_CPP_VERSION .
COPY llama/server llama/server
@@ -105,6 +108,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --build build/llama-server-cuda_v12 -- -l $(nproc) \
&& cmake --install build/llama-server-cuda_v12 --component llama-server --strip
FROM scratch AS publish-llama-server-cuda_v12
COPY --from=llama-server-cuda_v12 dist/lib/ollama /lib/ollama/
FROM cuda-13-deps AS llama-server-cuda_v13
COPY LLAMA_CPP_VERSION .
COPY llama/server llama/server
@@ -114,6 +120,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --build build/llama-server-cuda_v13 -- -l $(nproc) \
&& cmake --install build/llama-server-cuda_v13 --component llama-server --strip
FROM scratch AS publish-llama-server-cuda_v13
COPY --from=llama-server-cuda_v13 dist/lib/ollama /lib/ollama/
FROM rocm-7-deps AS llama-server-rocm_v7_2
ENV CC=clang CXX=clang++
COPY LLAMA_CPP_VERSION .
@@ -125,6 +134,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --install build/llama-server-rocm_v7_2 --component llama-server --strip
RUN rm -f dist/lib/ollama/rocm_v7_2/rocblas/library/*gfx90[06]*
FROM scratch AS publish-llama-server-rocm_v7_2
COPY --from=llama-server-rocm_v7_2 dist/lib/ollama /lib/ollama/
FROM vulkan-deps AS llama-server-vulkan
COPY LLAMA_CPP_VERSION .
COPY llama/server llama/server
@@ -134,6 +146,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --build build/llama-server-vulkan -- -l $(nproc) \
&& cmake --install build/llama-server-vulkan --component llama-server --strip
FROM scratch AS publish-llama-server-vulkan
COPY --from=llama-server-vulkan dist/lib/ollama /lib/ollama/
#
# JetPack stages — self-contained with their own base images
#
@@ -155,6 +170,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --build build/llama-server-cuda_jetpack5 -- -l $(nproc) \
&& cmake --install build/llama-server-cuda_jetpack5 --component llama-server --strip
FROM scratch AS publish-llama-server-cuda_jetpack5
COPY --from=jetpack-5 dist/lib/ollama /lib/ollama/
FROM --platform=linux/arm64 nvcr.io/nvidia/l4t-jetpack:${JETPACK6VERSION} AS jetpack-6
ARG CMAKEVERSION
ARG NINJAVERSION
@@ -172,6 +190,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake --build build/llama-server-cuda_jetpack6 -- -l $(nproc) \
&& cmake --install build/llama-server-cuda_jetpack6 --component llama-server --strip
FROM scratch AS publish-llama-server-cuda_jetpack6
COPY --from=jetpack-6 dist/lib/ollama /lib/ollama/
#
# MLX stage
#
@@ -210,6 +231,9 @@ RUN --mount=type=cache,target=/root/.ccache \
&& cmake -S . -B build/mlx_cuda_v13 -DOLLAMA_MLX_BACKENDS=cuda_v13 -DBLAS_INCLUDE_DIRS=/usr/include/openblas -DLAPACK_INCLUDE_DIRS=/usr/include/openblas -DCMAKE_CUDA_FLAGS="-t ${OLLAMA_MLX_NVCC_THREADS}" ${MLX_CUDA_RAM_MB:+-DMLX_CUDA_RAM_MB=${MLX_CUDA_RAM_MB}} -DOLLAMA_PAYLOAD_INSTALL_PREFIX=/go/src/github.com/ollama/ollama/dist \
&& cmake --build build/mlx_cuda_v13 --target ollama-mlx-cuda_v13 -- -l $(nproc) ${OLLAMA_MLX_BUILD_JOBS:+-j ${OLLAMA_MLX_BUILD_JOBS}}
FROM scratch AS publish-mlx
COPY --from=mlx /go/src/github.com/ollama/ollama/dist/lib/ollama /lib/ollama/
#
# Go build
#
@@ -230,6 +254,9 @@ ENV CGO_CXXFLAGS="${CGO_CXXFLAGS}"
RUN --mount=type=cache,target=/root/.cache/go-build \
go build -trimpath -buildmode=pie -o /bin/ollama .
FROM scratch AS publish-go
COPY --from=build /bin/ollama /bin/ollama
#
# Assembly stages — combine llama-server variants + GPU runtime libs
#
+1 -1
View File
@@ -1 +1 @@
b9409
b9452
+1
View File
@@ -575,6 +575,7 @@ if(OLLAMA_HAVE_LLAMA_SERVER)
ollama_append_cache_arg_if_set(_rocm_args AMDGPU_TARGETS)
ollama_append_cache_arg_if_set(_rocm_args CMAKE_HIP_ARCHITECTURES)
ollama_append_cache_arg_if_set(_rocm_args CMAKE_HIP_FLAGS)
ollama_append_cache_arg_if_set(_rocm_args GGML_CUDA_NO_PEER_COPY)
ollama_append_cache_arg_if_set(_rocm_args CMAKE_PREFIX_PATH)
ollama_add_llama_server_build(${_backend}
PRESET ${_rocm_preset}
+1 -1
View File
@@ -388,7 +388,7 @@ func filterIntegratedGPUs(devices []ml.DeviceInfo) []ml.DeviceInfo {
continue
}
slog.Info("dropping integrated GPU",
slog.Info("dropping integrated GPU; to enable, set OLLAMA_IGPU_ENABLE=1",
"id", device.ID,
"library", device.Library,
"compute", device.Compute(),
+3
View File
@@ -96,6 +96,9 @@ if(GGML_HIP AND OLLAMA_RUNNER_DIR MATCHES "^rocm_v")
ollama_set_cache_default(CMAKE_HIP_FLAGS STRING
"-parallel-jobs=4" "HIP compiler flags")
if(WIN32)
# Windows ROCm split-load needs peer copies disabled for correctness.
ollama_set_cache_default(GGML_CUDA_NO_PEER_COPY BOOL ON
"Disable direct peer device copies")
# HIP on Windows currently emits attributes and deprecated pragma
# warnings from ROCm headers. Keep the workaround local to Windows ROCm
# so it can be removed when the ROCm toolchain no longer needs it.
+49 -9
View File
@@ -141,9 +141,10 @@ type llamaServerRunner struct {
// used to map DeviceIDs to device names for VRAMByGPU lookups.
gpus []ml.DeviceInfo
ggml *ggml.GGML
totalLayers uint64 // maximum offloadable model layers
loadStart time.Time
ggml *ggml.GGML
totalLayers uint64 // maximum offloadable model layers
loadStart time.Time
rawEmbeddings bool
sem *semaphore.Weighted
@@ -559,20 +560,32 @@ func appendBatchArgs(params []string, opts api.Options, embedding bool, numParal
return params
}
func appendFlashAttentionArgs(params []string, gpus []ml.DeviceInfo) []string {
// LlamaServerFlashAttention resolves the flash-attention mode passed to llama-server.
func LlamaServerFlashAttention(gpus []ml.DeviceInfo) ml.FlashAttentionType {
enabled := envconfig.FlashAttention(false)
userSet := enabled == envconfig.FlashAttention(true)
if userSet {
if enabled {
return append(params, "--flash-attn", "on")
return ml.FlashAttentionEnabled
}
return append(params, "--flash-attn", "off")
return ml.FlashAttentionDisabled
}
if !ml.FlashAttentionSupported(gpus) {
return append(params, "--flash-attn", "off")
return ml.FlashAttentionDisabled
}
return ml.FlashAttentionAuto
}
func appendFlashAttentionArgs(params []string, gpus []ml.DeviceInfo) []string {
switch LlamaServerFlashAttention(gpus) {
case ml.FlashAttentionEnabled:
return append(params, "--flash-attn", "on")
case ml.FlashAttentionDisabled:
return append(params, "--flash-attn", "off")
default:
return append(params, "--flash-attn", "auto")
}
return append(params, "--flash-attn", "auto")
}
func appendMainGPUArgs(params []string, opts api.Options) []string {
@@ -778,6 +791,7 @@ func NewLlamaServerRunner(
gpus: gpus,
ggml: f,
totalLayers: f.KV().BlockCount() + 1,
rawEmbeddings: legacyEmbeddingsWereRaw(f.KV()),
sem: semaphore.NewWeighted(int64(numParallel)),
launch: launch,
output: memWriter,
@@ -801,6 +815,28 @@ func cloneStringMap(src map[string]string) map[string]string {
return dst
}
func legacyEmbeddingsWereRaw(kv ggml.KV) bool {
arch := kv.Architecture()
if _, ok := kv[fmt.Sprintf("%s.pooling_type", arch)]; !ok {
return false
}
// Legacy /api/embeddings returned runner output, so preserve only old raw embed paths.
switch arch {
case "bert":
if kv.String("tokenizer.ggml.model", "bert") != "bert" {
return true
}
return !kv.Bool("normalize_embeddings", true)
case "nomic-bert", "nomic-bert-moe":
return !kv.Bool("normalize_embeddings", false)
case "gemma3", "gemma-embedding", "qwen3":
return false
default:
return false
}
}
func (s *llamaServerRunner) startProcess() error {
cmd, port, err := startLlamaServer(s.launch, s.output)
if err != nil {
@@ -2034,7 +2070,11 @@ func (s *llamaServerRunner) Embedding(ctx context.Context, input string) ([]floa
// Use "input" field (not "content") to get the OAI-compatible response format
// which includes tokens_evaluated for prompt token counting
data, err := json.Marshal(map[string]string{"input": input})
req := map[string]any{"input": input}
if s.rawEmbeddings {
req["embd_normalize"] = -1
}
data, err := json.Marshal(req)
if err != nil {
return nil, 0, fmt.Errorf("error marshaling embed data: %w", err)
}
+41
View File
@@ -1298,6 +1298,47 @@ func TestLlamaServerEmbedding(t *testing.T) {
}
}
func TestLegacyEmbeddingsWereRaw(t *testing.T) {
tests := []struct {
name string
kv ggml.KV
want bool
}{
{
name: "bert t5 raw like bge-m3",
kv: ggml.KV{
"general.architecture": "bert",
"bert.pooling_type": uint32(1),
"tokenizer.ggml.model": "t5",
},
want: true,
},
{
name: "nomic bert default raw",
kv: ggml.KV{
"general.architecture": "nomic-bert",
"nomic-bert.pooling_type": uint32(1),
},
want: true,
},
{
name: "qwen3 remains normalized",
kv: ggml.KV{
"general.architecture": "qwen3",
"qwen3.pooling_type": uint32(1),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := legacyEmbeddingsWereRaw(tt.kv); got != tt.want {
t.Fatalf("legacyEmbeddingsWereRaw() = %v, want %v", got, tt.want)
}
})
}
}
func TestLlamaServerEmbeddingFallbackFormat(t *testing.T) {
// Fallback: non-OAI array format (from "content" field)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+80
View File
@@ -8,6 +8,7 @@ import (
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
@@ -42,6 +43,11 @@ type windowsFileVersion struct {
ls uint32
}
type windowsVersionTranslation struct {
language uint16
codePage uint16
}
func WindowsROCmRuntimeDLLPath(libDirs []string) (string, error) {
choice, err := windowsROCmRuntimeDLLChoice(libDirs)
if err != nil {
@@ -255,6 +261,10 @@ func readWindowsFileVersion(path string) (windowsFileVersion, bool) {
return windowsFileVersion{}, false
}
if version, ok := readWindowsStringFileVersion(versionInfo); ok {
return version, true
}
var fixedInfo *windows.VS_FIXEDFILEINFO
var fixedInfoLen uint32
if err := windows.VerQueryValue(unsafe.Pointer(&versionInfo[0]), `\`, unsafe.Pointer(&fixedInfo), &fixedInfoLen); err != nil {
@@ -266,6 +276,76 @@ func readWindowsFileVersion(path string) (windowsFileVersion, bool) {
return windowsFileVersion{ms: fixedInfo.FileVersionMS, ls: fixedInfo.FileVersionLS}, true
}
func readWindowsStringFileVersion(versionInfo []byte) (windowsFileVersion, bool) {
translations := windowsVersionTranslations(versionInfo)
if len(translations) == 0 {
translations = []windowsVersionTranslation{{language: 0x0409, codePage: 0x04b0}}
}
for _, translation := range translations {
for _, name := range []string{"FileVersion", "ProductVersion"} {
value, ok := windowsVersionString(versionInfo, translation, name)
if !ok {
continue
}
version, ok := parseWindowsFileVersionString(value)
if ok {
return version, true
}
}
}
return windowsFileVersion{}, false
}
func windowsVersionTranslations(versionInfo []byte) []windowsVersionTranslation {
var translations *windowsVersionTranslation
var translationsLen uint32
if err := windows.VerQueryValue(unsafe.Pointer(&versionInfo[0]), `\VarFileInfo\Translation`, unsafe.Pointer(&translations), &translationsLen); err != nil {
return nil
}
if translations == nil || translationsLen < uint32(unsafe.Sizeof(windowsVersionTranslation{})) {
return nil
}
return unsafe.Slice(translations, int(translationsLen)/int(unsafe.Sizeof(windowsVersionTranslation{})))
}
func windowsVersionString(versionInfo []byte, translation windowsVersionTranslation, name string) (string, bool) {
subBlock := fmt.Sprintf(`\StringFileInfo\%04x%04x\%s`, translation.language, translation.codePage, name)
var value *uint16
var valueLen uint32
if err := windows.VerQueryValue(unsafe.Pointer(&versionInfo[0]), subBlock, unsafe.Pointer(&value), &valueLen); err != nil {
return "", false
}
if value == nil || valueLen == 0 {
return "", false
}
return windows.UTF16PtrToString(value), true
}
func parseWindowsFileVersionString(s string) (windowsFileVersion, bool) {
parts := strings.FieldsFunc(s, func(r rune) bool {
return r < '0' || r > '9'
})
if len(parts) < 2 {
return windowsFileVersion{}, false
}
var version [4]uint16
for i := 0; i < len(version) && i < len(parts); i++ {
part, err := strconv.ParseUint(parts[i], 10, 16)
if err != nil {
return windowsFileVersion{}, false
}
version[i] = uint16(part)
}
return windowsFileVersion{
ms: uint32(version[0])<<16 | uint32(version[1]),
ls: uint32(version[2])<<16 | uint32(version[3]),
}, true
}
func (v windowsFileVersion) Compare(other windowsFileVersion) int {
if v.ms < other.ms {
return -1
+20 -6
View File
@@ -21,7 +21,7 @@ set -e
status() { echo >&2 ">>> $@"; }
usage() {
echo "usage: $(basename $0) [build app [sign]]"
echo "usage: $(basename $0) [build package app sign]"
exit 1
}
@@ -130,7 +130,7 @@ _merge_darwin_payload() {
done
}
_sign_darwin() {
_prepare_darwin_runtime() {
status "Creating universal binary..."
mkdir -p dist/darwin
lipo -create -output dist/darwin/ollama dist/darwin-amd64/ollama dist/darwin-arm64/ollama
@@ -146,7 +146,23 @@ _sign_darwin() {
lipo dist/darwin/llama-quantize -verify_arch x86_64 arm64
_merge_darwin_payload
}
_create_darwin_runtime_tarball() {
status "Creating universal tarball..."
rm -f dist/ollama-darwin.tar dist/ollama-darwin.tgz
tar -cf dist/ollama-darwin.tar --strip-components 2 dist/darwin/ollama dist/darwin/llama-server dist/darwin/llama-quantize
tar -rf dist/ollama-darwin.tar --strip-components 4 dist/darwin/lib/ollama
gzip -9vc <dist/ollama-darwin.tar >dist/ollama-darwin.tgz
}
_package_darwin_runtime() {
_prepare_darwin_runtime
_create_darwin_runtime_tarball
}
_sign_darwin() {
_prepare_darwin_runtime
if [ -n "$APPLE_IDENTITY" ]; then
for F in dist/darwin/ollama dist/darwin/llama-server dist/darwin/llama-quantize dist/darwin/lib/ollama/* dist/darwin/lib/ollama/mlx_metal_v*/*; do
[ -f "$F" ] && [ ! -L "$F" ] || continue
@@ -160,10 +176,7 @@ _sign_darwin() {
rm -f "$TEMP"
fi
status "Creating universal tarball..."
tar -cf dist/ollama-darwin.tar --strip-components 2 dist/darwin/ollama dist/darwin/llama-server dist/darwin/llama-quantize
tar -rf dist/ollama-darwin.tar --strip-components 4 dist/darwin/lib/ollama
gzip -9vc <dist/ollama-darwin.tar >dist/ollama-darwin.tgz
_create_darwin_runtime_tarball
}
_build_macapp() {
@@ -282,6 +295,7 @@ fi
for CMD in "$@"; do
case $CMD in
build) _build_darwin ;;
package) _package_darwin_runtime ;;
sign) _sign_darwin ;;
app) _build_macapp ;;
*) usage ;;
+59 -13
View File
@@ -143,10 +143,20 @@ func resolveContextShift(shift *bool, numCtx int) bool {
}
func effectiveModelContext(numCtx int, f *ggml.GGML) int {
if f != nil {
if trainCtx := int(f.KV().ContextLength()); trainCtx > 0 && numCtx > trainCtx {
return trainCtx
}
return effectiveContext(numCtx, modelTrainContext(f))
}
func modelTrainContext(f *ggml.GGML) int {
if f == nil {
return 0
}
return int(f.KV().ContextLength())
}
func effectiveContext(numCtx, trainCtx int) int {
if trainCtx > 0 && numCtx > trainCtx {
return trainCtx
}
return numCtx
@@ -522,7 +532,8 @@ func (s *Scheduler) load(req *LlmRequest, systemInfo ml.SystemInfo, gpus []ml.De
predicted := llm.PredictServerVRAM(req.model.ModelPath, f, predictedCtx)
loadGpus, launchOpts = selectLlamaServerPlacement(systemInfo, gpus, predicted, req.opts)
availableForBatch, _, _ := availableMemoryForPlacement(systemInfo, loadGpus, launchOpts)
req.applyAutomaticGenerationBatch(completion, predictedCtx, predicted, availableForBatch)
flashAttention := llm.LlamaServerFlashAttention(loadGpus)
req.applyAutomaticGenerationBatch(completion, predictedCtx, predicted, availableForBatch, flashAttention, loadGpus)
launchOpts.NumBatch = req.opts.NumBatch
predictedForLoad := predicted + generationBatchSurchargeForCompletion(completion, launchOpts.NumBatch)
@@ -676,8 +687,10 @@ iGPUScan:
}
totalSize, vramSize := llama.MemorySize()
if effectiveNumCtx := llama.ContextLength(); req.numCtxAuto && effectiveNumCtx > 0 {
trainContext := modelTrainContext(f)
if effectiveNumCtx := llama.ContextLength(); req.model.ModelPath != "" && effectiveNumCtx > 0 {
req.opts.NumCtx = effectiveNumCtx
req.contextShift = resolveContextShift(req.shift, effectiveNumCtx)
}
runner := &runnerRef{
model: req.model,
@@ -697,6 +710,7 @@ iGPUScan:
numBatchAuto: req.numBatchAuto,
useMMapAuto: req.useMMapAuto,
contextShift: req.contextShift,
trainContext: trainContext,
}
runner.numParallel = numParallel
runner.refMu.Lock() // hold lock until running or aborted
@@ -763,7 +777,7 @@ func (req *LlmRequest) reduceAutoNumCtxForLoadOOM(f *ggml.GGML, numParallel int,
predictedCtx := effectiveLlamaServerContext(req.opts.NumCtx, f, numParallel)
predictedVRAM := llm.PredictServerVRAM(req.model.ModelPath, f, predictedCtx)
available, _, _ := availableMemoryForPlacement(systemInfo, gpus, launchOpts)
req.applyAutomaticGenerationBatch(completion, predictedCtx, predictedVRAM, available)
req.applyAutomaticGenerationBatch(completion, predictedCtx, predictedVRAM, available, llm.LlamaServerFlashAttention(gpus), gpus)
newNumBatch = req.opts.NumBatch
return oldNumCtx, effectiveNumCtx, newNumCtx, oldNumBatch, newNumBatch, true
}
@@ -781,20 +795,21 @@ func effectiveLlamaServerContext(numCtx int, f *ggml.GGML, numParallel int) int
}
const (
llamaServerGenerationBatchDefault = 512
llamaServerGenerationBatchMedium = 1024
llamaServerGenerationBatchLarge = 2048
llamaServerGenerationBatchDefault = 512
llamaServerGenerationBatchConstrained = 256
llamaServerGenerationBatchMedium = 1024
llamaServerGenerationBatchLarge = 2048
llamaServerGenerationBatchMediumHeadroomPercent = 75
llamaServerGenerationBatchLargeHeadroomPercent = 60
)
func (req *LlmRequest) applyAutomaticGenerationBatch(completion bool, effectiveCtx int, predictedVRAM, availableMemory uint64) {
func (req *LlmRequest) applyAutomaticGenerationBatch(completion bool, effectiveCtx int, predictedVRAM, availableMemory uint64, flashAttention ml.FlashAttentionType, gpus []ml.DeviceInfo) {
if !completion || !req.numBatchAuto {
return
}
req.opts.NumBatch = automaticGenerationBatch(effectiveCtx, predictedVRAM, availableMemory)
req.opts.NumBatch = automaticGenerationBatch(effectiveCtx, predictedVRAM, availableMemory, flashAttention, gpus)
}
func generationBatchSurchargeForCompletion(completion bool, batch int) uint64 {
@@ -804,7 +819,14 @@ func generationBatchSurchargeForCompletion(completion bool, batch int) uint64 {
return generationBatchSurcharge(batch)
}
func automaticGenerationBatch(effectiveCtx int, predictedVRAM, availableMemory uint64) int {
func automaticGenerationBatch(effectiveCtx int, predictedVRAM, availableMemory uint64, flashAttention ml.FlashAttentionType, gpus []ml.DeviceInfo) int {
if flashAttention == ml.FlashAttentionDisabled && hasCUDADevice(gpus) {
if constrainedCUDAWithoutFlashAttention(effectiveCtx, gpus) {
return llamaServerGenerationBatchConstrained
}
return llamaServerGenerationBatchDefault
}
batch := generationBatchForContext(effectiveCtx)
for batch > llamaServerGenerationBatchDefault && !generationBatchFits(batch, predictedVRAM, availableMemory) {
batch = nextLowerGenerationBatch(batch)
@@ -812,6 +834,28 @@ func automaticGenerationBatch(effectiveCtx int, predictedVRAM, availableMemory u
return batch
}
func hasCUDADevice(gpus []ml.DeviceInfo) bool {
return slices.ContainsFunc(gpus, func(gpu ml.DeviceInfo) bool {
return gpu.Library == "CUDA"
})
}
func constrainedCUDAWithoutFlashAttention(effectiveCtx int, gpus []ml.DeviceInfo) bool {
if effectiveCtx <= 4096 {
return false
}
return slices.ContainsFunc(gpus, func(gpu ml.DeviceInfo) bool {
if gpu.Library != "CUDA" {
return false
}
memory := gpu.FreeMemory
if memory == 0 || (gpu.TotalMemory > 0 && gpu.TotalMemory < memory) {
memory = gpu.TotalMemory
}
return memory > 0 && memory <= 8*format.GibiByte
})
}
func generationBatchForContext(effectiveCtx int) int {
switch {
case effectiveCtx > 32768:
@@ -1316,6 +1360,7 @@ type runnerRef struct {
numBatchAuto bool
useMMapAuto bool
contextShift bool
trainContext int
*api.Options
}
@@ -1357,6 +1402,7 @@ func (runner *runnerRef) needsReload(ctx context.Context, req *LlmRequest) bool
// Don't reload runner if num_gpu=-1 was provided
optsExisting := runner.Options.Runner
optsNew := req.opts.Runner
optsNew.NumCtx = effectiveContext(optsNew.NumCtx, runner.trainContext)
if runner.numCtxAuto && req.numCtxAuto {
optsNew.NumCtx = optsExisting.NumCtx
}
+57 -3
View File
@@ -151,7 +151,7 @@ func TestSchedLoadStoresEffectiveContextLength(t *testing.T) {
}
}
func TestSchedLoadPreservesExplicitContextLength(t *testing.T) {
func TestSchedLoadStoresEffectiveExplicitContextLength(t *testing.T) {
ctx, done := context.WithTimeout(t.Context(), 500*time.Millisecond)
defer done()
@@ -167,7 +167,7 @@ func TestSchedLoadPreservesExplicitContextLength(t *testing.T) {
case err := <-scenario.req.errCh:
require.NoError(t, err)
case runner := <-scenario.req.successCh:
require.Equal(t, 262144, runner.Options.NumCtx)
require.Equal(t, 131072, runner.Options.NumCtx)
}
}
@@ -981,6 +981,34 @@ func TestSchedNeedsReloadUsesEffectiveAutomaticContextShift(t *testing.T) {
require.True(t, runner.needsReload(ctx, req))
}
func TestSchedNeedsReloadUsesEffectiveExplicitContext(t *testing.T) {
ctx, done := context.WithTimeout(t.Context(), 100*time.Millisecond)
defer done()
llm := &mockLlm{vramByGPU: map[ml.DeviceID]uint64{}}
opts := api.DefaultOptions()
opts.NumCtx = 2048
model := &Model{ModelPath: "model.gguf"}
runner := &runnerRef{
model: model,
Options: &opts,
llama: llm,
numParallel: 1,
contextShift: true,
trainContext: 2048,
}
req := &LlmRequest{
model: model,
opts: api.DefaultOptions(),
}
req.opts.NumCtx = 262144
require.False(t, runner.needsReload(ctx, req))
req.opts.NumCtx = 1024
require.True(t, runner.needsReload(ctx, req))
}
func TestSchedNeedsReloadIgnoresAutomaticNumBatchDerivation(t *testing.T) {
ctx, done := context.WithTimeout(t.Context(), 100*time.Millisecond)
defer done()
@@ -1050,16 +1078,20 @@ func TestAutomaticGenerationBatch(t *testing.T) {
effectiveCtx int
predicted uint64
available uint64
flash ml.FlashAttentionType
gpus []ml.DeviceInfo
want int
}{
{
name: "small context keeps default",
effectiveCtx: 4096,
flash: ml.FlashAttentionAuto,
want: 512,
},
{
name: "medium context uses 1024 with unknown memory",
effectiveCtx: 32768,
flash: ml.FlashAttentionAuto,
want: 1024,
},
{
@@ -1067,6 +1099,7 @@ func TestAutomaticGenerationBatch(t *testing.T) {
effectiveCtx: 131072,
predicted: 8 * format.GibiByte,
available: 14 * format.GibiByte,
flash: ml.FlashAttentionAuto,
want: 2048,
},
{
@@ -1074,6 +1107,7 @@ func TestAutomaticGenerationBatch(t *testing.T) {
effectiveCtx: 131072,
predicted: 9 * format.GibiByte,
available: 14 * format.GibiByte,
flash: ml.FlashAttentionAuto,
want: 1024,
},
{
@@ -1081,6 +1115,7 @@ func TestAutomaticGenerationBatch(t *testing.T) {
effectiveCtx: 131072,
predicted: 8 * format.GibiByte,
available: 11 * format.GibiByte,
flash: ml.FlashAttentionAuto,
want: 1024,
},
{
@@ -1088,13 +1123,32 @@ func TestAutomaticGenerationBatch(t *testing.T) {
effectiveCtx: 32768,
predicted: 8500 * format.MebiByte,
available: 11 * format.GibiByte,
flash: ml.FlashAttentionAuto,
want: 512,
},
{
name: "flash attention disabled suppresses promotion",
effectiveCtx: 131072,
predicted: 8 * format.GibiByte,
available: 14 * format.GibiByte,
flash: ml.FlashAttentionDisabled,
gpus: []ml.DeviceInfo{{DeviceID: ml.DeviceID{Library: "CUDA"}, FreeMemory: 14 * format.GibiByte}},
want: 512,
},
{
name: "constrained CUDA without flash attention uses smaller batch",
effectiveCtx: 131072,
predicted: 3 * format.GibiByte,
available: 6 * format.GibiByte,
flash: ml.FlashAttentionDisabled,
gpus: []ml.DeviceInfo{{DeviceID: ml.DeviceID{Library: "CUDA"}, FreeMemory: 6 * format.GibiByte}},
want: 256,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, automaticGenerationBatch(tt.effectiveCtx, tt.predicted, tt.available))
require.Equal(t, tt.want, automaticGenerationBatch(tt.effectiveCtx, tt.predicted, tt.available, tt.flash, tt.gpus))
})
}
}
+3 -4
View File
@@ -74,13 +74,12 @@ func main() {
return
}
// Check if MLX initialized successfully
if !mlx.IsMLXAvailable() {
log.Fatalf("MLX initialization failed: %v", mlx.GetMLXInitError())
if err := mlx.InitMLX(); err != nil {
log.Fatalf("MLX initialization failed: %v", err)
}
// Restore strict error handling now that we know MLX is working.
// During init(), a safe handler prevented exit(-1) on GPU errors.
// During InitMLX(), a safe handler prevented exit(-1) on GPU errors.
mlx.RestoreDefaultErrorHandler()
// CPU profiling
+18 -121
View File
@@ -47,14 +47,14 @@ import "C"
import (
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"sync"
"sync/atomic"
"time"
"unsafe"
mlxrunnermlx "github.com/ollama/ollama/x/mlxrunner/mlx"
)
// Dtype represents MLX data types
@@ -1707,99 +1707,6 @@ var (
mlxInitError error
)
// mlxLibName returns the platform-specific shared library filename.
func mlxLibName() string {
switch runtime.GOOS {
case "windows":
return "mlxc.dll"
case "darwin":
return "libmlxc.dylib"
default:
return "libmlxc.so"
}
}
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 {
libName := mlxLibName()
// 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) {
if candidate := findMLXLibraryInDir(dir, libName); candidate != "" {
return candidate
}
}
}
// 2. Executable directory and lib/ollama/mlx* subdirs
if exe, err := os.Executable(); err == nil {
if eval, err := filepath.EvalSymlinks(exe); err == nil {
exe = eval
}
exeDir := filepath.Dir(exe)
// Check exe dir directly (macOS copies dylib here)
if candidate := findMLXLibraryInDir(exeDir, libName); candidate != "" {
return candidate
}
// Check exe_dir/lib/ollama/mlx* subdirectories
// and exe_dir/../lib/ollama/mlx* (standard bin/lib sibling layout)
for _, libOllamaDir := range []string{
filepath.Join(exeDir, "lib", "ollama"),
filepath.Join(exeDir, "..", "lib", "ollama"),
} {
if candidate := findMLXLibraryInDir(libOllamaDir, libName); candidate != "" {
return candidate
}
}
}
// 3. Build directory (for tests run from repo root)
if cwd, err := os.Getwd(); err == nil {
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
}
}
}
return ""
}
// InitMLX initializes the MLX library by dynamically loading libmlxc.
// This must be called before using any MLX functions.
// Returns an error if the library cannot be loaded.
@@ -1808,10 +1715,9 @@ func InitMLX() error {
return mlxInitError
}
// Search for the library using Go path discovery
libPath := findMLXLibrary()
if libPath == "" {
mlxInitError = fmt.Errorf("failed to initialize MLX: %s not found", mlxLibName())
libPath, err := mlxrunnermlx.LoadedLibraryPath()
if err != nil {
mlxInitError = fmt.Errorf("failed to initialize MLX: %w", err)
return mlxInitError
}
@@ -1832,27 +1738,6 @@ func InitMLX() error {
mlxInitialized = true
mlxInitError = nil
return nil
}
// IsMLXAvailable returns whether MLX was successfully initialized
func IsMLXAvailable() bool {
return mlxInitialized && mlxInitError == nil
}
// GetMLXInitError returns any error that occurred during MLX initialization
func GetMLXInitError() error {
return mlxInitError
}
func init() {
// Initialize MLX dynamic library first
if err := InitMLX(); err != nil {
// Don't panic in init - let the caller handle the error
// Store the error for later retrieval
mlxInitError = err
return
}
// Enter safe mode: replace the default exit(-1) error handler with one
// that logs and stores errors. This prevents a GPU init failure from
@@ -1870,8 +1755,20 @@ func init() {
msg := C.GoString(C.mlx_get_init_error())
mlxInitError = fmt.Errorf("MLX GPU init failed: %s", msg)
mlxInitialized = false
return
return mlxInitError
}
return nil
}
// IsMLXAvailable returns whether MLX was successfully initialized
func IsMLXAvailable() bool {
return mlxInitialized && mlxInitError == nil
}
// GetMLXInitError returns any error that occurred during MLX initialization
func GetMLXInitError() error {
return mlxInitError
}
// RestoreDefaultErrorHandler restores the default MLX error handler (exit on error).
+11
View File
@@ -35,6 +35,17 @@ func CheckInit() error {
return initError
}
// LoadedLibraryPath returns the MLX dynamic library path selected by this package.
func LoadedLibraryPath() (string, error) {
if initLoadedPath != "" {
return initLoadedPath, nil
}
if initError != nil {
return "", initError
}
return "", fmt.Errorf("MLX dynamic library not loaded")
}
// tryLoadFromDir searches a directory for the mlxc shared library and loads it.
func tryLoadFromDir(dir string) bool {
// On Windows, MSVC produces mlxc.dll (no lib prefix)