Compare commits

..
Author SHA1 Message Date
Xuan Son Nguyen aeae9626e7 Merge branch 'master' into xsn/common_json 2026-08-22 15:55:07 +02:00
Xuan Son Nguyen ff0b1959a4 rm redundant decl 2026-08-22 15:22:57 +02:00
Xuan Son Nguyen 3c7135fc4d add some comments 2026-08-22 15:20:35 +02:00
Xuan Son Nguyen 527b42a575 rm json-shim 2026-08-22 10:35:31 +02:00
Xuan Son Nguyen ad32502d14 clean up 2026-08-22 10:28:04 +02:00
Xuan Son Nguyen b89d589040 harden a bit 2026-08-22 10:08:39 +02:00
Xuan Son Nguyen cdcd63bc03 fix ci 2026-08-22 02:04:35 +02:00
Xuan Son Nguyen e8bdf2cb52 various fixes 2026-08-22 01:37:12 +02:00
Xuan Son Nguyen b99247112e fix server crash 2026-08-22 01:07:12 +02:00
Xuan Son Nguyen 34861194be revert redundant changes 2026-08-22 00:48:33 +02:00
Xuan Son Nguyen 6609378e52 wip 2 2026-08-22 00:06:53 +02:00
Xuan Son Nguyen 0c2f1190f4 wip 2026-08-21 23:11:29 +02:00
Xuan Son Nguyen 173d7e804e revert some excessive changes 2026-08-21 22:56:42 +02:00
Xuan Son Nguyen 03eb25f393 wip 2026-08-21 22:43:39 +02:00
Xuan Son Nguyen 039426a696 migrate tests 2026-08-21 22:29:03 +02:00
Xuan Son Nguyen ea4b4b2862 big wip 2026-08-21 22:22:14 +02:00
Xuan Son Nguyen d1a9e5a8e6 migrate server 2026-08-21 21:05:03 +02:00
Xuan Son Nguyen f262180084 adapt jinja 2026-08-21 18:51:09 +02:00
Xuan Son Nguyen 7a3360f6d8 migrate common 2026-08-21 18:04:08 +02:00
Xuan Son Nguyen 76364591e1 add common/json 2026-08-21 18:03:55 +02:00
947 changed files with 27964 additions and 90448 deletions
+6 -6
View File
@@ -31,7 +31,7 @@
]
&& blas.meta.available,
useCuda ? config.cudaSupport,
useMetalKit ? stdenv.hostPlatform.isAarch64 && stdenv.hostPlatform.isDarwin,
useMetalKit ? stdenv.isAarch64 && stdenv.isDarwin,
# Increases the runtime closure size by ~700M
useMpi ? false,
useRocm ? config.rocmSupport,
@@ -92,7 +92,7 @@ let
cudaBuildInputs = with cudaPackages; [
cuda_cudart
cccl # <nv/target>
cuda_cccl # <nv/target>
libcublas
];
@@ -166,7 +166,7 @@ effectiveStdenv.mkDerivation (finalAttrs: {
# `xcrun` is used find the path of the Metal compiler, which is varible
# and not on $PATH
# see https://github.com/ggml-org/llama.cpp/pull/6118 for discussion
__noChroot = effectiveStdenv.hostPlatform.isDarwin && useMetalKit && precompileMetalShaders;
__noChroot = effectiveStdenv.isDarwin && useMetalKit && precompileMetalShaders;
nativeBuildInputs =
[
@@ -181,10 +181,10 @@ effectiveStdenv.mkDerivation (finalAttrs: {
autoAddDriverRunpath
]
++ optionals (effectiveStdenv.hostPlatform.isGnu && enableStatic) [ glibc.static ]
++ optionals (effectiveStdenv.hostPlatform.isDarwin && useMetalKit && precompileMetalShaders) [ xcrunHost ];
++ optionals (effectiveStdenv.isDarwin && useMetalKit && precompileMetalShaders) [ xcrunHost ];
buildInputs =
optionals effectiveStdenv.hostPlatform.isDarwin darwinBuildInputs
optionals effectiveStdenv.isDarwin darwinBuildInputs
++ optionals useCuda cudaBuildInputs
++ optionals useMpi [ mpi ]
++ optionals useRocm rocmBuildInputs
@@ -245,7 +245,7 @@ effectiveStdenv.mkDerivation (finalAttrs: {
# Configurations that are known to result in build failures. Can be
# overridden by importing Nixpkgs with `allowBroken = true`.
broken = (useMetalKit && !effectiveStdenv.hostPlatform.isDarwin);
broken = (useMetalKit && !effectiveStdenv.isDarwin);
description = "Inference of LLaMA model in pure C/C++${descriptionSuffix}";
homepage = "https://github.com/ggml-org/llama.cpp/";
+6 -9
View File
@@ -1,12 +1,12 @@
ARG OPENVINO_VERSION_MAJOR=2026.3.1
ARG OPENVINO_VERSION_FULL=2026.3.1.22476.56d9685302d
ARG OPENVINO_VERSION_MAJOR=2026.3
ARG OPENVINO_VERSION_FULL=2026.3.0.22451.bd8d6542e3c
ARG UBUNTU_VERSION=24.04
# Intel GPU driver versions. https://github.com/intel/compute-runtime/releases
ARG IGC_VERSION=v2.40.13
ARG IGC_VERSION_FULL=2_2.40.13+22418
ARG COMPUTE_RUNTIME_VERSION=26.31.39395.13
ARG COMPUTE_RUNTIME_VERSION_FULL=26.31.39395.13-0
ARG IGC_VERSION=v2.38.2
ARG IGC_VERSION_FULL=2_2.38.2+22051
ARG COMPUTE_RUNTIME_VERSION=26.27.39122.11
ARG COMPUTE_RUNTIME_VERSION_FULL=26.27.39122.11-0
ARG IGDGMM_VERSION=22.10.0
# Intel NPU driver versions. https://github.com/intel/linux-npu-driver/releases
@@ -90,9 +90,6 @@ RUN bash -c "source ${OpenVINO_DIR}/setupvars.sh && \
cmake -B build/ReleaseOV -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_BUILD_TESTS=OFF \
-DGGML_NATIVE=OFF \
-DGGML_BACKEND_DL=ON \
-DGGML_CPU_ALL_VARIANTS=ON \
-DGGML_OPENVINO=ON && \
cmake --build build/ReleaseOV --parallel "
+1 -1
View File
@@ -1,4 +1,4 @@
blank_issues_enabled: false
blank_issues_enabled: true
contact_links:
- name: Got an idea?
url: https://github.com/ggml-org/llama.cpp/discussions/categories/ideas
-95
View File
@@ -1,95 +0,0 @@
name: "ccache-buckets"
description: "Save/restore latest GitHub Actions ccache matching a key prefix to/from HF buckets"
inputs:
key:
description: "Cache key prefix to match and load"
required: true
folder:
description: "Bucket folder containing ccache files"
required: true
evict-old-files:
description: "Corresponds to the ccache --evict-older-than AGE option, where AGE is the number of seconds or days followed by the 's' or 'd' suffix respectively."
default: ''
save:
description: "Save ccache"
required: false
default: false
type: boolean
hf_bucket:
description: 'Hugging Face buckets path'
required: true
runs:
using: "composite"
steps:
- name: Install Hugging Face Hub CLI
shell: bash
run: |
python3 -m venv .venv-hf
.venv-hf/bin/pip install -U huggingface_hub==1.28.0
- name: Restore ccache from buckets
if: ${{ inputs.save != 'true' }}
shell: bash
run: |
set +e -uo pipefail
source .venv-hf/bin/activate
CCACHE_DIR=$(ccache -k cache_dir)
if [[ -d "$CCACHE_DIR" ]]; then
CACHE_PATH=$(hf buckets list "hf://buckets/${{ inputs.hf_bucket }}/${{ inputs.folder }}" --json | jq -r '[.[] | select(.type == "file") | select(.path | startswith("${{ inputs.folder }}/${{ inputs.key }}") and endswith(".tar.gz"))] | sort_by(.path) | last | .path // ""')
if [[ -n "$CACHE_PATH" ]]; then
echo "Restoring ccache from '$CACHE_PATH'."
hf buckets cp "hf://buckets/${{ inputs.hf_bucket }}/$CACHE_PATH" ccache_bucket.tar.gz
mkdir -p ccache_bucket
if tar -xzf ccache_bucket.tar.gz -C ccache_bucket; then
rm -rf "$CCACHE_DIR"
mv ccache_bucket "$CCACHE_DIR"
ccache -z
fi
rm ccache_bucket.tar.gz
else
echo "No ccache found."
fi
else
echo "'$CCACHE_DIR' not found."
fi
- name: Save ccache to buckets
if: ${{ inputs.save == 'true' }}
shell: bash
run: |
if [[ -n "$HF_TOKEN" ]]; then
set +e -uo pipefail
source .venv-hf/bin/activate
CCACHE_DIR=$(ccache -k cache_dir)
if [[ -d "$CCACHE_DIR" ]]; then
ccache -s
if [[ -n "${{ inputs.evict-old-files }}" ]]; then
ccache --evict-older-than "${{ inputs.evict-old-files }}"
fi
DATESTAMP=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
CACHEFILE="${{ inputs.key }}-$DATESTAMP.tar.gz"
if tar -czf ccache_bucket.tar.gz -C "$CCACHE_DIR" .; then
hf buckets cp ccache_bucket.tar.gz "hf://buckets/${{ inputs.hf_bucket }}/${{ inputs.folder }}/$CACHEFILE"
fi
rm ccache_bucket.tar.gz
else
echo "'$CCACHE_DIR' not found."
fi
fi
- name: Remove old ccache files from buckets
if: ${{ inputs.save == 'true' }}
shell: bash
run: |
if [[ -n "$HF_TOKEN" ]]; then
set +e -uo pipefail
source .venv-hf/bin/activate
CACHE_FILES=$(hf buckets list "hf://buckets/${{ inputs.hf_bucket }}/${{ inputs.folder }}" --json | jq -r '[.[] | select(.type == "file") | select((.uploaded_at | .[:19]+"Z" | fromdateiso8601) < (now - 5 * 60)) | select(.path | startswith("${{ inputs.folder }}/${{ inputs.key }}") and endswith(".tar.gz"))] | sort_by(.path)[:-1] | .[] | [.path // ""] | @tsv')
if [[ -n "$CACHE_FILES" ]]; then
echo "Removing old ccache files..."
while IFS=$'\t' read -r CACHE_PATH; do
hf buckets rm "hf://buckets/${{ inputs.hf_bucket }}/$CACHE_PATH" -y
done <<< "$CACHE_FILES"
fi
fi
+62 -24
View File
@@ -21,30 +21,68 @@ inputs:
runs:
using: "composite"
steps:
- name: Install GitHub CLI if missing
shell: bash
run: |
# e.g. in container jobs, where it is not preinstalled
if ! command -v gh >/dev/null 2>&1; then
echo "GitHub CLI not found, installing..."
if ! command -v curl >/dev/null 2>&1; then
apt-get update >/dev/null 2>&1 || true
apt-get install -y curl >/dev/null 2>&1 || true
fi
mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null
chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
apt-get update >/dev/null 2>&1 || true
apt-get install -y gh || { echo "Failed to install GitHub CLI (gh)" >&2; exit 1; }
fi
command -v gh >/dev/null 2>&1 || { echo "GitHub CLI (gh) is required but could not be installed" >&2; exit 1; }
- name: Clear caches
shell: bash
env:
CLEAR_KEY: ${{ inputs.key }}
CLEAR_OLDER: ${{ inputs.older }}
CLEAR_MIN: ${{ inputs.min }}
CLEAR_DRY_RUN: ${{ inputs.dry-run }}
run: |
bash scripts/ccache-clear.sh \
--key "${{ inputs.key }}" \
--older "${{ inputs.older }}" \
--min "${{ inputs.min }}" \
${{ inputs.dry-run == 'true' && '--dry-run' || '' }}
# Convert a duration (e.g. 90m, 1h, 1d, plain seconds) to seconds
to_seconds() {
local val="$1"
[[ "$val" =~ ^[0-9]+$ ]] && { echo "$val"; return 0; }
local num="${val%?}" unit="${val: -1}" mult
[[ "$num" =~ ^[0-9]+$ ]] || return 1
case "$unit" in
s) mult=1 ;;
m) mult=60 ;;
h) mult=3600 ;;
d) mult=86400 ;;
*) return 1 ;;
esac
echo $((num * mult))
}
[[ "$CLEAR_MIN" =~ ^[0-9]+$ ]] || { echo "Invalid min value: $CLEAR_MIN" >&2; exit 1; }
[[ "$CLEAR_DRY_RUN" =~ ^(true|false)$ ]] || { echo "Invalid dry-run value: $CLEAR_DRY_RUN" >&2; exit 1; }
CACHES=$(gh cache list --key "ccache-$CLEAR_KEY" --json id,key,createdAt --jq '.[] | [.createdAt, .id, .key] | @tsv' 2>/dev/null | LC_ALL=C sort)
if [ -z "$CACHES" ]; then
echo "No caches found with key prefix: $CLEAR_KEY"
exit 0
fi
TOTAL=$(( $(wc -l <<< "$CACHES") ))
echo "Found $TOTAL cache(s) with key prefix: $CLEAR_KEY (oldest first):"
while IFS=$'\t' read -r CREATED ID KEY; do
printf ' %s %s %s\n' "$CREATED" "$ID" "$KEY"
done <<< "$CACHES"
CUTOFF=""
if [ -n "$CLEAR_OLDER" ]; then
OLDER_SECONDS=$(to_seconds "$CLEAR_OLDER") || { echo "Invalid older value: $CLEAR_OLDER (expected e.g. 90m, 1h, 1d)" >&2; exit 1; }
CUTOFF=$(( $(date +%s) - OLDER_SECONDS ))
fi
# Caches are sorted oldest first
DELETED=0
while IFS=$'\t' read -r CREATED ID KEY; do
if [ -n "$CUTOFF" ] && [ "$(date -d "$CREATED" +%s)" -ge "$CUTOFF" ]; then
echo "Rest are not older than $CLEAR_OLDER, stopping"
break
fi
if [ $((TOTAL - DELETED - 1)) -lt "$CLEAR_MIN" ]; then
echo "Keeping at least $CLEAR_MIN cache(s), stopping"
break
fi
if [ "$CLEAR_DRY_RUN" = "true" ]; then
echo "Would delete cache: $ID ($KEY)"
else
echo "Deleting cache: $ID ($KEY)"
gh cache delete "$ID"
fi
DELETED=$((DELETED + 1))
done <<< "$CACHES"
@@ -24,7 +24,7 @@ runs:
write-host "Installing ROCm wheels for multi-arch support"
# Install ROCm wheels for multi-arch support (this may take several minutes)
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{ inputs.version }}"
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{ inputs.version }}"
# Pre-expand the devel tree so it is included in the cache
write-host "Initializing ROCm devel tree"
+1 -1
View File
@@ -110,7 +110,7 @@ jobs:
# cache on: https://github.com/ggerganov/tmp2/actions/runs/26534713799/job/78224189394
#
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@v1.2.21
# with:
# key: android-ubuntu-arm64
# evict-old-files: 1d
+32 -55
View File
@@ -22,8 +22,7 @@ on:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/build-apple.yml',
'ggml/src/ggml-metal/**',
'ggml/src/ggml-rpc/**'
'ggml/src/ggml-metal/**'
]
concurrency:
@@ -47,19 +46,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: apple-arm64
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: apple-arm64
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
@@ -74,25 +65,7 @@ jobs:
-DGGML_RPC=ON \
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.3
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: apple-arm64
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Check for leaks
run: |
cmd=(./build/bin/test-thread-safety -hf ggml-org/gemma-3-270m-qat-GGUF -ngl 99 -p "$(printf 'hello %.0s' {1..128})" -n 16 -c 512 -ub 32 -np 2 -t 2 -lv 1)
leaks -atExit -- "${cmd[@]}"
# Graphics devices are leaked by Metal in Apple code sometimes, so we ignore those leaks
OBJC_DEBUG_MISSING_POOLS=YES "${cmd[@]}" 2>&1 | awk '{ print } index($0, "autoreleased with no pool in place") && !/class [a-zA-Z0-9]+Device autoreleased/ { found = 1 } END { exit found }'
leaks -atExit -- ./build/bin/test-thread-safety -hf ggml-org/gemma-3-270m-qat-GGUF -ngl 99 -p "$(printf 'hello %.0s' {1..128})" -n 16 -c 512 -ub 32 -np 2 -t 2 -lv 1
- name: Test
id: cmake_test
@@ -109,19 +82,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: apple-x64
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: apple-x64
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
@@ -138,18 +103,6 @@ jobs:
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.3
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: apple-x64
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
@@ -210,6 +163,14 @@ jobs:
id: checkout
uses: actions/checkout@v6
# TODO: this likely does not do anything - if yes, remove it
- name: ccache
uses: ggml-org/ccache-action@v1.2.21
with:
key: apple-tvos
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
run: |
@@ -235,6 +196,14 @@ jobs:
id: checkout
uses: actions/checkout@v6
# TODO: this likely does not do anything - if yes, remove it
- name: ccache
uses: ggml-org/ccache-action@v1.2.21
with:
key: apple-visionos
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
run: |
@@ -265,6 +234,14 @@ jobs:
id: checkout
uses: actions/checkout@v6
# TODO: this likely does not do anything - if yes, remove it
- name: ccache
uses: ggml-org/ccache-action@v1.2.21
with:
key: apple-swift
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Download xcframework artifact
uses: actions/download-artifact@v7
with:
+4 -4
View File
@@ -41,8 +41,8 @@ jobs:
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Clone
@@ -69,8 +69,8 @@ jobs:
env:
# Sync versions in build.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Clone
+16 -34
View File
@@ -62,10 +62,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cpu-${{ matrix.os }}
save: false
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build Dependencies
id: build_depends
@@ -90,15 +91,6 @@ jobs:
python3 -m pip install --upgrade pip setuptools
pip3 install ./gguf-py
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: cpu-${{ matrix.os }}
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
@@ -108,18 +100,6 @@ jobs:
-DGGML_RPC=ON
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: cpu-${{ matrix.os }}
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
@@ -137,6 +117,18 @@ jobs:
./bin/llama-convert-llama2c-to-ggml --copy-vocab-from-model ./tok512.bin --llama2c-model stories260K.bin --llama2c-output-model stories260K.gguf
./bin/llama-completion -m stories260K.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
# note: real deletion only on push to master (same condition as the ccache save),
# dry-run otherwise (the token is read-only on PRs from forks)
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: cpu-${{ matrix.os }}
older: 1h
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
windows:
name: windows / ${{ matrix.build }}
runs-on: windows-2025
@@ -164,7 +156,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cpu-windows-2025-${{ matrix.build }}
variant: ccache
@@ -223,13 +215,3 @@ jobs:
# cd build
# $env:LLAMA_SKIP_TESTS_SLOW_ON_EMULATOR = 1
# & $sde -future -- ctest -L main -C Release --verbose --timeout 900
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: cpu-windows-2025-${{ matrix.build }}
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+12 -72
View File
@@ -50,22 +50,14 @@ jobs:
DEBIAN_FRONTEND: noninteractive
run: |
apt update
apt install -y cmake build-essential ninja-build libgomp1 git libssl-dev jq python3 python3-venv python3-pip
apt install -y cmake build-essential ninja-build libgomp1 git libssl-dev
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cuda-ubuntu-24.04-cuda
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: cuda-ubuntu-24.04-cuda
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build with CMake
# TODO: Remove GGML_CUDA_CUB_3DOT2 flag once CCCL 3.2 is bundled within CTK and that CTK version is used in this project
@@ -80,18 +72,6 @@ jobs:
-DGGML_CUDA_CUB_3DOT2=ON
cmake --build build
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: cuda-ubuntu-24.04-cuda
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
hip:
runs-on: ubuntu-22.04
container: rocm/dev-ubuntu-22.04:6.1.2
@@ -105,22 +85,14 @@ jobs:
id: depends
run: |
sudo apt-get update
sudo apt-get install -y build-essential git cmake rocblas-dev hipblas-dev libssl-dev rocwmma-dev jq python3-venv
sudo apt-get install -y build-essential git cmake rocblas-dev hipblas-dev libssl-dev rocwmma-dev
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cuda-ubuntu-22.04-hip
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: cuda-ubuntu-22.04-hip
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build with native CMake HIP support
id: cmake_build
@@ -131,18 +103,6 @@ jobs:
-DGGML_HIP=ON
cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: cuda-ubuntu-22.04-hip
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
musa:
runs-on: ubuntu-22.04
container: mthreads/musa:rc4.3.0-devel-ubuntu22.04-amd64
@@ -156,22 +116,14 @@ jobs:
id: depends
run: |
apt-get update
apt-get install -y build-essential git cmake libssl-dev jq
apt-get install -y build-essential git cmake libssl-dev
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cuda-ubuntu-22.04-musa
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: cuda-ubuntu-22.04-musa
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build with native CMake MUSA support
id: cmake_build
@@ -179,15 +131,3 @@ jobs:
cmake -B build -S . \
-DGGML_MUSA=ON
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: cuda-ubuntu-22.04-musa
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
+2 -2
View File
@@ -47,7 +47,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-windows-2022-${{ matrix.arch }}-cuda-${{ matrix.cuda }}
@@ -152,7 +152,7 @@ jobs:
& "${env:HIP_PATH}\lib\llvm\bin\clang.exe" --version
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
# TODO: this build does not match the build in release.yml, so we use a different cache key
# ideally, the builds should match, similar to the CUDA build above so that we would be able
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
uses: actions/checkout@v6
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@v1.2.16
# with:
# key: msys-windows-2025-x64
# variant: ccache
+1 -11
View File
@@ -44,7 +44,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: opencl-windows-2025-x64
variant: ccache
@@ -80,13 +80,3 @@ jobs:
run: |
cmake -S . -B build -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-llvm.cmake -DCMAKE_PREFIX_PATH="$env:RUNNER_TEMP/opencl-arm64-release" -DGGML_OPENCL=ON -DGGML_OPENCL_USE_ADRENO_KERNELS=ON -DLLAMA_BUILD_BORINGSSL=ON
cmake --build build --config Release -j ${env:NUMBER_OF_PROCESSORS}
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: opencl-windows-2025-x64
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+11 -20
View File
@@ -32,8 +32,6 @@ env:
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
LLAMA_ARG_LOG_TIMESTAMPS: 1
# TODO: fix failing tests on OpenVINO backend
CTEST_EXCLUDE: "test-llama-archs|^test-recurrent-state-|test-backend-ops|test-save-load-state"
jobs:
ubuntu-24-openvino:
@@ -41,8 +39,8 @@ jobs:
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Clone
@@ -80,24 +78,26 @@ jobs:
- name: Test (CPU)
id: cmake_test_cpu
# TODO: fix and re-enable the `test-llama-archs` test below
run: |
cd ${{ github.workspace }}
ctest --test-dir build/ReleaseOV -L main -E "${{ env.CTEST_EXCLUDE }}" --verbose --timeout 3000
ctest --test-dir build/ReleaseOV -L main -E "test-llama-archs|test-recurrent-state-rollback-nemotron-h" --verbose --timeout 2000
- name: Test (GPU)
id: cmake_test_gpu
# TODO: fix and re-enable the `test-llama-archs` test below
run: |
cd ${{ github.workspace }}
export GGML_OPENVINO_DEVICE=GPU
ctest --test-dir build/ReleaseOV -L main -E "${{ env.CTEST_EXCLUDE }}" --verbose --timeout 3000
ctest --test-dir build/ReleaseOV -L main -E "test-llama-archs|test-recurrent-state-rollback-nemotron-h" --verbose --timeout 3000
openvino-windows-2022:
runs-on: windows-2022
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Clone
@@ -105,7 +105,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: openvino-windows-2022
variant: ccache
@@ -159,20 +159,11 @@ jobs:
- name: Test (CPU)
id: cmake_test_cpu
shell: cmd
# TODO: fix and re-enable the `test-llama-archs` test below
run: |
REM Find extracted OpenVINO folder dynamically
for /d %%i in (openvino_toolkit\*) do set OPENVINO_ROOT=%%i
call "%OPENVINO_ROOT%\setupvars.bat"
cd build
ctest --test-dir ReleaseOV -L main -E "${{ env.CTEST_EXCLUDE }}" -C Release --verbose --timeout 3000
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: openvino-windows-2022
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
ctest --test-dir ReleaseOV -L main -E "test-llama-archs|test-recurrent-state-rollback-nemotron-h" -C Release --verbose --timeout 3000
+2 -2
View File
@@ -67,7 +67,7 @@ jobs:
# note: sparing some ccache since these jobs run on dedicated runners that are not part of the organitzation
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@afde29e5b5422e5da23cb1f639e8baecadeadfc3 # https://github.com/ggml-org/ccache-action/pull/1
# with:
# key: riscv-ubuntu-native
# evict-old-files: 1d
@@ -137,7 +137,7 @@ jobs:
# note: sparing some ccache since these jobs run on dedicated runners that are not part of the organitzation
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@afde29e5b5422e5da23cb1f639e8baecadeadfc3 # https://github.com/ggml-org/ccache-action/pull/1
# with:
# key: riscv-ubuntu-native-sanitizer-${{ matrix.sanitizer }}-${{ matrix.build_type }}
# evict-old-files: 1d
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
uses: actions/checkout@v6
# - name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@v1.2.21
# if: ${{ matrix.sanitizer != 'UNDEFINED' }}
# with:
# key: ctest-${{ matrix.sanitizer }}-ubuntu-24.04
+2 -2
View File
@@ -288,8 +288,8 @@ jobs:
env:
# Sync versions in build.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Clone
+4 -34
View File
@@ -75,19 +75,11 @@ jobs:
sudo apt-get install -y ./level-zero.deb ./level-zero-devel.deb
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: sycl-ubuntu-24-${{ matrix.build }}
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: sycl-ubuntu-24-${{ matrix.build }}
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
@@ -104,18 +96,6 @@ jobs:
-DGGML_SYCL_F16=${{ matrix.fp16 }}
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: sycl-ubuntu-24-${{ matrix.build }}
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
windows-latest-sycl:
runs-on: windows-2022
@@ -147,7 +127,7 @@ jobs:
"LEVEL_ZERO_V1_SDK_PATH=C:/level-zero-sdk" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: sycl-windows-latest
variant: ccache
@@ -159,13 +139,3 @@ jobs:
- name: Build
id: cmake_build
run: examples/sycl/win-build-sycl.bat
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: sycl-windows-latest
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+8 -58
View File
@@ -53,20 +53,12 @@ jobs:
echo "CXX=g++-14" >> "$GITHUB_ENV"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: vulkan-ubuntu-24.04-arm
key: vulkan-ubuntu-24.04-arm-new
variant: ccache
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: vulkan-ubuntu-24.04-arm
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Configure
id: cmake_configure
@@ -81,18 +73,6 @@ jobs:
run: |
time cmake --build build -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: vulkan-ubuntu-24.04-arm
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
ubuntu-llvmpipe:
runs-on: ubuntu-24.04
@@ -122,19 +102,11 @@ jobs:
strip: 1
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: vulkan-ubuntu-24.04-llvmpipe
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: vulkan-ubuntu-24.04-llvmpipe
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
@@ -145,18 +117,6 @@ jobs:
-DGGML_VULKAN=ON
cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: vulkan-ubuntu-24.04-llvmpipe
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
@@ -180,7 +140,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: cpu-windows-2025-x64-vulkan
variant: ccache
@@ -220,13 +180,3 @@ jobs:
run: |
cd build
ctest -L main -C Release --verbose --timeout 900
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: cpu-windows-2025-x64-vulkan
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+3 -23
View File
@@ -54,10 +54,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: webgpu-ubuntu-24.04-arm-wasm
save: false
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Install Emscripten
run: |
@@ -75,15 +76,6 @@ jobs:
"https://github.com/google/dawn/releases/download/${DAWN_TAG}/${EMDAWN_PKG}"
unzip emdawn.zip
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-ubuntu-24.04-arm-wasm
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build WASM WebGPU
run: |
source emsdk/emsdk_env.sh
@@ -96,15 +88,3 @@ jobs:
-DEMDAWNWEBGPU_DIR=emdawnwebgpu_pkg
time cmake --build build-wasm --config Release --target test-backend-ops -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-ubuntu-24.04-arm-wasm
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
+6 -46
View File
@@ -69,10 +69,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: webgpu-macos-latest
save: false
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Dawn Dependency
id: dawn-depends
@@ -87,15 +88,6 @@ jobs:
mkdir dawn
tar -xvf artifact.tar.gz -C dawn --strip-components=1
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-macos-latest
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
@@ -103,18 +95,6 @@ jobs:
cmake -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DGGML_WEBGPU=ON -DGGML_METAL=OFF -DGGML_BLAS=OFF
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-macos-latest
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
@@ -130,10 +110,11 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: webgpu-ubuntu-24.04
save: false
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Dependencies
id: depends
@@ -157,15 +138,6 @@ jobs:
mkdir dawn
tar -xvf artifact.tar.gz -C dawn --strip-components=1
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-ubuntu-24.04
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
@@ -174,18 +146,6 @@ jobs:
-DGGML_WEBGPU=ON
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-ubuntu-24.04
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: copilot-setup-steps
evict-old-files: 1d
+2 -2
View File
@@ -64,7 +64,7 @@ jobs:
needs: create_tag
uses: ./.github/workflows/ui-build.yml
with:
ui_version: ${{ needs.create_tag.outputs.source_tag }}
hf_ui_version: ${{ needs.create_tag.outputs.source_tag }}
prepare_matrices:
name: Prepare Docker matrices
@@ -162,7 +162,7 @@ jobs:
if: ${{ matrix.config.prebuilt_ui == true }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist
- name: Set up QEMU
+4 -24
View File
@@ -49,22 +49,14 @@ jobs:
id: depends
run: |
sudo apt-get update
sudo apt-get install -y build-essential git cmake rocblas-dev hipblas-dev libssl-dev python3 python3-venv python3-pip jq
sudo apt-get install -y build-essential git cmake rocblas-dev hipblas-dev libssl-dev python3
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: hip-quality-check-ubuntu-22.04
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: hip-quality-check-ubuntu-22.04
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build with Werror
id: cmake_build
@@ -92,15 +84,3 @@ jobs:
cd build
make -j $(nproc) 2>&1 | tee metrics.log | grep -v 'Rpass-analysis=kernel-resource-usage\|remark:\|^$'
python3 ../scripts/hip/gcn-cdna-vgpr-check.py metrics.log
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: hip-quality-check-ubuntu-22.04
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
+2 -28
View File
@@ -19,7 +19,6 @@ env:
permissions:
contents: write
packages: write
jobs:
make-release:
@@ -85,13 +84,11 @@ jobs:
New version has been released.
## Assets
${{ steps.desc.outputs.nightly }}
## More info
**Web UI:** the `nightly-tag.txt` asset contains the tag of the corresponding nightly release
- [Releases and versioning of `ggml-org` projects](https://github.com/ggml-org/ggml/discussions/1579)
**More info:** [dist : releases and versioning of ggml-org projects](https://github.com/ggml-org/ggml/discussions/1579)
## ${{ steps.desc.outputs.changelog_title }}
@@ -114,29 +111,6 @@ jobs:
data: await fs.readFileSync('./nightly-tag.txt')
});
- name: Re-tag container images with release version
if: ${{ github.event.inputs.dry_run == 'false' && steps.desc.outputs.nightly_tag != '' }}
env:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
run: |
VERSION="${{ steps.checks.outputs.version }}"
NIGHTLY_TAG="${{ steps.desc.outputs.nightly_tag }}"
REPO_OWNER="${GITHUB_REPOSITORY_OWNER,,}"
IMAGE_REPO="ghcr.io/${REPO_OWNER}/${{ github.event.repository.name }}"
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
VARIANTS=("" "-cuda" "-cuda13" "-vulkan" "-rocm" "-intel" "-musa" "-openvino")
TYPES=("full" "light" "server")
for type in "${TYPES[@]}"; do
for variant in "${VARIANTS[@]}"; do
src="${IMAGE_REPO}:${type}${variant}-${NIGHTLY_TAG}"
dst="${IMAGE_REPO}:${type}${variant}-${VERSION}"
echo "Tagging ${src} -> ${dst}"
docker buildx imagetools create --tag "${dst}" "${src}"
done
done
- name: Dry run summary
if: ${{ github.event.inputs.dry_run == 'true' }}
run: |
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: "3.11"
pip-install: -r requirements/requirements-all.txt ty==0.0.78
pip-install: -r requirements/requirements-all.txt ty==0.0.35
# - name: Type-check with Pyright
# uses: jakebailey/pyright-action@v2
# with:
@@ -40,4 +40,4 @@ jobs:
# warnings: true
- name: Type-check with ty
run: |
ty check --exit-zero-on-warning --output-format=github
ty check --output-format=github
+158 -135
View File
@@ -61,8 +61,31 @@ jobs:
echo "should_release=false" >> $GITHUB_OUTPUT
fi
get-version:
runs-on: ubuntu-slim
outputs:
ui_version: ${{ steps.version.outputs.ui_version }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- id: version
run: |
# Resolve UI version: BUILD_NUMBER from cmake/build-info.cmake > git hash + epoch > fallback
version=""
if grep -q "BUILD_NUMBER" cmake/build-info.cmake; then
build_number=$(grep "set(BUILD_NUMBER" cmake/build-info.cmake | grep -oP '\d+')
if [ -n "$build_number" ] && [ "$build_number" -gt 0 ]; then
version="b${build_number}"
fi
fi
if [ -z "$version" ]; then
version=$(git rev-parse --short HEAD)-$(date +%s)
fi
echo "ui_version=${version}" >> $GITHUB_OUTPUT
macos-cpu:
needs: [check-release, ui-build]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
strategy:
matrix:
@@ -96,14 +119,15 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-${{ matrix.os }}-${{ matrix.arch }}
@@ -117,6 +141,7 @@ jobs:
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_BUILD_BORINGSSL=ON \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
@@ -142,7 +167,7 @@ jobs:
key: release-${{ matrix.os }}-${{ matrix.arch }}
ubuntu-cpu:
needs: [check-release, ui-build]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
strategy:
matrix:
@@ -166,11 +191,12 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Dependencies
id: depends
@@ -187,7 +213,7 @@ jobs:
- name: ccache
if: ${{ matrix.build != 's390x' }}
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-${{ matrix.os }}-cpu
@@ -201,6 +227,7 @@ jobs:
-DGGML_NATIVE=OFF \
-DGGML_CPU_ALL_VARIANTS=ON \
-DLLAMA_FATAL_WARNINGS=ON \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build --config Release -j $(nproc)
@@ -227,7 +254,7 @@ jobs:
key: release-${{ matrix.os }}-cpu
ubuntu-vulkan:
needs: [check-release, ui-build]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
strategy:
@@ -250,11 +277,12 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Dependencies
id: depends
@@ -272,7 +300,7 @@ jobs:
fi
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-${{ matrix.os }}-vulkan
@@ -286,6 +314,7 @@ jobs:
-DGGML_NATIVE=OFF \
-DGGML_CPU_ALL_VARIANTS=ON \
-DGGML_VULKAN=ON \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build --config Release -j $(nproc)
@@ -311,7 +340,7 @@ jobs:
key: release-${{ matrix.os }}-vulkan
android-arm64:
needs: [check-release, ui-build]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: ubuntu-latest
@@ -329,11 +358,12 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Set up JDK
uses: actions/setup-java@v5
@@ -358,7 +388,7 @@ jobs:
# cache on: https://github.com/ggerganov/tmp2/actions/runs/26534713799/job/78224189394
#
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@v1.2.21
# with:
# key: release-android-arm64
@@ -377,6 +407,7 @@ jobs:
-DLLAMA_FATAL_WARNINGS=ON \
-DGGML_OPENMP=OFF \
-DLLAMA_BUILD_BORINGSSL=ON \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build --config Release -j $(nproc)
@@ -402,7 +433,7 @@ jobs:
name: llama-bin-android-arm64.tar.gz
ubuntu-24-openvino:
needs: [check-release, ui-build]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: ubuntu-24.04
@@ -415,8 +446,8 @@ jobs:
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Set OpenVINO version output
@@ -429,14 +460,15 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-ubuntu-24.04-openvino-release-no-preset-v1
@@ -476,6 +508,7 @@ jobs:
-DGGML_OPENVINO=ON \
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build/ReleaseOV --config Release --parallel
@@ -519,7 +552,7 @@ jobs:
key: release-ubuntu-24.04-openvino-release-no-preset-v1
windows-openvino:
needs: [check-release, ui-build]
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: windows-2022
@@ -529,8 +562,8 @@ jobs:
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.3.1"
OPENVINO_VERSION_FULL: "2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR: "2026.3"
OPENVINO_VERSION_FULL: "2026.3.0.22451.bd8d6542e3c"
steps:
- name: Set OpenVINO version output
@@ -544,14 +577,15 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-windows-2022-openvino
variant: ccache
@@ -648,7 +682,7 @@ jobs:
windows-cpu:
name: windows-cpu / ${{ matrix.arch }}
needs: [check-release, ui-build]
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: windows-2025-vs2026
@@ -668,18 +702,19 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install Ninja
run: |
choco install ninja
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-windows-2025-vs2026-${{ matrix.arch }}-cpu
@@ -714,8 +749,6 @@ jobs:
with:
key: release-windows-2025-vs2026-${{ matrix.arch }}-cpu
# note: builds only the ggml-hip backend - llama-server is injected from the
# windows-cpu zip during the release "Merge artifacts" step
windows-rocm:
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
@@ -725,7 +758,7 @@ jobs:
strategy:
matrix:
include:
- ROCM_VERSION: "10.0.0"
- ROCM_VERSION: "7.14.0"
gpu_targets: "gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1103;gfx1150;gfx1151;gfx1152;gfx1153;gfx1200;gfx1201"
build: x64
@@ -736,12 +769,8 @@ jobs:
with:
fetch-depth: 0
- name: Install Ninja
run: |
choco install ninja
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: windows-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
evict-old-files: 1d
@@ -793,28 +822,33 @@ jobs:
- name: Build
run: |
cmake -S . -B build `
-G "Ninja Multi-Config" `
mkdir build
cd build
cmake .. `
-G "Unix Makefiles" `
-DCMAKE_PREFIX_PATH="${env:HIP_PATH}" `
-DCMAKE_BUILD_TYPE=Release `
-DGGML_BACKEND_DL=ON `
-DGGML_NATIVE=OFF `
-DGGML_CPU=OFF `
-DGGML_CPU=ON `
-DGGML_CPU_ALL_VARIANTS=ON `
-DGGML_HIP=ON `
-DCMAKE_C_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
-DCMAKE_CXX_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang++.exe" `
-DCMAKE_C_FLAGS="-Wno-error=incompatible-pointer-types" `
-DCMAKE_HIP_COMPILER="${env:HIP_PATH}\lib\llvm\bin\clang.exe" `
-DHIP_PATH="${env:HIP_PATH}" `
-DGGML_HIP_ROCWMMA_FATTN=ON `
-DAMDGPU_TARGETS="${{ matrix.gpu_targets }}"
cmake --build build --config Release --parallel ${env:NUMBER_OF_PROCESSORS} --target ggml-hip
cmake --build . --config Release --parallel ${env:NUMBER_OF_PROCESSORS}
- name: Verify HIP backend was built
run: |
$hipDll = Get-ChildItem -Path build\bin\Release -Filter "ggml-hip*.dll" -ErrorAction SilentlyContinue
$hipDll = Get-ChildItem -Path build\bin -Filter "ggml-hip*.dll" -ErrorAction SilentlyContinue
if (-not $hipDll) {
Write-Host "##[error]ggml-hip*.dll was NOT produced. The HIP backend silently failed to build."
Write-Host "Contents of build\bin\Release:"
Get-ChildItem build\bin\Release | Format-Table -AutoSize
Write-Host "Contents of build\bin:"
Get-ChildItem build\bin | Format-Table -AutoSize
exit 1
}
Write-Host "HIP backend artifact found:"
@@ -829,40 +863,10 @@ jobs:
$rocmVersionShort = ('${{ matrix.ROCM_VERSION }}'.Split('.')[0..1] -join '.')
echo "ROCM_VERSION_SHORT=$rocmVersionShort" >> $env:GITHUB_ENV
- name: Bundle HIP runtime DLLs (amdhip64_7.dll, rocm_kpack.dll, amd_comgr.dll)
run: |
$ErrorActionPreference = "Stop"
# See issue https://github.com/ggml-org/llama.cpp/issues/26929.
# ggml-hip.dll loads amdhip64_7.dll at run time. The Adrenalin driver
# ships an amdhip64_7.dll in System32, which the loader searches before PATH,
# so a matching DLL from PATH cannot win. Copy amdhip64 next to the
# binaries (exe directory is searched before System32) so the correct
# runtime is used. rocm_kpack.dll is amdhip64_7's direct dependency, so
# copy the matching version too. amd_comgr is copied as well to keep it
# in sync with the bundled amdhip64, avoiding a version mismatch with a
# amd_comgr from System32.
# rocblas/hipblaslt kernels resolve fine via PATH and are not copied.
$binPath = (rocm-sdk path --bin).Trim()
if (-not $binPath) { throw "rocm-sdk path --bin returned empty" }
write-host "ROCm bin path: $binPath"
$patterns = @("amdhip64_7.dll", "rocm_kpack.dll", "amd_comgr.dll")
foreach ($pattern in $patterns) {
$files = Get-ChildItem -Path $binPath -Filter $pattern -ErrorAction SilentlyContinue
if (-not $files) { throw "no match for $pattern in $binPath" }
foreach ($f in $files) {
Copy-Item $f.FullName -Destination build\bin\Release -Force
write-host " copied $($f.Name)"
}
}
- name: Pack artifacts
run: |
7z a -snl llama-bin-win-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.zip `
.\build\bin\Release\ggml-hip.dll `
.\build\bin\Release\amdhip64_7.dll `
.\build\bin\Release\rocm_kpack.dll `
.\build\bin\Release\amd_comgr.dll
cp "LICENSE" "build\bin\"
7z a -snl llama-bin-win-rocm-${{ env.ROCM_VERSION_SHORT }}-${{ matrix.build }}.zip .\build\bin\*
- name: Upload artifacts
uses: actions/upload-artifact@v6
@@ -875,8 +879,6 @@ jobs:
with:
key: windows-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
# note: builds only the backend library - llama-server (with the embedded UI)
# is injected from the windows-cpu zip during the release "Merge artifacts" step
windows:
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
@@ -907,6 +909,13 @@ jobs:
id: checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install Vulkan SDK
id: get_vulkan
if: ${{ matrix.backend == 'vulkan' }}
@@ -923,7 +932,7 @@ jobs:
# TODO: these jobs need to use llvm toolchain in order to utilize the ccache
#- name: ccache
# uses: ggml-org/ccache-action@v1.2.24
# uses: ggml-org/ccache-action@v1.2.21
# with:
# key: release-windows-2025-${{ matrix.arch }}-${{ matrix.backend }}
@@ -969,8 +978,6 @@ jobs:
path: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip
name: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip
# note: builds only the ggml-cuda backend - llama-server is injected from the
# windows-cpu zip during the release "Merge artifacts" step
windows-cuda:
name: windows-cuda (${{ matrix.cuda }}, ${{ matrix.arch }})
needs: [check-release]
@@ -999,6 +1006,13 @@ jobs:
id: checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install Cuda Toolkit
uses: ./.github/actions/windows-setup-cuda
with:
@@ -1011,7 +1025,7 @@ jobs:
choco install ninja
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-windows-2022-${{ matrix.arch }}-cuda-${{ matrix.cuda }}
@@ -1070,8 +1084,6 @@ jobs:
with:
key: release-windows-2022-${{ matrix.arch }}-cuda-${{ matrix.cuda }}
# note: builds only the ggml-sycl backend - llama-server is injected from the
# windows-cpu zip during the release "Merge artifacts" step
windows-sycl:
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
@@ -1106,8 +1118,15 @@ jobs:
Expand-Archive -Path "level-zero-win-sdk.zip" -DestinationPath "C:/level-zero-sdk" -Force
"LEVEL_ZERO_V1_SDK_PATH=C:/level-zero-sdk" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-windows-2022-x64-sycl
@@ -1176,7 +1195,7 @@ jobs:
key: release-windows-2022-x64-sycl
ubuntu-24-sycl:
needs: [check-release, ui-build]
needs: [check-release]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
strategy:
@@ -1218,14 +1237,15 @@ jobs:
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero-devel_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero-devel.deb
sudo apt-get install -y ./level-zero.deb ./level-zero-devel.deb
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-ubuntu-24.04-sycl-${{ matrix.build }}
@@ -1267,11 +1287,11 @@ jobs:
with:
key: release-ubuntu-24.04-sycl-${{ matrix.build }}
ubuntu-24-rocm:
needs: [check-release, ui-build]
ubuntu-22-rocm:
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
permissions:
actions: write
@@ -1279,7 +1299,7 @@ jobs:
strategy:
matrix:
include:
- ROCM_VERSION: "10.0.0"
- ROCM_VERSION: "7.14.0"
gpu_targets: "gfx908;gfx90a;gfx942;gfx950;gfx1010;gfx1011;gfx1012;gfx1030;gfx1031;gfx1032;gfx1033;gfx1034;gfx1035;gfx1036;gfx1100;gfx1101;gfx1102;gfx1150;gfx1151;gfx1152;gfx1200;gfx1201"
build: 'x64'
@@ -1290,11 +1310,12 @@ jobs:
with:
fetch-depth: 0
- name: Download UI build
uses: actions/download-artifact@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
name: llama-ui.zip
path: tools/ui/dist
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Free up disk space
uses: ggml-org/free-disk-space@v1.3.1
@@ -1302,9 +1323,9 @@ jobs:
tool-cache: true
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: release-ubuntu-24.04-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
key: release-ubuntu-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
evict-old-files: 1d
max-size: "1G"
@@ -1333,7 +1354,7 @@ jobs:
# libraries = HIP runtime and CMake configs needed for linking
# devel = compilers, headers, static libs
python -m pip install --upgrade pip
python -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel]==${{ matrix.ROCM_VERSION }}"
python -m pip install --index-url https://repo.amd.com/rocm/whl-multi-arch/ "rocm[libraries,devel]==${{ matrix.ROCM_VERSION }}"
# Get ROCm installation paths using the rocm-sdk CLI tool
ROCM_PATH=$(rocm-sdk path --root)
@@ -1367,6 +1388,7 @@ jobs:
-DGPU_TARGETS="${{ matrix.gpu_targets }}" \
-DGGML_HIP=ON \
-DHIP_PLATFORM=amd \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
${{ env.CMAKE_ARGS }}
cmake --build build --config Release -j $(nproc)
@@ -1392,10 +1414,10 @@ jobs:
- name: ccache-clear
uses: ./.github/actions/ccache-clear
with:
key: release-ubuntu-24.04-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
key: release-ubuntu-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
ios-xcode:
needs: [check-release]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: macos-26
@@ -1423,7 +1445,8 @@ jobs:
-DLLAMA_BUILD_SERVER=OFF \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_DEPLOYMENT_TARGET=16.0 \
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml \
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }}
cmake --build build --config Release -j $(sysctl -n hw.logicalcpu) -- CODE_SIGNING_ALLOWED=NO
- name: xcodebuild for swift package
@@ -1546,9 +1569,11 @@ jobs:
# name: llama-bin-${{ matrix.chip_type }}-openEuler-${{ matrix.arch }}${{ matrix.use_acl_graph == 'on' && '-aclgraph' || '' }}.tar.gz
ui-build:
needs: [check-release]
needs: [check-release, get-version]
if: ${{ needs.check-release.outputs.should_release == 'true' }}
uses: ./.github/workflows/ui-build.yml
with:
hf_ui_version: ${{ needs.get-version.outputs.ui_version }}
release:
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
@@ -1563,13 +1588,14 @@ jobs:
runs-on: ubuntu-slim
needs:
- get-version
- windows
- windows-cpu
- windows-cuda
- windows-sycl
- windows-rocm
- windows-openvino
- ubuntu-24-rocm
- ubuntu-22-rocm
- ubuntu-cpu
- ubuntu-vulkan
- ubuntu-24-openvino
@@ -1602,27 +1628,24 @@ jobs:
path: ./artifact
merge-multiple: true
- name: Merge artifacts
- name: Move artifacts
id: move_artifacts
run: |
mkdir -p release
# the windows-cpu zip contains the full toolset (llama-server with the embedded
# UI, ggml-cpu) - inject it into the other windows zips so that every archive
# ships the same binaries, only with a different backend library on top
echo "Injecting windows-cpu binaries (llama-server + CPU backend) into the backend zips..."
echo "Adding CPU backend files to existing zips..."
for arch in x64 arm64; do
cpu_zip="artifact/llama-bin-win-cpu-${arch}.zip"
temp_dir=$(mktemp -d)
echo "Extracting windows-cpu-${arch} package..."
echo "Extracting CPU backend for $arch..."
unzip "$cpu_zip" -d "$temp_dir"
echo "Merging into $arch zips..."
echo "Adding CPU files to $arch zips..."
for target_zip in artifact/llama-bin-win-*-${arch}.zip; do
if [[ "$target_zip" == "$cpu_zip" ]]; then
continue
fi
echo "Injecting into $(basename "$target_zip")"
echo "Adding CPU backend to $(basename "$target_zip")"
realpath_target_zip=$(realpath "$target_zip")
(cd "$temp_dir" && zip -r "$realpath_target_zip" .)
done
@@ -1646,7 +1669,7 @@ jobs:
id: download_ui
uses: actions/download-artifact@v7
with:
name: llama-ui.zip
name: ui-build
path: ./ui-dist
- name: Package UI
@@ -1703,7 +1726,7 @@ jobs:
- [Ubuntu s390x (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-s390x.tar.gz)
- [Ubuntu x64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-vulkan-x64.tar.gz)
- [Ubuntu arm64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-vulkan-arm64.tar.gz)
- [Ubuntu x64 (ROCm 10.0)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-rocm-10.0-x64.tar.gz)
- [Ubuntu x64 (ROCm 7.14)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-rocm-7.14-x64.tar.gz)
- [Ubuntu x64 (OpenVINO)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-openvino-${{ needs.ubuntu-24-openvino.outputs.openvino_version }}-x64.tar.gz)
- [Ubuntu x64 (SYCL FP32)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-sycl-fp32-x64.tar.gz)
- [Ubuntu x64 (SYCL FP16)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-sycl-fp16-x64.tar.gz)
@@ -1721,7 +1744,7 @@ jobs:
- [Windows x64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-vulkan-x64.zip)
- [Windows x64 (OpenVINO)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-openvino-${{ needs.windows-openvino.outputs.openvino_version }}-x64.zip)
- [Windows x64 (SYCL)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-sycl-x64.zip)
- [Windows x64 (ROCm 10.0)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-rocm-10.0-x64.zip)
- [Windows x64 (ROCm 7.14)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-rocm-7.14-x64.zip)
**openEuler:**
- [DISABLED](https://github.com/ggml-org/llama.cpp/pull/23705)
+9 -2
View File
@@ -73,6 +73,13 @@ jobs:
fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Build
id: cmake_build
run: |
@@ -103,7 +110,7 @@ jobs:
source .venv/bin/activate
cd tools/server/tests
export ${{ matrix.extra_args }}
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Slow tests
id: server_integration_tests_slow
@@ -112,4 +119,4 @@ jobs:
source .venv/bin/activate
cd tools/server/tests
export ${{ matrix.extra_args }}
PYTEST_WORKERS=1 SLOW_TESTS=1 ./tests.sh
SLOW_TESTS=1 ./tests.sh
+10 -40
View File
@@ -72,7 +72,7 @@ jobs:
run: |
cd tools/server/tests
source venv/bin/activate
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx1, backend-sampling)
id: server_integration_tests_backend_sampling
@@ -81,7 +81,7 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export LLAMA_ARG_BACKEND_SAMPLING=1
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx2)
id: server_integration_tests_gpu2
@@ -90,7 +90,7 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export GGML_METAL_DEVICES=2
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx2, backend-sampling)
id: server_integration_tests_gpu2_backend_sampling
@@ -99,10 +99,10 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export GGML_METAL_DEVICES=2 LLAMA_ARG_BACKEND_SAMPLING=1
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
server-cuda:
runs-on: "hf-jobs-t4-small:cuda13"
runs-on: [self-hosted, llama-server, Linux, NVIDIA]
steps:
- name: Clone
@@ -112,42 +112,12 @@ jobs:
fetch-depth: 0
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y cmake libssl-dev python3 python3-venv python3-pip
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
with:
key: self-hosted-server-cuda
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build -DGGML_CUDA=ON -DGGML_SCHED_NO_REALLOC=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc
cmake -B build -DGGML_CUDA=ON -DGGML_SCHED_NO_REALLOC=ON
cmake --build build --config Release -j $(nproc) --target llama-server
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: self-hosted-server-cuda
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Python setup
id: setup_python
run: |
@@ -162,7 +132,7 @@ jobs:
run: |
cd tools/server/tests
source venv/bin/activate
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx1, backend-sampling)
id: server_integration_tests_backend_sampling
@@ -171,7 +141,7 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export LLAMA_ARG_BACKEND_SAMPLING=1
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx2)
id: server_integration_tests_gpu2
@@ -180,7 +150,7 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export GGML_CUDA_DEVICES=2
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
- name: Tests (GPUx2, backend-sampling)
id: server_integration_tests_gpu2_backend_sampling
@@ -189,7 +159,7 @@ jobs:
cd tools/server/tests
source venv/bin/activate
export GGML_CUDA_DEVICES=2 LLAMA_ARG_BACKEND_SAMPLING=1
PYTEST_WORKERS=1 ./tests.sh
./tests.sh
server-kleidiai:
runs-on: ah-ubuntu_22_04-c8g_8x
+4 -34
View File
@@ -80,19 +80,11 @@ jobs:
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: server-ubuntu-24.04-arm
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: server-ubuntu-24.04-arm
folder: llama.cpp
hf_bucket: ggml-org/cache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build
id: cmake_build
@@ -101,18 +93,6 @@ jobs:
-DGGML_SCHED_NO_REALLOC=ON
cmake --build build --config Release -j $(nproc) --target llama-server
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: server-ubuntu-24.04-arm
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Python setup
id: setup_python
uses: actions/setup-python@v6
@@ -160,7 +140,7 @@ jobs:
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
uses: ggml-org/ccache-action@v1.2.21
with:
key: server-windows-2025-x64
evict-old-files: 1d
@@ -201,13 +181,3 @@ jobs:
cd tools/server/tests
export SLOW_TESTS="1"
./tests.sh
- name: ccache-clear
uses: ./.github/actions/ccache-clear
env:
GH_TOKEN: ${{ github.token }}
with:
key: server-windows-2025-x64
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+3 -4
View File
@@ -17,9 +17,8 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "24"
# cache: "npm"
# cache-dependency-path: "tools/ui/package-lock.json"
package-manager-cache: false
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies
run: npm ci
@@ -32,6 +31,6 @@ jobs:
- name: Upload built UI
uses: actions/upload-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
retention-days: 1
+7 -18
View File
@@ -3,8 +3,8 @@ name: UI Build
on:
workflow_call:
inputs:
ui_version:
description: 'Version string embedded in build.json (e.g. b1234); defaults to b<commit-count>'
hf_ui_version:
description: 'Version string for version.json (e.g. 12345)'
required: false
type: string
@@ -17,25 +17,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Resolve UI version
id: version
run: |
version="${{ inputs.ui_version }}"
if [ -z "$version" ]; then
version="b$(git rev-list --count HEAD)"
fi
echo "ui_version=${version}" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
# cache: "npm"
# cache-dependency-path: "tools/ui/package-lock.json"
package-manager-cache: false
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies
run: npm ci
@@ -43,7 +31,8 @@ jobs:
- name: Build application
env:
LLAMA_BUILD_NUMBER: ${{ steps.version.outputs.ui_version }}
HF_UI_VERSION: ${{ inputs.hf_ui_version || '' }}
LLAMA_BUILD_NUMBER: ${{ inputs.hf_ui_version || 'b0000' }}
run: npm run build
working-directory: tools/ui
@@ -54,6 +43,6 @@ jobs:
- name: Upload built UI
uses: actions/upload-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
retention-days: 1
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- name: Download UI build artifact
uses: actions/download-artifact@v7
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
- name: Create distribution archive
+2 -2
View File
@@ -64,7 +64,7 @@ jobs:
- name: Download built UI artifacts
uses: actions/download-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
- name: Run type checking
@@ -106,7 +106,7 @@ jobs:
- name: Download built UI artifacts
uses: actions/download-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
- name: Build Storybook
+6 -8
View File
@@ -57,14 +57,13 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "24"
# cache: "npm"
# cache-dependency-path: "tools/ui/package-lock.json"
package-manager-cache: false
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Download built UI artifacts
uses: actions/download-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
- name: Install dependencies
@@ -115,9 +114,8 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "24"
# cache: "npm"
# cache-dependency-path: "tools/ui/package-lock.json"
package-manager-cache: false
cache: "npm"
cache-dependency-path: "tools/ui/package-lock.json"
- name: Install dependencies
id: setup
@@ -128,7 +126,7 @@ jobs:
- name: Download built UI artifacts (reuses ui-build)
uses: actions/download-artifact@v6
with:
name: llama-ui.zip
name: ui-build
path: tools/ui/dist/
- name: Install Playwright browsers
+1 -60
View File
@@ -1,4 +1,4 @@
# date: Fri Sep 4 10:06:46 EEST 2026
# date: Tue Aug 18 14:32:43 EEST 2026
# this file is auto-generated by scripts/gen-authors.sh
Нияз Гарифзянов <112617865+garrnizon@users.noreply.github.com>
@@ -46,7 +46,6 @@ Abhijit Ramesh <abhijitramesh2k@gmail.com>
abhijitb11 <113058133+abhijitb11@users.noreply.github.com>
Abhilash Majumder <30946547+abhilash1910@users.noreply.github.com>
Abhinay Krishna <abhinaykrishna60@gmail.com>
Abhiram <78226909+geckguy@users.noreply.github.com>
Abhishek Gopinath K <31348521+overtunned@users.noreply.github.com>
abotsis <github@bots.is>
Abraham Gonzalez <theabecaster0@gmail.com>
@@ -88,7 +87,6 @@ akleine <alb.kleine@gmx.de>
Al G <toasting@gmail.com>
Al Mochkin <14274697+amochkin@users.noreply.github.com>
Alan Gray <agray3@users.noreply.github.com>
Alan Tseng <alanhc.tseng1999@gmail.com>
Alawode Oluwandabira <dabiraalawode@yahoo.com>
Albert Jin <albert.jin@gmail.com>
Alberto <57916483+albbus-stack@users.noreply.github.com>
@@ -138,9 +136,7 @@ alonfaraj <alonfaraj@gmail.com>
AlpinDale <52078762+AlpinDale@users.noreply.github.com>
alwqx <kenan3015@gmail.com>
Aman <amangupta052@gmail.com>
Aman Chadha(IVIXMMI) <79802170+ac-mmi@users.noreply.github.com>
Aman Gupta <amangupta052@gmail.com>
Aman Karki <itsamankarki@gmail.com>
amd-dwang <dong.wang@amd.com>
amd-lalithnc <lalithnc@amd.com>
Amir <amir_zia@outlook.com>
@@ -191,7 +187,6 @@ Anton Mitkov <anton.mitkov@codeplay.com>
Antonis Makropoulos <benuix@gmail.com>
Anudit Nagar <nagaranudit@gmail.com>
Anuj Attri <anujattri01@gmail.com>
anujj <ajalota@nvidia.com>
anzz1 <anzz1@live.com>
Aparna M P <aparmp@qti.qualcomm.com>
Aparna M P <quic_aparmp@quicinc.com>
@@ -201,7 +196,6 @@ arch-btw <57669023+arch-btw@users.noreply.github.com>
arcrank <arcrank@gmail.com>
ardfork <134447697+ardfork@users.noreply.github.com>
Arik Poznanski <arikpoz@users.noreply.github.com>
Aritro Bandyopadhyay <71339004+AriBandyo@users.noreply.github.com>
arlo-phoenix <140345165+arlo-phoenix@users.noreply.github.com>
Armen Kaleshian <kriation@users.noreply.github.com>
Arsen Arutunan <58118221+limloop@users.noreply.github.com>
@@ -236,7 +230,6 @@ bandoti <141645996+bandoti@users.noreply.github.com>
Bar Haim <barvhaim@gmail.com>
BarfingLemurs <128182951+BarfingLemurs@users.noreply.github.com>
Bart Louwers <bart.louwers@gmail.com>
Bartosz Taudul <wolf@nereid.pl>
Bartowski <3266127+bartowski1182@users.noreply.github.com>
Bartowski <ckealty1182@gmail.com>
Bas Nijholt <basnijholt@gmail.com>
@@ -284,7 +277,6 @@ Bono Lv <lvscar@users.noreply.github.com>
Borislav Stanimirov <b.stanimirov@abv.bg>
Borislav Stanimirov <b@ibob.bg>
Bowen Han <fancycode@gmail.com>
Brad Smith <1472326+infinitewarp@users.noreply.github.com>
Branden Butler <bwtbutler@hotmail.com>
Brandon Squizzato <35474886+bsquizz@users.noreply.github.com>
Brian <mofosyne@gmail.com>
@@ -295,7 +287,6 @@ Bryan Honof <bryanhonof@gmail.com>
bryanSwk <93190252+bryanSwk@users.noreply.github.com>
bsilvereagle <bsilvereagle@users.noreply.github.com>
bssrdf <merlintiger@hotmail.com>
Buğra Özgürsoy <13810383+ozgursoy@users.noreply.github.com>
byte-6174 <88070277+byte-6174@users.noreply.github.com>
Caleb DeLeeuw <143902425+SolshineCode@users.noreply.github.com>
Calvin Laurenson <calvin@laurenson.dev>
@@ -335,7 +326,6 @@ Chenguang Li <757486878@qq.com>
Chenguang Li <87689256+noemotiovon@users.noreply.github.com>
Chipmunk <101038159+CHIPMUNK-T0T@users.noreply.github.com>
chiranko <96988916+chiranko@users.noreply.github.com>
Chris Danis <cdanis@gmail.com>
Chris Elrod <elrodc@gmail.com>
Chris Kuehl <ckuehl@ckuehl.me>
Chris Lee <clee@mg8.org>
@@ -366,7 +356,6 @@ clyang <clyang@clyang.net>
cmdr2 <secondary.cmdr2@gmail.com>
cmdr2 <shashank.shekhar.global@gmail.com>
cocktailpeanut <121128867+cocktailpeanut@users.noreply.github.com>
codemonkey <441345965@qq.com>
codezjx <code.zjx@gmail.com>
coezbek <c.oezbek@gmail.com>
comex <comexk@gmail.com>
@@ -378,8 +367,6 @@ Copilot <198982749+Copilot@users.noreply.github.com>
Corentin REGAL <corentin.regal@gmail.com>
cphlipot <9103367+cphlipot@users.noreply.github.com>
cpumaxx <163466046+cpumaxx@users.noreply.github.com>
cqderek <cqderek@gmail.com>
cqderek <cqiang@qti.qualcomm.com>
crasm <crasm@git.vczf.net>
crasm <crasm@git.vczf.us>
crat0z <11581854+crat0z@users.noreply.github.com>
@@ -440,7 +427,6 @@ DavidKorczynski <david@adalogics.com>
davidrhodus <david@vacovideo.com>
Dawid Potocki <github@dawidpotocki.com>
Dawid Wysocki <62249621+TortillaZHawaii@users.noreply.github.com>
Daya Adianto <addianto@users.noreply.github.com>
ddh0 <chemist-mulches-39@icloud.com>
ddh0 <dylanhalladay02@icloud.com>
ddpasa <112642920+ddpasa@users.noreply.github.com>
@@ -477,7 +463,6 @@ Dmytro Romanov <casteldazur@gmail.com>
Dobri Danchev <12420863+danchev@users.noreply.github.com>
DocShotgun <126566557+DocShotgun@users.noreply.github.com>
Doctor Shotgun <126566557+DocShotgun@users.noreply.github.com>
Dominik Pantaleoni <95251853+dpantaleoni@users.noreply.github.com>
Don Mahurin <dmahurin@users.noreply.github.com>
Dong Won Kim <63934649+ddwkim@users.noreply.github.com>
Donghyeon Jeong <54725479+djeong20@users.noreply.github.com>
@@ -519,7 +504,6 @@ Emmanuel Ferdman <emmanuelferdman@gmail.com>
Emreerdog <34742675+Emreerdog@users.noreply.github.com>
Engininja2 <139037756+Engininja2@users.noreply.github.com>
Equim <sayaka@ekyu.moe>
Eric A Stalee <87948564+Eric-A-Stalee@users.noreply.github.com>
Eric Curtin <ecurtin@redhat.com>
Eric Curtin <eric.curtin@docker.com>
Eric Curtin <ericcurtin17@gmail.com>
@@ -535,7 +519,6 @@ Esko Toivonen <eskot98@gmail.com>
Ethan Turner <eturner64@gmail.com>
Ettore Di Giacinto <mudler@users.noreply.github.com>
EugeoSynthesisThirtyTwo <gabriel.dhimoila@gmail.com>
Eurekatic <eurekatic@eurekatic.eu>
Evan Huus <eapache@gmail.com>
Evan Jones <evan.q.jones@gmail.com>
Evan Miller <emmiller@gmail.com>
@@ -694,7 +677,6 @@ HimariO <dsfhe49854@gmail.com>
hipudding <huafengchun@gmail.com>
Hitesh Chopra <34310832+hiteshchopra11@users.noreply.github.com>
hksdpc255 <43977088+hksdpc255@users.noreply.github.com>
hmirin <hmirin@users.noreply.github.com>
hmscider <201289679+hmscider@users.noreply.github.com>
Hoang Nguyen <hugo53@users.noreply.github.com>
hoangmit <hoangmit@users.noreply.github.com>
@@ -719,7 +701,6 @@ Huawei Lin <huaweilin.cs@gmail.com>
Hugo <hugo@whynothugo.nl>
Hugo Roussel <hugo.rous@gmail.com>
Huifeng Ou <79071290+ho2103@users.noreply.github.com>
HumerousGorgon <31957201+HumerousGorgon@users.noreply.github.com>
hutli <6594598+hutli@users.noreply.github.com>
hutli <hutli@hutli.hu>
hutli <jensstaermose@hotmail.com>
@@ -757,15 +738,12 @@ intelmatt <61025942+intelmatt@users.noreply.github.com>
iohub <rickyang.pro@gmail.com>
Ionoclast Laboratories <brigham@ionoclast.com>
iron <lizhenneng@gmail.com>
Isaac <34376531+init-22@users.noreply.github.com>
Isaac McFadyen <isaac@imcf.me>
IsaacDynamo <61521674+IsaacDynamo@users.noreply.github.com>
Ishaan Gandhi <Ishaangandhi@gmail.com>
iSma <ismail.senhaji@gmail.com>
Ismail <115064057+AlrIsmail@users.noreply.github.com>
issixx <46835150+issixx@users.noreply.github.com>
itsnotoger <19309683+itsnotoger@users.noreply.github.com>
itterative <190138728+itterative@users.noreply.github.com>
Ivan <nekotekina@gmail.com>
Ivan Chikish <nekotekina@gmail.com>
Ivan Filipov <159561759+vanaka11@users.noreply.github.com>
@@ -790,7 +768,6 @@ Jakkala Mahesh <155058658+MaheshJakkala@users.noreply.github.com>
Jakub N <jakubniemczyk97@gmail.com>
JamePeng <jame_peng@sina.com>
James A Capozzoli <157492257+jac-jim@users.noreply.github.com>
James Francis <6763899+JamesFranc@users.noreply.github.com>
James O'Leary <65884233+jpohhhh@users.noreply.github.com>
James Reynolds <magnusviri@users.noreply.github.com>
jameswu2014 <545426914@qq.com>
@@ -821,7 +798,6 @@ Jed Fox <git@jedfox.com>
Jeff Bolz <jbolz@nvidia.com>
Jeffrey Morgan <jmorganca@gmail.com>
Jeffrey Quesnelle <emozilla@nousresearch.com>
Jeremie Miller <jeremie.miller@gmail.com>
Jeremy Demeule <jdemeule@users.noreply.github.com>
Jeremy Rand <244188+JeremyRand@users.noreply.github.com>
Jeroen Mostert <jeroen.mostert@cm.com>
@@ -833,7 +809,6 @@ Jesse Jojo Johnson <williamsaintgeorge@gmail.com>
Jesse LaRose <jesse@taey.ai>
Jesse Posner <jesse.posner@gmail.com>
Jesus Talavera <145992175+jesus-talavera-ibm@users.noreply.github.com>
Jetson Tan <tanzongyouyi@outlook.com>
Jett Janiak <jettjaniak@gmail.com>
Jeximo <jeximo@gmail.com>
JFLFY2255 <JFLFY2255@163.com>
@@ -850,7 +825,6 @@ Jie Fu (傅杰) <jiefu@tencent.com>
jiez <373447296@qq.com>
Jillis ter Hove <j.terhove@gmail.com>
Jim Wu <jimw567@users.noreply.github.com>
Jingxin (Philip) Li <philipaslee@gmail.com>
Jinwoo Jeong <33892306+williamjeong2@users.noreply.github.com>
Jinyang He <hejinyang@loongson.cn>
jinzihao <jinzihao1996@gmail.com>
@@ -876,13 +850,11 @@ John Balis <phobossystems@gmail.com>
John Bean <113509988+johnbean393@users.noreply.github.com>
John Eismeier <42679190+jeis4wpi@users.noreply.github.com>
John Smith <67539080+kingsidelee@users.noreply.github.com>
John-Henry Lim <42513874+Interpause@users.noreply.github.com>
Johnathan Craig Maudlin <13183098+jcmdln@users.noreply.github.com>
JohnnyB <jboero@users.noreply.github.com>
johnson442 <56517414+johnson442@users.noreply.github.com>
jojorne <jojorne@users.noreply.github.com>
jon-chuang <9093549+jon-chuang@users.noreply.github.com>
Jonas J <111707981+John-194@users.noreply.github.com>
Jonas Jankaitis <111707981+John-194@users.noreply.github.com>
Jonas Wunderlich <32615971+jonas-w@users.noreply.github.com>
Jonathan <47618606+jbuchananr@users.noreply.github.com>
@@ -952,7 +924,6 @@ Karsten Weiss <knweiss@gmail.com>
Karthick <j.karthic2004@gmail.com>
Karthik Kumar Viswanathan <195178+guilt@users.noreply.github.com>
Karthik Sethuraman <k.seth1993@gmail.com>
Kartik Gulia <kgulia@nvidia.com>
Kartik Sirohi <99896785+sirohikartik@users.noreply.github.com>
Kashif Rasul <kashif.rasul@gmail.com>
KASR <karim.asrih@gmail.com>
@@ -960,7 +931,6 @@ Kasumi <90275229+kasumi-1@users.noreply.github.com>
Katostrofik <georgiopapairo@gmail.com>
katsu560 <118887472+katsu560@users.noreply.github.com>
Kawrakow <48489457+ikawrakow@users.noreply.github.com>
kbenkhaled <khalilbenkhaled01@gmail.com>
kchro3 <62481661+kchro3@users.noreply.github.com>
kdkd <2569413+kdkd@users.noreply.github.com>
Keiichi Tabata <keiichi.tabata@outlook.com>
@@ -969,7 +939,6 @@ Kenvix ⭐ <kenvixzure@live.com>
Kerfuffle <44031344+KerfuffleV2@users.noreply.github.com>
Kevin Gibbons <bakkot@gmail.com>
Kevin Hannon <kehannon@redhat.com>
Kevin Hopper <93635715+kh0pper@users.noreply.github.com>
Kevin Ji <1146876+kevinji@users.noreply.github.com>
Kevin Kwok <antimatter15@gmail.com>
Kevin Liu <4396kevinliu@gmail.com>
@@ -995,14 +964,12 @@ Konstantin Herud <konstantin.herud@denkbares.com>
Konstantin Zhuravlyov <konstantin.zhuravlyov@amd.com>
Krishna Sridhar <99914379+srikris-sridhar@users.noreply.github.com>
krystiancha <krystian@krystianch.com>
krzsztf <krzysztof@witkowscy.org>
kubawoo <k-wach@o2.pl>
kumaal <44551860+kumaal@users.noreply.github.com>
kunal-vaishnavi <115581922+kunal-vaishnavi@users.noreply.github.com>
kunnis <kunnis@users.noreply.github.com>
Kunshang Ji <kunshang.ji@intel.com>
kuronekosaiko <EvanChanJ@163.com>
kurquhar <kurquhar@qti.qualcomm.com>
Kusha Gharahi <3326002+kushagharahi@users.noreply.github.com>
kustaaya <58045274+kustaaya@users.noreply.github.com>
kuvaus <22169537+kuvaus@users.noreply.github.com>
@@ -1014,7 +981,6 @@ Kyle Liang <liangmanlai@gmail.com>
Kyle Mistele <kyle@mistele.com>
KyleHagy <59183061+KyleHagy@users.noreply.github.com>
Kylin <56434533+KyL0N@users.noreply.github.com>
Kyozzz <1147385157@qq.com>
l-austenfeld <53152202+l-austenfeld@users.noreply.github.com>
l3utterfly <gc.pthzfoldr@gmail.com>
l8bloom <l8bloomapi@gmail.com>
@@ -1026,7 +992,6 @@ Lars Sonchocky-Helldorf <lars.sonchocky-helldorf@hamburg.de>
las7 <98077186+las7@users.noreply.github.com>
Lasse Lauwerys <65569591+Iemand005@users.noreply.github.com>
Laura <Tijntje_7@msn.com>
Laurent Zuijdwijk <laurent.zuijdwijk@gmail.com>
Law Po Ying <30721578+yingying0906@users.noreply.github.com>
lcy <lcy0321@users.noreply.github.com>
ldwang <ftgreat@163.com>
@@ -1074,8 +1039,6 @@ Ludovic Henry <git@ludovic.dev>
Ludovic Henry <ludovic@rivosinc.com>
Lukas Straub <lukasstraub2@web.de>
Łukasz Ślusarczyk <112692748+lslusarczyk@users.noreply.github.com>
Lukasz Stolcman <4583553+lstolcman@users.noreply.github.com>
LunalFresh <165352784+LunalFresh@users.noreply.github.com>
Luo Tian <lt@basecity.com>
luoyu-intel <yu.luo@intel.com>
luyhcsu <110711054+luyhcsu@users.noreply.github.com>
@@ -1091,7 +1054,6 @@ Maarten ter Huurne <maarten@treewalker.org>
Maciej Lisowski <39798354+MaciejDromin@users.noreply.github.com>
Mack Straight <eiz@users.noreply.github.com>
maddes8cht <55592906+maddes8cht@users.noreply.github.com>
Mads Marquart <mads@marquart.dk>
Maël Kerbiriou <m431.kerbiriou@gmail.com>
MaggotHATE <clay1326@gmail.com>
MagicExists <106458387+gugugiyu@users.noreply.github.com>
@@ -1253,8 +1215,6 @@ Naco Siren <naco-siren@users.noreply.github.com>
Nam D. Tran <42194884+namtranase@users.noreply.github.com>
nanahi <130121847+na-na-hi@users.noreply.github.com>
Nathan Epstein <nate2@umbc.edu>
Nathan Wilson <67372905+Nathanw1014@users.noreply.github.com>
Nathanw1014 <67372905+Nathanw1014@users.noreply.github.com>
Natsu <chino@hotococoa.moe>
Nauful Shaikh <nauful@gmail.com>
NawafAlansari <72708095+NawafAlansari@users.noreply.github.com>
@@ -1277,7 +1237,6 @@ niansa/tuxifan <tuxifan@posteo.de>
Nicholai Tukanov <nicholaitukanov@gmail.com>
Nicholas Sparks <157740354+nisparks@users.noreply.github.com>
Nick <0x0b4ac@gmail.com>
Nick Farrell <nick.farrell@aiven.io>
nick huang <nickhuang99@hotmail.com>
Nick Lafleur <55208706+nicklafleur@users.noreply.github.com>
Nick Towle <ntowle@gmail.com>
@@ -1300,7 +1259,6 @@ NikolaiLyssogor <59844691+NikolaiLyssogor@users.noreply.github.com>
Nikolaos Pothitos <pothitos@di.uoa.gr>
Nikolas <127742645+nneubacher@users.noreply.github.com>
Nikolay Popov <131475237+npopov-vst@users.noreply.github.com>
Nils Gladitz <nilsgladitz@gmail.com>
Nindaleth <Nindaleth@users.noreply.github.com>
ningshanwutuobang <ningshanwutuobang@gmail.com>
Noah <99681487+NoahOksuz@users.noreply.github.com>
@@ -1397,7 +1355,6 @@ Pop Flamingo <trevor.annedenise@icloud.com>
postmasters <namnguyen@google.com>
Pouya <PooyaGhahramanian@Gmail.com>
pqnet <119850+pqnet@users.noreply.github.com>
Prabhsimran Singh <pskrunner14@gmail.com>
Prabod <prabod@maincode.com>
Prajwal B Mehendarkar <prajwal.b.mehendarkar@ibm.com>
Pranav Dhinakar <pdhinaka@qti.qualcomm.com>
@@ -1421,7 +1378,6 @@ qouoq <qouoq@fastmail.com>
Qu Zongfu <43257352+yancaoweidaode@users.noreply.github.com>
quei <56998528+quei4r@users.noreply.github.com>
Quentin Bramas <quentin.bramas@gmail.com>
QuintinShaw <github@xyt.email>
QuintinShaw <yx6f20@soton.ac.uk>
qunash <anzoria@gmail.com>
quyentonndbs <raynaedgar8677@outlook.com>
@@ -1506,7 +1462,6 @@ robertomeroni <150194833+robertomeroni@users.noreply.github.com>
Robey Holderith <robey@flaminglunchbox.net>
Robin Davidsson <40024429+R-Dson@users.noreply.github.com>
Robyn <robyngraf@users.noreply.github.com>
Rock Chen <rockchen.tw@gmail.com>
Rőczey Barnabás <31726601+An0nie@users.noreply.github.com>
RodriMora <bullerwins@gmail.com>
Roger Chen <chenrui@gmail.com>
@@ -1544,21 +1499,17 @@ runfuture <runfuture@users.noreply.github.com>
RunningLeon <maningsheng@sensetime.com>
RunningLeon <mnsheng@yeah.net>
Russyyds <161207317+Russyyds@users.noreply.github.com>
Ryan C <ryan5rdx@users.noreply.github.com>
Ryan Goulden <percontation@gmail.com>
Ryan Landay <rlanday@gmail.com>
Ryan Mangeno <160974989+ryan-mangeno@users.noreply.github.com>
Ryder Wishart <ryderwishart@gmail.com>
Ryuei <louixs@users.noreply.github.com>
s-goto-11 <206795233+s-goto-11@users.noreply.github.com>
s0mecode <213953308+s0mecode@users.noreply.github.com>
s8322 <s0527684199@gmail.com>
Saad Ali <NIXKnight@users.noreply.github.com>
Saba Fallah <10401143+sfallah@users.noreply.github.com>
Saba Fallah <sabafallah@gmail.com>
Sachin Desai <smdesai@gmail.com>
Sachin Sharma <sachin@zettabolt.com>
Safi Ullah <safiullah.3915@gmail.com>
safranowith <bsh155762@gmail.com>
SakuraUmi <yukinon244@gmail.com>
Salvador E. Tropea <stropea@inti.gob.ar>
@@ -1601,7 +1552,6 @@ Sergey Alirzaev <l29ah@riseup.net>
Sergey Alirzaev <zl29ah@gmail.com>
Sergey Fedorov <vital.had@gmail.com>
Sergey Malinin <sergmalinin@gmail.com>
Sergey Sklyarov <sergey.sklyarov@gmail.com>
Sergio López <slp@redhat.com>
Sergio López <slp@sinrega.org>
Sergiu <8598216+mzsergiu@users.noreply.github.com>
@@ -1632,13 +1582,11 @@ Shawn Gu <shawngu@qti.qualcomm.com>
Shawn yang <137684499+Yangxiaoz@users.noreply.github.com>
Shelby Jenkins <47464908+ShelbyJenkins@users.noreply.github.com>
Sheldon Robinson <sheldon.robinson@live.com>
Shenghan Yang <ysharke@sjtu.edu.cn>
shibe2 <shibe@tuta.io>
Shijie <821898965@qq.com>
Shin-myoung-serp <relent95@naver.com>
Shintarou Okada <kokuzen@gmail.com>
shivamkumard-ctrl <shivamkumard@nvidia.com>
Shobhit <sobhit.me@gmail.com>
Shouyu <65317431+joeldushouyu@users.noreply.github.com>
Shouzheng Liu <61452103+lshzh-ww@users.noreply.github.com>
Shouzheng Liu <lshzh.hi@gmail.com>
@@ -1659,7 +1607,6 @@ Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>
simevo <github@simevo.com>
Simon Redman <simon@ergotech.com>
Simon Teixidor <simon@flaskpost.me>
Simon Willison <swillison@gmail.com>
simon886212 <37953122+simon886212@users.noreply.github.com>
Simranjeet Singh <105192966+simrnsingh@users.noreply.github.com>
@@ -1716,7 +1663,6 @@ stevenkuang <stevenkuang@tencent.com>
Steward Garcia <57494570+FSSRepo@users.noreply.github.com>
StrangeBytesDev <141275258+StrangeBytesDev@users.noreply.github.com>
strawberrymelonpanda <152940198+strawberrymelonpanda@users.noreply.github.com>
Strongtut <Strongtut@users.noreply.github.com>
Suaj Carrot <72162667+SuajCarrot@users.noreply.github.com>
sudhiarm <sudhi.sathyavathy@arm.com>
Sukriti Sharma <Ssukriti@users.noreply.github.com>
@@ -1741,7 +1687,6 @@ Tamar <Tamar0812@outlook.co.il>
tamarPal <tamarp3385@gmail.com>
Tameem <113388789+AhmadTameem@users.noreply.github.com>
Tamotsu Takahashi <ttakah+github@gmail.com>
Tanner Bruhn <66120666+tannerbruhn@users.noreply.github.com>
tarcey <cey.tarik@gmail.com>
Tarek Dakhran <t.dakhran@gmail.com>
Tarek Dakhran <tarek@liquid.ai>
@@ -1751,7 +1696,6 @@ Taylor <quantumtraveling@gmail.com>
tc-mb <157115220+tc-mb@users.noreply.github.com>
TecJesh <qdvm5gl@163.com>
Tei Home <taiteitonghome@proton.me>
Tekin Ertekin <tekin.ertekin@gmail.com>
Tekin Ertekin <tekinertekin@gmail.com>
tempstudio <49735574+tempstudio@users.noreply.github.com>
teo <TeoZosa@users.noreply.github.com>
@@ -1793,7 +1737,6 @@ Ting Lou <louting@189.cn>
Ting Lou <ting.lou@gmail.com>
Ting Sun <suntcrick@gmail.com>
Titaniumtown <titaniumtown@proton.me>
Tiwei Bie <tiwei.btw@antgroup.com>
tjohnman <tjohnman@users.noreply.github.com>
Tobias Lütke <tobi@shopify.com>
Toby <25832191+aetherbird@users.noreply.github.com>
@@ -1870,7 +1813,6 @@ Vishal Agarwal <vishalagarwal.jss@gmail.com>
Vishal Singh <vishal@zettabolt.com>
Vitali Lovich <vlovich+github@gmail.com>
Vivian <vynride@gmail.com>
vk <89937361+itsvedantkumar@users.noreply.github.com>
Vlad <spitfireage@gmail.com>
Vladimir <bogdad@gmail.com>
Vladimir Malyutin <first-leon@yandex.ru>
@@ -1955,7 +1897,6 @@ Yaiko <elyaiko@hotmail.com>
Yakine Tahtah <96926916+ReinforcedKnowledge@users.noreply.github.com>
YangLe <smilingpoplar@gmail.com>
yangli2 <yangli2@gmail.com>
Yaniss Amazouz <yaniss91600@gmail.com>
Yann Follet <131855179+YannFollet@users.noreply.github.com>
Yanzhao Wang <yanzhaow@qti.qualcomm.com>
Yarden Tal <yardent@qti.qualcomm.com>
+3 -3
View File
@@ -4,7 +4,7 @@ include(CheckIncludeFileCXX)
### llama.cpp version
set(LLAMA_VERSION_MAJOR 0)
set(LLAMA_VERSION_MINOR 4)
set(LLAMA_VERSION_MINOR 2)
set(LLAMA_VERSION_PATCH 0)
set(LLAMA_VERSION_BASE "${LLAMA_VERSION_MAJOR}.${LLAMA_VERSION_MINOR}.${LLAMA_VERSION_PATCH}")
@@ -134,8 +134,8 @@ option(LLAMA_BUILD_TOOLS "llama: build tools"
option(LLAMA_BUILD_EXAMPLES "llama: build examples" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_SERVER "llama: build server example" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_APP "llama: build the unified binary" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" OFF)
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available" ON)
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" ON)
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available (requires LLAMA_BUILD_UI=ON)" ON)
option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT})
option(LLAMA_TESTS_INSTALL "llama: install tests" ON)
-1
View File
@@ -57,7 +57,6 @@
/ggml/src/ggml-cann/ @ggml-org/ggml-cann
/ggml/src/ggml-common.h @ggerganov
/ggml/src/ggml-cpu/ @ggerganov
/ggml/src/ggml-cpu/iqp.* @bartowski1182
/ggml/src/ggml-cpu/spacemit/ @alex-spacemit
/ggml/src/ggml-cuda/ @ggml-org/ggml-cuda
/ggml/src/ggml-cuda/vendors/hip.h @IMbackK
-1
View File
@@ -74,7 +74,6 @@ For more info, please refer to the [AGENTS.md](AGENTS.md) file.
- If a PR does not warrant a new release, add `[no release]` in the squashed commit to spare CI resources
- Be mindful of maintenance: most of the work going into a feature happens after the PR is merged. If the PR author is not committed to contribute long-term, someone else needs to take responsibility (you)
- Add the ["merge ready"](https://github.com/ggml-org/llama.cpp/pulls?q=is%3Apr+is%3Aopen+draft%3Ano+sort%3Aupdated-desc+label%3A%22merge+ready%22+) label to a PR to indicate when a PR can be fast-merged without waiting for 2 independent reviews. [(more info)](https://github.com/ggml-org/llama.cpp/pull/26178)
- Wait for CI results before merging
Maintainers reserve the right to decline review or close pull requests for any reason, without any questions, particularly under any of the following conditions:
- The proposed change is already mentioned in the roadmap or an existing issue, and it has been assigned to someone.
+2 -2
View File
@@ -13,7 +13,7 @@
[![Docker](https://img.shields.io/github/actions/workflow/status/ggml-org/llama.cpp/docker.yml?label=Docker)](https://github.com/ggml-org/llama.cpp/actions/workflows/docker.yml)
[![Winget](https://img.shields.io/github/actions/workflow/status/ggml-org/llama.cpp/winget.yml?label=Winget)](https://github.com/ggml-org/llama.cpp/actions/workflows/winget.yml)
[ggml](https://github.com/ggml-org/ggml) / [ops](https://github.com/ggml-org/llama.cpp/blob/master/docs/ops.md) / [maintainer PRs](https://github.com/ggml-org/llama.cpp/issues?q=is%3Apr%20is%3Aopen%20draft%3AFalse%20(author%3Argerganov%20OR%20author%3AKitaitiMakoto%20OR%20author%3Adanbev%20OR%20author%3Aaldehir%20OR%20author%3Amax-krasnyansky%20OR%20author%3ACISC%20OR%20author%3Aggerganov%20OR%20author%3Aam17an%20OR%20author%3Ajhen0409%20OR%20author%3Abartowski1182%20OR%20author%3Anikwen%20OR%20author%3Ahipudding%20OR%20author%3Aravi9%20OR%20author%3AServeurpersoCom%20OR%20author%3Apwilkin%20OR%20author%3Areeselevine%20OR%20author%3Angxson%20OR%20author%3Ajeffbolznv%20OR%20author%3Amarty1885%20OR%20author%3A0cc4m%20OR%20author%3ATitaniumtown%20OR%20author%3Aangt%20OR%20author%3AIMbackK%20OR%20author%3Aarthw%20OR%20author%3AJohannesGaessler%20OR%20author%3AORippler%20OR%20author%3Aruixiang63%20OR%20author%3Axctan%20OR%20author%3Aallozaur%20OR%20author%3Ayomaytk%20OR%20author%3Aaendk%20OR%20author%3Awine99%20OR%20author%3Agaugarg-nv%20OR%20author%3Ataronaeo%20OR%20author%3Aforforever73%20OR%20author%3Alhez%20OR%20author%3Anetrunnereve%20OR%20author%3Afairydreaming)%20sort%3Aupdated-desc) / [dev stats](https://github.com/ggml-org/llama.cpp-dev) / [lib llama API](https://github.com/ggml-org/llama.cpp/issues/9289) / [llama-server REST API](https://github.com/ggml-org/llama.cpp/issues/9291)
[manifesto](https://github.com/ggml-org/llama.cpp/discussions/205) / [ggml](https://github.com/ggml-org/ggml) / [ops](https://github.com/ggml-org/llama.cpp/blob/master/docs/ops.md) / [maintainer PRs](https://github.com/ggml-org/llama.cpp/issues?q=is%3Apr%20is%3Aopen%20draft%3AFalse%20(author%3Argerganov%20OR%20author%3AKitaitiMakoto%20OR%20author%3Adanbev%20OR%20author%3Aaldehir%20OR%20author%3Amax-krasnyansky%20OR%20author%3ACISC%20OR%20author%3Aggerganov%20OR%20author%3Aam17an%20OR%20author%3Abartowski1182%20OR%20author%3Ahipudding%20OR%20author%3AServeurpersoCom%20OR%20author%3Apwilkin%20OR%20author%3Areeselevine%20OR%20author%3Angxson%20OR%20author%3Ajeffbolznv%20OR%20author%3A0cc4m%20OR%20author%3Aangt%20OR%20author%3AIMbackK%20OR%20author%3Aarthw%20OR%20author%3AJohannesGaessler%20OR%20author%3AORippler%20OR%20author%3Aruixiang63%20OR%20author%3Axctan%20OR%20author%3Aallozaur%20OR%20author%3Ayomaytk%20OR%20author%3Aaendk%20OR%20author%3Agaugarg-nv%20OR%20author%3Ataronaeo%20OR%20author%3Aforforever73%20OR%20author%3Alhez%20OR%20author%3Anetrunnereve%20OR%20author%3Afairydreaming)%20sort%3Aupdated-desc) / [compile times](https://github.com/ggml-org/llama.cpp-dev/blob/master/README-compile-times.md) / [lib llama API](https://github.com/ggml-org/llama.cpp/issues/9289) / [llama-server REST API](https://github.com/ggml-org/llama.cpp/issues/9291)
</div>
@@ -74,7 +74,7 @@ The `llama.cpp` project is build on top of the [ggml](https://github.com/ggml-or
| [CANN](docs/build.md#cann) | Ascend NPU |
| [CUDA](docs/build.md#cuda) | Nvidia GPU |
| [HIP](docs/build.md#hip) | AMD GPU |
| [Hexagon](docs/backend/snapdragon/README.md) | Snapdragon |
| [Hexagon [In Progress]](docs/backend/snapdragon/README.md) | Snapdragon |
| [IBM zDNN](docs/backend/zDNN.md) | IBM Z & LinuxONE |
| [MUSA](docs/build.md#musa) | Moore Threads GPU |
| [Metal](docs/build.md#metal-build) | Apple Silicon |
+1 -1
View File
@@ -80,7 +80,7 @@ static const command cmds[] = {
#undef UPDATE_HIDDEN
static int version(int /*argc*/, char ** /*argv*/) {
llama_print_build_info(llama_version(), stdout);
llama_print_build_info(llama_version());
return 0;
}
+1 -15
View File
@@ -18,7 +18,7 @@ LLAMA_BUILD_TESTS=OFF
LLAMA_BUILD_SERVER=OFF
LLAMA_BUILD_MTMD=ON
GGML_METAL=ON
GGML_METAL_EMBED_LIBRARY=${GGML_METAL_EMBED_LIBRARY:-ON}
GGML_METAL_EMBED_LIBRARY=ON
GGML_BLAS_DEFAULT=ON
GGML_OPENMP=OFF
@@ -169,14 +169,6 @@ setup_framework_structure() {
cp tools/mtmd/mtmd.h ${header_path}
cp tools/mtmd/mtmd-helper.h ${header_path}
if [[ "$GGML_METAL_EMBED_LIBRARY" == "OFF" ]]; then
if [[ "$platform" == "macos" ]]; then
cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/Versions/A/Resources/
else
cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/
fi
fi
# Create module map (common for all platforms)
cat > ${module_path}module.modulemap << EOF
framework module llama {
@@ -458,7 +450,6 @@ build_ios_sim() {
-DIOS=ON \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphonesimulator \
-DGGML_METAL_TARGET_OS=ios \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphonesimulator \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
@@ -476,7 +467,6 @@ build_ios_device() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphoneos \
-DGGML_METAL_TARGET_OS=ios \
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphoneos \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
@@ -508,7 +498,6 @@ build_visionos() {
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DCMAKE_SYSTEM_NAME=visionOS \
-DCMAKE_OSX_SYSROOT=xros \
-DGGML_METAL_TARGET_OS=xros \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xros \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
@@ -527,7 +516,6 @@ build_visionos_sim() {
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DCMAKE_SYSTEM_NAME=visionOS \
-DCMAKE_OSX_SYSROOT=xrsimulator \
-DGGML_METAL_TARGET_OS=xros \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xrsimulator \
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
@@ -546,7 +534,6 @@ build_tvos_sim() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvsimulator \
-DGGML_METAL_TARGET_OS=tvos \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DGGML_METAL=ON \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvsimulator \
@@ -565,7 +552,6 @@ build_tvos_device() {
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvos \
-DGGML_METAL_TARGET_OS=tvos \
-DCMAKE_OSX_ARCHITECTURES="arm64" \
-DGGML_METAL=ON \
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvos \
+2 -43
View File
@@ -189,8 +189,8 @@ if [ ! -z ${GG_BUILD_OPENVINO} ]; then
fi
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_OPENVINO=ON"
# TODO: fix failing tests on OpenVINO backend
CTEST_EXTRA="-E test-llama-archs|^test-recurrent-state-|test-backend-ops|test-save-load-state"
# TODO: fix and re-enable the `test-llama-archs` test below
CTEST_EXTRA="-E test-llama-archs|test-recurrent-state-rollback-nemotron-h"
fi
## helpers
@@ -300,40 +300,6 @@ function gg_sum_ctest_release {
gg_printf '```\n'
}
# test_llama_archs_tensor_split
function gg_run_test_llama_archs_tensor_split {
cd ${SRC}
set -e
if [ ! -z ${GG_BUILD_CUDA} ]; then
GGML_CUDA_DEVICES=1 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_CUDA_DEVICES=2 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_CUDA_DEVICES=3 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_CUDA_DEVICES=4 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
fi
if [ ! -z ${GG_BUILD_METAL} ]; then
GGML_METAL_DEVICES=1 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_METAL_DEVICES=2 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_METAL_DEVICES=3 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
GGML_METAL_DEVICES=4 ./build-ci-release/bin/test-llama-archs -s 1 2>&1
fi
set +e
}
function gg_sum_test_llama_archs_tensor_split {
gg_printf '### %s\n\n' "${ci}"
gg_printf 'Runs test-llama-archs with 1 to 4 devices\n'
gg_printf '- status: %s\n' "$(cat $OUT/${ci}.exit)"
gg_printf '```\n'
gg_printf '%s\n' "$(cat $OUT/${ci}.log)"
gg_printf '```\n'
}
# test_scripts
function gg_run_test_scripts {
@@ -732,11 +698,6 @@ function gg_check_build_requirements {
gg_printf 'ctest not found, please install\n'
exit 1
fi
if ! command -v unzip &> /dev/null; then
gg_printf 'unzip not found, please install\n'
exit 1
fi
}
function gg_run_test_backend_ops_cpu {
@@ -790,8 +751,6 @@ ret=0
test $ret -eq 0 && gg_run ctest_debug
test $ret -eq 0 && gg_run ctest_release
test $ret -eq 0 && gg_run test_llama_archs_tensor_split
if [ ! -z ${GG_BUILD_HIGH_PERF} ]; then
test $ret -eq 0 && gg_run test_backend_ops_cpu
fi
-3
View File
@@ -53,10 +53,7 @@ endif()
set(TARGET llama-common)
include(parsers/sources.cmake)
add_library(${TARGET}
${LLAMA_CHAT_PARSERS_SOURCES}
arg.cpp
arg.h
base64.hpp
+14 -114
View File
@@ -894,12 +894,6 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
postprocess_cpu_params(params.speculative.draft.cpuparams, &params.cpuparams);
postprocess_cpu_params(params.speculative.draft.cpuparams_batch, &params.cpuparams_batch);
// default the mmproj device to the global device selection if not set explicitly with -mmdev
if (params.mmproj_use_gpu && params.mmproj_device == nullptr && !params.devices.empty()) {
params.mmproj_device = params.devices.front();
params.mmproj_use_gpu = params.mmproj_device != nullptr;
}
if (params.prompt_cache_all && (params.interactive || params.interactive_first)) {
throw std::invalid_argument("error: --prompt-cache-all not supported in interactive mode yet\n");
}
@@ -966,11 +960,6 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
));
}
// if the preserve_reasoning kwarg was not specified explicitly, enable it by default
if (!params.default_template_kwargs.count("preserve_reasoning")) {
params.default_template_kwargs["preserve_reasoning"] = "true";
}
return true;
}
@@ -1654,14 +1643,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
}
}
).set_env("LLAMA_ARG_CTX_SIZE"));
add_opt(common_arg(
{ "--kv-unified-per-slot" }, "N",
"context limit per parallel slot (default: unset, behavior unchanged).\n"
"when set without -c/--ctx-size, the shared KV pool is sized to n_parallel*N",
[](common_params & params, int value) {
params.kv_unified_per_slot = value;
}
).set_env("LLAMA_ARG_KV_UNIFIED_PER_SLOT").set_examples({ LLAMA_EXAMPLE_SERVER }));
add_opt(common_arg(
{"-n", "--predict", "--n-predict"}, "N",
string_format(
@@ -2616,7 +2597,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
add_opt(common_arg(
// note: "-mmdev" must sort after "--rpc" in the preset map, else RPC devices are not registered yet
{"-mmdev", "--mmproj-device"}, "DEVICE",
"device to use for multimodal projector (none = don't offload, default: follows --device)\n"
"device to use for multimodal projector (none = don't offload, default: auto)\n"
"use --list-devices to see a list of available devices",
[](common_params & params, const std::string & value) {
if (value == "none") {
@@ -2663,27 +2644,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.mtmd_batch_max_tokens = value;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_MTMD_BATCH_MAX_TOKENS"));
add_opt(common_arg(
{"--video-fps"}, "N",
string_format("target video frame rate (default: %.1f)", params.video_fps),
[](common_params & params, const std::string & value) {
params.video_fps = std::stof(value);
}
).set_examples(mmproj_examples).set_env("LLAMA_ARG_VIDEO_FPS"));
add_opt(common_arg(
{"--video-timestamp-interval"}, "N",
string_format("interval in milliseconds between text timestamps (default: %" PRId64 ")", params.video_timestamp_interval_ms),
[](common_params & params, int value) {
params.video_timestamp_interval_ms = value;
}
).set_examples(mmproj_examples).set_env("LLAMA_ARG_VIDEO_TIMESTAMP_INTERVAL"));
add_opt(common_arg(
{"--video-ffmpeg-dir"}, "DIR",
"path to the directory containing ffmpeg and ffprobe (default: search in PATH)",
[](common_params & params, const std::string & value) {
params.video_ffmpeg_bin_dir = value;
}
).set_examples(mmproj_examples).set_env("LLAMA_ARG_VIDEO_FFMPEG_DIR"));
if (params.is_gen_docs || llama_supports_rpc()) {
add_opt(common_arg(
{"--rpc"}, "SERVERS",
@@ -2739,19 +2699,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
else { throw std::invalid_argument("invalid value"); }
}
).set_env("LLAMA_ARG_LOAD_MODE"));
add_opt(common_arg(
{"-lzm", "--lazy-mode"}, "MODE",
"on-demand reading of certain tensors, for example per-layer embeddings (default: auto)\n"
"- on: read the rows of such tensors from disk on demand instead of keeping them resident (requires mmap)\n"
"- auto: on, but only for tensors larger than 4 GiB\n"
"- off: always keep them resident",
[](common_params & params, const std::string & value) {
/**/ if (value == "on") { params.lazy_mode = LLAMA_LAZY_MODE_ON; }
else if (value == "auto") { params.lazy_mode = LLAMA_LAZY_MODE_AUTO; }
else if (value == "off") { params.lazy_mode = LLAMA_LAZY_MODE_OFF; }
else { throw std::invalid_argument("invalid value"); }
}
).set_env("LLAMA_ARG_LAZY_MODE"));
add_opt(common_arg(
{"--numa"}, "TYPE",
"attempt optimizations that help on some NUMA systems\n"
@@ -2803,20 +2750,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
if (value < 0) {
throw std::invalid_argument("invalid value");
}
llm_add_n_cpu_ffn_overrides(value, LLM_FFN_EXPS_REGEX, params.tensor_buft_overrides);
for (int i = 0; i < value; ++i) {
// keep strings alive and avoid leaking memory by storing them in a static vector
static std::list<std::string> buft_overrides;
buft_overrides.push_back(llm_ffn_exps_block_regex(i));
params.tensor_buft_overrides.push_back({buft_overrides.back().c_str(), ggml_backend_cpu_buffer_type()});
}
}
).set_env("LLAMA_ARG_N_CPU_MOE"));
add_opt(common_arg(
{"-ncffn", "--n-cpu-ffn"}, "N",
"keep the dense FFN weights of the first N layers in the CPU\n"
"(dense models; for MoE expert weights use --n-cpu-moe)",
[](common_params & params, int value) {
if (value < 0) {
throw std::invalid_argument("invalid value");
}
llm_add_n_cpu_ffn_overrides(value, LLM_FFN_DENSE_REGEX, params.tensor_buft_overrides);
}
).set_env("LLAMA_ARG_N_CPU_FFN"));
GGML_ASSERT(params.n_gpu_layers < 0); // string_format would need to be extended for a default >= 0
add_opt(common_arg(
{"-ngl", "--gpu-layers", "--n-gpu-layers"}, "N",
@@ -3564,10 +3505,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
LOG_WRN("Setting 'enable_thinking' via --chat-template-kwargs is deprecated. "
"Use --reasoning on / --reasoning off instead.\n");
}
if (item.key() == "preserve_reasoning") {
LOG_WRN("Setting 'preserve_reasoning' via --chat-template-kwargs is deprecated. "
"Use --reasoning-preserve / --no-reasoning-preserve instead.\n");
}
params.default_template_kwargs[item.key()] = item.value().dump();
}
}
@@ -3758,7 +3695,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
add_opt(common_arg(
{"--reasoning-preserve"},
{"--no-reasoning-preserve"},
"preserve reasoning trace in the full history, not just the last assistant message (default: enabled)\n"
"preserve reasoning trace in the full history, not just the last assistant message (default: template default)\n"
"compatible with certain templates having 'supports_preserve_reasoning' capability\n"
"example: https://docs.z.ai/guides/capabilities/thinking-mode#preserved-thinking",
[](common_params & params, bool value) {
@@ -3767,7 +3704,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
} else {
params.default_template_kwargs["preserve_reasoning"] = "false";
}
params.preserve_reasoning_specified = true;
}
).set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_COMPLETION, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_REASONING_PRESERVE"));
add_opt(common_arg(
@@ -3907,14 +3843,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
common_log_set_file(common_log_main(), value.c_str());
}
).set_env("LLAMA_ARG_LOG_FILE"));
add_opt(common_arg(
{"--log-jsonl"},
{"--no-log-jsonl"},
"Log as JSONL (one JSON object per line) to stdout, this also disables colored logging (default: disabled)",
[](common_params &, bool value) {
common_log_set_jsonl(common_log_main(), value);
}
).set_env("LLAMA_ARG_LOG_JSONL"));
add_opt(common_arg(
{"--log-prompts-dir"}, "PATH",
"Log prompts to directory (auto-created if not present; only used for debugging, default: disabled)",
@@ -4156,7 +4084,11 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
if (value < 0) {
throw std::invalid_argument("invalid value");
}
llm_add_n_cpu_ffn_overrides(value, LLM_FFN_EXPS_REGEX, params.speculative.draft.tensor_buft_overrides);
for (int i = 0; i < value; ++i) {
static std::list<std::string> buft_overrides_draft;
buft_overrides_draft.push_back(llm_ffn_exps_block_regex(i));
params.speculative.draft.tensor_buft_overrides.push_back({buft_overrides_draft.back().c_str(), ggml_backend_cpu_buffer_type()});
}
}
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_N_CPU_MOE"));
@@ -4177,38 +4109,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.speculative.draft.n_min = value;
}
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_LOOKUP, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_N_MIN"));
add_opt(common_arg(
{"--spec-synth-len"}, "L",
"target mean synthetic acceptance length, including the target token (benchmarking only)",
[](common_params & params, const std::string & value) {
const std::string text = string_strip(value);
size_t pos = 0;
const double length = std::stod(text, &pos);
if (pos != text.size() || length == -1.0) {
throw std::invalid_argument("invalid value");
}
params.speculative.synth_len = length;
}
).set_spec().set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_SYNTH_LEN"));
add_opt(common_arg(
{"--spec-synth-rates"}, "P0,P1,...",
"comma-separated unconditional per-position synthetic acceptance probabilities (benchmarking only)",
[](common_params & params, const std::string & value) {
const auto values = string_split<std::string>(value, ',');
std::vector<double> rates;
rates.reserve(values.size());
for (const auto & raw : values) {
const std::string text = string_strip(raw);
size_t pos = 0;
const double rate = std::stod(text, &pos);
if (pos != text.size()) {
throw std::invalid_argument("invalid value");
}
rates.push_back(rate);
}
params.speculative.synth_rates = std::move(rates);
}
).set_spec().set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_SYNTH_RATES"));
add_opt(common_arg(
{"--spec-draft-p-split", "--draft-p-split"}, "P",
@@ -4235,7 +4135,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_BACKEND_SAMPLING"));
add_opt(common_arg(
{"--spec-draft-device", "-devd", "--device-draft"}, "<dev1,dev2,..>",
"comma-separated list of devices to use for offloading the draft model (none = don't offload, default: follows --device)\n"
"comma-separated list of devices to use for offloading the draft model (none = don't offload)\n"
"use --list-devices to see a list of available devices",
[](common_params & params, const std::string & value) {
params.speculative.draft.devices = parse_device_list(value);
+3 -3
View File
@@ -29,7 +29,7 @@ const char * llama_build_info(void) {
return s.c_str();
}
void llama_print_build_info(const char * llama_version, FILE * stream) {
fprintf(stream, "version: %s (build %d, commit %s)\n", llama_version, llama_build_number(), llama_commit());
fprintf(stream, "built with %s for %s\n", llama_compiler(), llama_build_target());
void llama_print_build_info(const char * llama_version) {
fprintf(stderr, "version: %s (build %d, commit %s)\n", llama_version, llama_build_number(), llama_commit());
fprintf(stderr, "built with %s for %s\n", llama_compiler(), llama_build_target());
}
+1 -3
View File
@@ -1,7 +1,5 @@
#pragma once
#include <cstdio>
int llama_build_number(void);
const char * llama_commit(void);
@@ -10,4 +8,4 @@ const char * llama_compiler(void);
const char * llama_build_target(void);
const char * llama_build_info(void);
void llama_print_build_info(const char *, FILE * = stderr);
void llama_print_build_info(const char *);
+2404 -9
View File
File diff suppressed because it is too large Load Diff
+2 -27
View File
@@ -402,11 +402,10 @@ void common_params_print_info(const common_params & params, bool print_devices)
#endif
COM_TRC("%s: build %d (%s) with %s for %s%s\n", __func__, llama_build_number(), llama_commit(), llama_compiler(), llama_build_target(), build_type);
const int verbosity = common_log_get_verbosity_thold();
COM_INF("%s: verbosity = %d (adjust with the `-lv N` CLI arg)\n", __func__, verbosity);
COM_INF("%s: verbosity = %d (adjust with the `-lv N` CLI arg)\n", __func__, common_log_get_verbosity_thold());
// device enumeration creates a primary context on CUDA backends, skip it when the caller does not own any device
if (print_devices && verbosity >= LOG_LEVEL_TRACE) {
if (print_devices) {
COM_TRC("%s", "device_info:\n");
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
auto * dev = ggml_backend_dev_get(i);
@@ -1295,34 +1294,11 @@ common_init_result::common_init_result(common_params & params, bool model_only)
if (params.fit_params) {
COM_TRC("%s", "fitting params to device memory ...\n");
COM_TRC("%s", "(for bugs during this step try to reproduce them with -fit off, or provide --verbose logs if the bug only occurs with -fit on)\n");
// the draft context is created from the same base params and follows the main context, fit both together
const bool has_draft = params.speculative.has_dft();
const bool spec_mtp = std::find(params.speculative.types.begin(), params.speculative.types.end(),
COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
common_params params_dft = common_base_params_to_speculative(params);
auto mparams_dft = common_model_params_to_llama(params_dft);
auto cparams_dft = common_context_params_to_llama(params_dft);
if (spec_mtp) {
cparams_dft.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
}
cparams_dft.n_rs_seq = 0;
const common_fit_extra_model extra = {
/*.path_model =*/ params_dft.model.path.c_str(),
/*.mparams =*/ &mparams_dft,
/*.cparams =*/ &cparams_dft,
/*.shares_model =*/ !has_draft, // an MTP context runs on the weights of the main model
};
common_fit_params(params.model.path.c_str(), &mparams, &cparams,
params.tensor_split,
params.tensor_buft_overrides.data(),
params.fit_params_target.data(),
params.fit_params_min_ctx,
has_draft || spec_mtp ? &extra : nullptr,
params.verbosity >= LOG_LEVEL_DEBUG ? GGML_LOG_LEVEL_DEBUG : GGML_LOG_LEVEL_ERROR);
}
@@ -1688,7 +1664,6 @@ struct llama_model_params common_model_params_to_llama(common_params & params) {
mparams.main_gpu = params.main_gpu;
mparams.split_mode = params.split_mode;
mparams.load_mode = params.load_mode;
mparams.lazy_mode = params.lazy_mode;
mparams.tensor_split = params.tensor_split;
mparams.check_tensors = params.check_tensors;
mparams.use_extra_bufts = !params.no_extra_bufts;
+4 -32
View File
@@ -8,7 +8,6 @@
#include "ggml.h"
#include "llama.h"
#include <list>
#include <set>
#include <sstream>
#include <string>
@@ -270,7 +269,7 @@ struct common_params_sampling {
COMMON_SAMPLER_TYPE_TEMPERATURE,
};
common_grammar grammar; // optional grammar constraint (user / output-format / tool-calls)
common_grammar grammar; // optional grammar constraint (user / output-format / tool-calls)
bool grammar_lazy = false;
std::vector<common_grammar_trigger> grammar_triggers; // optional triggers (for lazy grammars)
std::set<llama_token> preserved_tokens;
@@ -370,9 +369,6 @@ struct common_params_speculative_ngram_cache {
struct common_params_speculative {
std::vector<enum common_speculative_type> types = { COMMON_SPECULATIVE_TYPE_NONE };
double synth_len = -1.0;
std::vector<double> synth_rates;
// used by Simple, MTP, Eagle3, etc. - all methods that require some kind of draft model
common_params_speculative_draft draft;
@@ -387,10 +383,6 @@ struct common_params_speculative {
return !draft.mparams.empty();
}
bool has_synth() const {
return synth_len != -1.0 || !synth_rates.empty();
}
uint32_t need_n_rs_seq() const {
bool needs_rs_seq = std::any_of(types.begin(), types.end(), [&](auto t) {
return t == COMMON_SPECULATIVE_TYPE_DRAFT_MTP || t == COMMON_SPECULATIVE_TYPE_DRAFT_EAGLE3 || t == COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH || t == COMMON_SPECULATIVE_TYPE_DRAFT_DSPARK;
@@ -483,8 +475,6 @@ struct common_params {
enum llama_split_mode split_mode = LLAMA_SPLIT_MODE_LAYER; // how to split the model across GPUs
enum llama_load_mode load_mode = LLAMA_LOAD_MODE_AUTO; // how to load the model
enum llama_lazy_mode lazy_mode = LLAMA_LAZY_MODE_AUTO; // on-demand reading of tensors marked by the arch
common_cpu_params cpuparams;
common_cpu_params cpuparams_batch;
@@ -599,11 +589,6 @@ struct common_params {
int image_max_tokens = -1;
int mtmd_batch_max_tokens = 1024;
// for video input
float video_fps = 4.0f;
int64_t video_timestamp_interval_ms = 5000;
std::string video_ffmpeg_bin_dir = "";
// finetune
struct lr_opt lr;
enum ggml_opt_optimizer_type optimizer = GGML_OPT_OPTIMIZER_TYPE_ADAMW;
@@ -627,7 +612,6 @@ struct common_params {
bool cache_prompt = true; // whether to enable prompt caching
bool cache_idle_slots = true; // save and clear idle slots upon starting a new task
int32_t n_ctx_checkpoints = 32; // max number of context checkpoints per slot
int32_t kv_unified_per_slot = 0; // max context per parallel slot; 0 = unset
int32_t checkpoint_min_step = 8192; // minimum spacing between context checkpoints
int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
@@ -657,7 +641,6 @@ struct common_params {
std::string ssl_file_cert = ""; // NOLINT
std::map<std::string, std::string> default_template_kwargs;
bool preserve_reasoning_specified = false;
// CLI params
std::string server_base; // if set, connect to this server instead of starting a new one
@@ -1125,30 +1108,19 @@ const char * const LLM_KV_SPLIT_TENSORS_COUNT = "split.tensors.count";
}
//
// FFN offload utils
// MoE utils
//
const char * const LLM_FFN_EXPS_REGEX = "\\.ffn_(up|down|gate|gate_up)_(ch|)exps";
const char * const LLM_FFN_DENSE_REGEX = "\\.ffn_(up|down|gate)\\.";
inline std::string llm_ffn_block_regex(int idx, const char * ffn_regex) {
return string_format("blk\\.%d%s", idx, ffn_regex);
inline std::string llm_ffn_exps_block_regex(int idx) {
return string_format("blk\\.%d%s", idx, LLM_FFN_EXPS_REGEX);
}
inline llama_model_tensor_buft_override llm_ffn_exps_cpu_override() {
return { LLM_FFN_EXPS_REGEX, ggml_backend_cpu_buffer_type() };
}
inline void llm_add_n_cpu_ffn_overrides(int n, const char * ffn_regex, std::vector<llama_model_tensor_buft_override> & overrides) {
// keep strings alive and avoid leaking memory by storing them in a static list
static std::list<std::string> buft_override_strings;
for (int i = 0; i < n; ++i) {
buft_override_strings.push_back(llm_ffn_block_regex(i, ffn_regex));
overrides.push_back({buft_override_strings.back().c_str(), ggml_backend_cpu_buffer_type()});
}
}
//
// training utils
//
+17 -105
View File
@@ -178,7 +178,7 @@ common_device_memory_data_vec common_get_device_memory_data(
static void common_params_fit_impl(
const char * path_model, struct llama_model_params * mparams, struct llama_context_params * cparams,
float * tensor_split, struct llama_model_tensor_buft_override * tensor_buft_overrides,
size_t * margins_s, uint32_t n_ctx_min, const common_fit_extra_model * extra, enum ggml_log_level log_level) {
size_t * margins_s, uint32_t n_ctx_min, enum ggml_log_level log_level) {
if (mparams->split_mode == LLAMA_SPLIT_MODE_TENSOR) {
throw common_params_fit_exception("llama_params_fit is not implemented for SPLIT_MODE_TENSOR, abort");
}
@@ -191,92 +191,10 @@ static void common_params_fit_impl(
uint32_t hp_nct = 0; // hparams.n_ctx_train
uint32_t hp_nex = 0; // hparams.n_expert
// with non-unified kv, we need to take into account n_streams
// for example, if memory can hold more than model's trained context size, we must extend the n_ctx to hold enough n_streams
const uint32_t n_streams = cparams->kv_unified ? 1 : std::max<uint32_t>(1, cparams->n_seq_max);
const bool n_ctx_auto = cparams->n_ctx == 0;
dmds_t dmds_extra; // memory of the extra model, laid out on the devices of the main model
uint32_t n_ctx_extra = 0; // context that memory was measured at
// the extra model competes for the same memory as the main model, add it to every measurement
// its memory is measured again whenever the context it follows changes
auto add_extra_memory = [&](dmds_t & dmds) {
if (extra == nullptr) {
return;
}
if (dmds_extra.empty() || n_ctx_extra != cparams->n_ctx) {
std::vector<ggml_backend_dev_t> devs_extra;
uint32_t ngl_extra = 0;
uint32_t nct_extra = 0;
uint32_t nex_extra = 0;
extra->cparams->n_ctx = cparams->n_ctx;
LOG_TRC("%s: getting device memory data for the extra model at a context size of %" PRIu32 ":\n",
__func__, cparams->n_ctx);
dmds_t measured;
try {
measured = common_get_device_memory_data_impl(
extra->path_model, extra->mparams, extra->cparams, devs_extra, ngl_extra, nct_extra, nex_extra, log_level);
} catch (const std::runtime_error & e) {
// the extra model is optional, fit the main model alone rather than giving up
LOG_WRN("%s: failed to measure the memory of the extra model, fitting without it: %s\n", __func__, e.what());
dmds_extra = dmds_t(devs.size() + 1);
n_ctx_extra = cparams->n_ctx;
return;
}
dmds_extra = dmds_t(devs.size() + 1);
dmds_extra.back().mb = measured.back().mb;
for (size_t je = 0; je < devs_extra.size(); je++) {
for (size_t id = 0; id < devs.size(); id++) {
if (devs_extra[je] == devs[id]) {
dmds_extra[id].mb.model += measured[je].mb.model;
dmds_extra[id].mb.context += measured[je].mb.context;
dmds_extra[id].mb.compute += measured[je].mb.compute;
break;
}
}
}
if (extra->shares_model) {
for (llama_device_memory_data & dmd : dmds_extra) {
dmd.mb.model = 0;
}
}
n_ctx_extra = cparams->n_ctx;
}
for (size_t id = 0; id < dmds.size(); id++) {
dmds[id].mb.model += dmds_extra[id].mb.model;
dmds[id].mb.context += dmds_extra[id].mb.context;
dmds[id].mb.compute += dmds_extra[id].mb.compute;
}
};
// step 1: get data for default parameters and check whether any changes are necessary in the first place
LOG_TRC("%s: getting device memory data for initial parameters:\n", __func__);
dmds_t dmds_full = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
// saturate instead of overflowing, this also preserves the UINT32_MAX sentinel of n_ctx_min:
const uint32_t n_ctx_max = (uint32_t) std::min<uint64_t>(uint64_t(hp_nct) * n_streams, UINT32_MAX);
const uint32_t n_ctx_min_total = (uint32_t) std::min<uint64_t>(uint64_t(n_ctx_min) * n_streams, UINT32_MAX);
// llama_context would use only hp_nct in total for n_ctx == 0, resolve the context before measuring anything else:
if (n_ctx_auto) {
cparams->n_ctx = n_ctx_max;
if (n_streams > 1) {
LOG_TRC("%s: context size unset and KV cache not unified -> using %" PRIu32 " for %" PRIu32 " sequences:\n",
__func__, n_ctx_max, n_streams);
dmds_full = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
}
}
add_extra_memory(dmds_full);
const dmds_t dmds_full = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
const size_t nd = devs.size(); // number of devices
std::vector<int64_t> margins; // this function uses int64_t rather than size_t for memory sizes to more conveniently handle deficits
@@ -389,8 +307,8 @@ static void common_params_fit_impl(
"%s: cannot meet free memory targets on all devices, need to use %" PRId64 " MiB less in total\n",
__func__, -global_surplus/MiB);
}
if (n_ctx_auto) {
if (n_ctx_max > n_ctx_min_total) {
if (cparams->n_ctx == 0) {
if (hp_nct > n_ctx_min) {
int64_t sum_used_target = sum_free;
if (nd == 0) {
sum_used_target -= margins[0];
@@ -410,9 +328,8 @@ static void common_params_fit_impl(
}
int64_t sum_projected_used_min_ctx = 0;
cparams->n_ctx = n_ctx_min_total;
dmds_t dmds_min_ctx = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
add_extra_memory(dmds_min_ctx);
cparams->n_ctx = n_ctx_min;
const dmds_t dmds_min_ctx = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
if (nd == 0) {
sum_projected_used_min_ctx = dmds_min_ctx.back().mb.total();
} else {
@@ -422,16 +339,14 @@ static void common_params_fit_impl(
}
if (sum_used_target > sum_projected_used_min_ctx) {
// linear interpolation between minimum and maximum context size:
cparams->n_ctx += (n_ctx_max - n_ctx_min_total) * (sum_used_target - sum_projected_used_min_ctx)
cparams->n_ctx += (hp_nct - n_ctx_min) * (sum_used_target - sum_projected_used_min_ctx)
/ (sum_projected_used - sum_projected_used_min_ctx);
// round down context for CUDA backend, keep it divisible by the number of streams:
const uint32_t align = 256 * n_streams;
cparams->n_ctx = std::max(cparams->n_ctx - cparams->n_ctx % align, n_ctx_min_total);
cparams->n_ctx = std::max(cparams->n_ctx - cparams->n_ctx % 256, n_ctx_min); // round down context for CUDA backend
const int64_t bytes_per_ctx = (sum_projected_used - sum_projected_used_min_ctx) / (n_ctx_max - n_ctx_min_total);
const int64_t memory_reduction = (n_ctx_max - cparams->n_ctx) * bytes_per_ctx;
const int64_t bytes_per_ctx = (sum_projected_used - sum_projected_used_min_ctx) / (hp_nct - n_ctx_min);
const int64_t memory_reduction = (hp_nct - cparams->n_ctx) * bytes_per_ctx;
LOG_TRC("%s: context size reduced from %" PRIu32 " to %" PRIu32 " -> need %" PRId64 " MiB less memory in total\n",
__func__, n_ctx_max, cparams->n_ctx, memory_reduction/MiB);
__func__, hp_nct, cparams->n_ctx, memory_reduction/MiB);
if (nd <= 1) {
LOG_TRC("%s: entire model can be fit by reducing context\n", __func__);
return;
@@ -440,14 +355,14 @@ static void common_params_fit_impl(
} else {
const int64_t memory_reduction = sum_projected_used - sum_projected_used_min_ctx;
LOG_TRC("%s: context size reduced from %" PRIu32 " to %" PRIu32 " -> need %" PRId64 " MiB less memory in total\n",
__func__, n_ctx_max, cparams->n_ctx, memory_reduction/MiB);
__func__, hp_nct, cparams->n_ctx, memory_reduction/MiB);
}
} else {
if (n_ctx_min == UINT32_MAX) {
LOG_TRC("%s: user has requested full context size of %" PRIu32 " -> no change\n", __func__, n_ctx_max);
LOG_TRC("%s: user has requested full context size of %" PRIu32 " -> no change\n", __func__, hp_nct);
} else {
LOG_TRC("%s: default model context size is %" PRIu32 " which is <= the min. context size of %" PRIu32 " -> no change\n",
__func__, n_ctx_max, n_ctx_min_total);
__func__, hp_nct, n_ctx_min);
}
}
} else {
@@ -592,9 +507,8 @@ static void common_params_fit_impl(
llama_model_params mparams_copy = *mparams;
set_ngl_tensor_split_tbo(ngl_per_device, overflow_bufts, mparams_copy);
dmds_t dmd_nl = common_get_device_memory_data_impl(
const dmds_t dmd_nl = common_get_device_memory_data_impl(
path_model, &mparams_copy, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
add_extra_memory(dmd_nl);
LOG_TRC("%s: memory for test allocation by device:\n", func_name);
for (size_t id = 0; id < nd; id++) {
@@ -621,9 +535,8 @@ static void common_params_fit_impl(
mparams->tensor_buft_overrides = tensor_buft_overrides;
LOG_TRC("%s: getting device memory data with all MoE tensors moved to system memory:\n", __func__);
dmds_t dmds_cpu_moe = common_get_device_memory_data_impl(
const dmds_t dmds_cpu_moe = common_get_device_memory_data_impl(
path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
add_extra_memory(dmds_cpu_moe);
for (size_t id = 0; id < nd; id++) {
global_surplus_cpu_moe += dmds_cpu_moe[id].free;
@@ -883,12 +796,11 @@ enum common_params_fit_status common_fit_params(
llama_model_tensor_buft_override * tensor_buft_overrides,
size_t * margins,
uint32_t n_ctx_min,
const common_fit_extra_model * extra,
ggml_log_level log_level) {
const int64_t t0_us = llama_time_us();
common_params_fit_status status = COMMON_PARAMS_FIT_STATUS_SUCCESS;
try {
common_params_fit_impl(path_model, mparams, cparams, tensor_split, tensor_buft_overrides, margins, n_ctx_min, extra, log_level);
common_params_fit_impl(path_model, mparams, cparams, tensor_split, tensor_buft_overrides, margins, n_ctx_min, log_level);
LOG_TRC("%s: successfully fit params to free device memory\n", __func__);
} catch (const common_params_fit_exception & e) {
LOG_WRN("%s: failed to fit params to free device memory: %s\n", __func__, e.what());
-11
View File
@@ -11,16 +11,6 @@ enum common_params_fit_status {
COMMON_PARAMS_FIT_STATUS_ERROR = 2, // a hard error occurred, e.g. because no model could be found at the specified path
};
// a second model that shares the devices of the main model, e.g. a draft model
// - its context follows the context of the main model, so its memory is measured again whenever that context changes
// - shares_model tells the fit that the weights are already counted in the main model, as for an MTP context
struct common_fit_extra_model {
const char * path_model;
llama_model_params * mparams;
llama_context_params * cparams;
bool shares_model;
};
// fits mparams and cparams to free device memory (assumes system memory is unlimited)
// - returns true if the parameters could be successfully modified to fit device memory
// - this function is NOT thread safe because it modifies the global llama logger state
@@ -34,7 +24,6 @@ common_params_fit_status common_fit_params(
llama_model_tensor_buft_override * tensor_buft_overrides, // writable buffer for overrides, needs at least llama_max_tensor_buft_overrides elements
size_t * margins, // margins of memory to leave per device in bytes
uint32_t n_ctx_min, // minimum context size to set when trying to reduce memory use
const common_fit_extra_model * extra, // model to fit alongside the main one, nullptr if there is none
ggml_log_level log_level); // minimum log level to print during fitting, lower levels go to debug log
// print estimated memory to stdout
-32
View File
@@ -117,7 +117,6 @@ caps caps_get(jinja::program & prog) {
JJ_DEBUG("%s\n", ">>> Running capability check: typed content");
bool checks_for_string = false;
static const std::string content_marker = "STRING_MARKER";
// case: typed content support
@@ -137,10 +136,6 @@ caps caps_get(jinja::program & prog) {
[&](context &, bool success, value & messages, value &, const std::string & rendered) {
auto & content = messages->at(0)->at("content");
caps_print_stats(content, "messages[0].content");
if (has_op(content, "test_is_string")) {
// checked if content is string
checks_for_string = true;
}
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
if (used_as_array) {
// accessed as an array
@@ -156,33 +151,6 @@ caps caps_get(jinja::program & prog) {
}
);
if (checks_for_string) {
caps_try_execute(
prog,
[&]() {
// messages
return json::array({
{
{"role", "user"},
{"content", json::array({
})}
}
});
},
nullptr, // ctx_fn
nullptr, // tools_fn
[&](context &, bool success, value & messages, value &, const std::string &) {
auto & content = messages->at(0)->at("content");
caps_print_stats(content, "messages[0].content");
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
if (used_as_array && success) {
// accessed as an array
result.supports_typed_content = true;
}
}
);
}
JJ_DEBUG("%s\n", ">>> Running capability check: system prompt");
// case: system prompt support
+2 -8
View File
@@ -412,18 +412,12 @@ value test_expression::execute_impl(context & ctx) {
throw std::runtime_error("Invalid test expression");
}
const std::string test_name = "test_is_" + test_id;
auto it = builtins.find(test_name);
JJ_DEBUG("Test expression %s '%s' %s (using function '%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_name.c_str());
auto it = builtins.find("test_is_" + test_id);
JJ_DEBUG("Test expression %s '%s' %s (using function 'test_is_%s')", operand->type().c_str(), test_id.c_str(), negate ? "(negate)" : "", test_id.c_str());
if (it == builtins.end()) {
throw std::runtime_error("Unknown test '" + test_id + "'");
}
if (ctx.is_get_stats) {
value_t::stats_t::mark_used(input);
input->stats.ops.insert(test_name);
}
auto res = it->second(args);
if (negate) {
-4
View File
@@ -748,10 +748,6 @@ private:
optional_props.push_back("*");
}
if (required_props.empty() && optional_props.empty()) {
return "\"{\" space \"}\"";
}
std::string rule = "\"{\" space ";
for (size_t i = 0; i < required_props.size(); i++) {
if (i > 0) {
+26 -22
View File
@@ -78,21 +78,19 @@ common_json_value::common_json_value(const common_json & val) :
common_json_value::common_json_value(common_json && val) :
type(VAL_JSON), val_json(std::make_shared<common_json>(std::move(val))) {}
// the ctors and get<T>() below are explicit specializations, giving strong symbols
// an explicit instantiation is a weak symbol, dropped by some LTO builds (clang-cl)
template <typename T>
static std::shared_ptr<common_json> set_json(const std::set<T> & vals) {
common_json_value::common_json_value(const std::set<T> & vals) : type(VAL_JSON) {
common_json out = common_json::array();
for (const auto & val : vals) {
out.push_back(val);
}
return std::make_shared<common_json>(std::move(out));
val_json = std::make_shared<common_json>(std::move(out));
}
// a set value is usable only for the types below
#define COMMON_JSON_SET(...) template <> common_json_value::common_json_value(const std::set<__VA_ARGS__> & vals) : type(VAL_JSON), val_json(set_json(vals)) {}
#define COMMON_JSON_SET(...) template common_json_value::common_json_value(const std::set<__VA_ARGS__> &);
COMMON_JSON_SET(int)
COMMON_JSON_SET(std::string)
@@ -100,45 +98,56 @@ COMMON_JSON_SET(std::string)
#undef COMMON_JSON_SET
template <typename T>
static std::shared_ptr<common_json> map_json(const T & vals) {
common_json_value::common_json_value(const std::map<std::string, T> & vals) : type(VAL_JSON) {
common_json out = common_json::object();
for (const auto & val : vals) {
out.set({ val.first, val.second });
}
return std::make_shared<common_json>(std::move(out));
val_json = std::make_shared<common_json>(std::move(out));
}
// a map value is usable only for the types below
#define COMMON_JSON_MAP(...) template <> common_json_value::common_json_value(const std::map<std::string, __VA_ARGS__> & vals) : type(VAL_JSON), val_json(map_json(vals)) {}
#define COMMON_JSON_MAP(...) template common_json_value::common_json_value(const std::map<std::string, __VA_ARGS__> &);
COMMON_JSON_MAP(bool)
COMMON_JSON_MAP(std::string)
#undef COMMON_JSON_MAP
template <typename T>
common_json_value::common_json_value(const std::unordered_map<std::string, T> & vals) : type(VAL_JSON) {
common_json out = common_json::object();
for (const auto & val : vals) {
out.set({ val.first, val.second });
}
val_json = std::make_shared<common_json>(std::move(out));
}
// an unordered map value is usable only for the types below
#define COMMON_JSON_UMAP(...) template <> common_json_value::common_json_value(const std::unordered_map<std::string, __VA_ARGS__> & vals) : type(VAL_JSON), val_json(map_json(vals)) {}
#define COMMON_JSON_UMAP(...) template common_json_value::common_json_value(const std::unordered_map<std::string, __VA_ARGS__> &);
COMMON_JSON_UMAP(size_t)
#undef COMMON_JSON_UMAP
template <typename T>
static std::shared_ptr<common_json> vec_json(const std::vector<T> & vals) {
common_json_value::common_json_value(const std::vector<T> & vals) : type(VAL_JSON) {
common_json out = common_json::array();
for (const auto & val : vals) {
out.push_back(val);
}
return std::make_shared<common_json>(std::move(out));
val_json = std::make_shared<common_json>(std::move(out));
}
// a vector value is usable only for the types below
// note: std::vector<bool> is not here, its proxy reference does not convert
#define COMMON_JSON_VEC(...) template <> common_json_value::common_json_value(const std::vector<__VA_ARGS__> & vals) : type(VAL_JSON), val_json(vec_json(vals)) {}
#define COMMON_JSON_VEC(...) template common_json_value::common_json_value(const std::vector<__VA_ARGS__> &);
COMMON_JSON_VEC(int)
COMMON_JSON_VEC(unsigned char)
@@ -395,6 +404,10 @@ common_json::items_view common_json::items() const {
return items_view(const_cast<common_json *>(this), size());
}
template <typename T> T common_json::get() const {
return guard([&] { return as_json(this).get<T>(); });
}
// the backing library cannot build a common_json, so this one is just a copy
template <> common_json common_json::get<common_json>() const {
return *this;
@@ -402,7 +415,7 @@ template <> common_json common_json::get<common_json>() const {
// get<T>() is usable only for the types below
#define COMMON_JSON_GET(...) template <> __VA_ARGS__ common_json::get<__VA_ARGS__>() const { return guard([&] { return as_json(this).get<__VA_ARGS__>(); }); }
#define COMMON_JSON_GET(...) template __VA_ARGS__ common_json::get<__VA_ARGS__>() const;
COMMON_JSON_GET(bool)
COMMON_JSON_GET(int)
@@ -422,12 +435,3 @@ COMMON_JSON_GET(std::vector<size_t>)
COMMON_JSON_GET(std::unordered_map<std::string, size_t>)
#undef COMMON_JSON_GET
// must stay below the get<std::string> specialization
common_json::operator std::string() const {
return get<std::string>();
}
std::string common_json::value(const std::string & key, const char * def) const {
return contains(key) ? at(key).get<std::string>() : std::string(def);
}
+4 -2
View File
@@ -221,14 +221,16 @@ class common_json {
// implicit get<T>() for plain values, so they can be assigned to their C++ type directly
// note: kept to this short list on purpose, a wider one makes j["key"] ambiguous
// note: a numeric one would make "str = json;" ambiguous, a number converts to char too
operator std::string() const;
operator std::string() const { return get<std::string>(); }
template <typename T>
T value(const std::string & key, T def) const {
return contains(key) ? at(key).get<T>() : def;
}
std::string value(const std::string & key, const char * def) const;
std::string value(const std::string & key, const char * def) const {
return contains(key) ? at(key).get<std::string>() : std::string(def);
}
// a JSON default needs no get<T>(), it is already the right type
common_json value(const std::string & key, const common_json & def) const {
+3 -41
View File
@@ -1,6 +1,5 @@
#include "common.h"
#include "log.h"
#include "json.h"
#include <chrono>
#include <condition_variable>
@@ -67,17 +66,6 @@ static const char* g_col[] = {
"",
};
static const char * level_str(enum ggml_log_level level) {
switch (level) {
case GGML_LOG_LEVEL_DEBUG: return "debug";
case GGML_LOG_LEVEL_INFO: return "info";
case GGML_LOG_LEVEL_WARN: return "warn";
case GGML_LOG_LEVEL_ERROR: return "error";
case GGML_LOG_LEVEL_CONT: return "cont";
default: return "none";
}
}
struct common_log_entry {
enum ggml_log_level level {GGML_LOG_LEVEL_INFO};
@@ -86,7 +74,6 @@ struct common_log_entry {
int64_t timestamp { 0 };
bool is_end { false }; // signals the worker thread to stop
bool prefix { false };
bool jsonl { false };
common_log_entry(size_t size = 256) : msg(size) { }
@@ -101,23 +88,11 @@ struct common_log_entry {
fcur = stdout;
if (level != GGML_LOG_LEVEL_NONE && !jsonl) {
if (level != GGML_LOG_LEVEL_NONE) {
fcur = stderr;
}
}
if (jsonl) {
common_json obj = {
{"type", "log"},
{"time", timestamp},
{"level", level_str(level)},
{"msg", msg.data()},
};
fprintf(fcur, "%s\n", obj.dump_safe().c_str());
fflush(fcur);
return;
}
if (level != GGML_LOG_LEVEL_NONE && level != GGML_LOG_LEVEL_CONT && prefix) {
if (timestamp) {
// [M.s.ms.us]
@@ -156,7 +131,6 @@ struct common_log {
file = nullptr;
prefix = false;
timestamps = false;
jsonl = false;
running = false;
t_start = t_us();
@@ -184,7 +158,6 @@ private:
bool prefix;
bool timestamps;
bool jsonl;
bool running;
int64_t t_start;
@@ -273,7 +246,6 @@ public:
entry.is_end = false;
entry.level = level;
entry.prefix = prefix;
entry.jsonl = jsonl;
entry.timestamp = 0;
if (timestamps) {
entry.timestamp = t_us() - t_start;
@@ -388,12 +360,6 @@ public:
this->timestamps = timestamps;
}
void set_jsonl(bool jsonl) {
std::lock_guard<std::mutex> lock(mtx);
this->jsonl = jsonl;
}
};
//
@@ -467,16 +433,12 @@ void common_log_set_timestamps(struct common_log * log, bool timestamps) {
log->set_timestamps(timestamps);
}
void common_log_set_jsonl(struct common_log * log, bool jsonl) {
log->set_jsonl(jsonl);
}
void common_log_flush(struct common_log * log) {
log->pause();
log->resume();
}
int common_log_get_verbosity(enum ggml_log_level level) {
static int common_get_verbosity(enum ggml_log_level level) {
switch (level) {
case GGML_LOG_LEVEL_DEBUG: return LOG_LEVEL_DEBUG;
case GGML_LOG_LEVEL_INFO: return LOG_LEVEL_TRACE;
@@ -490,7 +452,7 @@ int common_log_get_verbosity(enum ggml_log_level level) {
}
void common_log_default_callback(enum ggml_log_level level, const char * text, void * /*user_data*/) {
auto verbosity = common_log_get_verbosity(level);
auto verbosity = common_get_verbosity(level);
if (verbosity <= common_log_verbosity_thold) {
common_log_add(common_log_main(), level, "%s", text);
}
-3
View File
@@ -43,8 +43,6 @@ int common_log_get_verbosity_thold(void);
void common_log_set_verbosity_thold(int verbosity); // not thread-safe
int common_log_get_verbosity(enum ggml_log_level level);
void common_log_default_callback(enum ggml_log_level level, const char * text, void * user_data);
// the common_log uses an internal worker thread to print/write log messages
@@ -91,7 +89,6 @@ void common_log_set_file (struct common_log * log, const char * file); // n
void common_log_set_colors (struct common_log * log, log_colors colors); // not thread-safe
void common_log_set_prefix (struct common_log * log, bool prefix); // whether to output prefix to each log
void common_log_set_timestamps(struct common_log * log, bool timestamps); // whether to output timestamps in the prefix
void common_log_set_jsonl (struct common_log * log, bool jsonl); // print each log as a JSON object on one line, not thread-safe
void common_log_flush (struct common_log * log); // flush all pending log messages
// helper macros for logging
-150
View File
@@ -1,150 +0,0 @@
#include "parsers.h"
// Cohere2 MoE (a.k.a. "North Code") parser.
//
// The assistant turn is fully marker-wrapped:
// <|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>
// <|START_THINKING|>{reasoning}<|END_THINKING|>
// then EITHER content: <|START_TEXT|>{content}<|END_TEXT|>
// OR tool calls: <|START_ACTION|>[
// {"tool_call_id": "0", "tool_name": "f", "parameters": {...}}, ...
// ]<|END_ACTION|>
// <|END_OF_TURN_TOKEN|>
//
// The generation prompt forces a leading <|START_THINKING|> (when reasoning is enabled, which is
// the template default), so the model's output continues from *inside* the thinking block. The
// parser literal therefore only covers the stable <|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|> prefix
// and the reasoning rule consumes the <|START_THINKING|> ... <|END_THINKING|> markers itself,
// regardless of whether they came from the generation prompt or the generated text.
common_chat_params common_chat_params_init_cohere2moe(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
const std::string TURN_START = "<|START_OF_TURN_TOKEN|>";
const std::string TURN_END = "<|END_OF_TURN_TOKEN|>";
const std::string CHATBOT = "<|CHATBOT_TOKEN|>";
const std::string USER = "<|USER_TOKEN|>";
const std::string SYSTEM = "<|SYSTEM_TOKEN|>";
const std::string THINK_START = "<|START_THINKING|>";
const std::string THINK_END = "<|END_THINKING|>";
const std::string TEXT_START = "<|START_TEXT|>";
const std::string TEXT_END = "<|END_TEXT|>";
const std::string ACTION_START = "<|START_ACTION|>";
const std::string ACTION_END = "<|END_ACTION|>";
const std::string RESULT_START = "<|START_TOOL_RESULT|>";
const std::string RESULT_END = "<|END_TOOL_RESULT|>";
// Stable prefix of the generation prompt that precedes the (forced) <|START_THINKING|> marker.
const std::string GEN_PREFIX = TURN_START + CHATBOT;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = {THINK_END};
data.preserved_tokens = {
TURN_START, TURN_END, CHATBOT, USER, SYSTEM,
THINK_START, THINK_END,
TEXT_START, TEXT_END,
ACTION_START, ACTION_END,
RESULT_START, RESULT_END,
};
// Declare per-role message delimiters. Tool results are rendered with the
// system token followed by <|START_TOOL_RESULT|>, so the "tool" delimiter must be listed before
// the plain "system" one (it is a strict superset, and the role split tries delimiters in order).
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, GEN_PREFIX },
{ COMMON_CHAT_ROLE_USER, TURN_START + USER },
{ COMMON_CHAT_ROLE_TOOL, TURN_START + SYSTEM + RESULT_START },
{ COMMON_CHAT_ROLE_SYSTEM, TURN_START + SYSTEM },
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = GEN_PREFIX + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + TEXT_START + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal(GEN_PREFIX);
auto end = p.end();
// The thinking block is always present (the generation prompt forces <|START_THINKING|>).
// When extracting reasoning, capture its body; otherwise keep the whole block (markers
// included) inline as content, matching reasoning_format=NONE conventions.
common_peg_parser reasoning = p.eps();
if (extract_reasoning) {
reasoning = p.optional(p.literal(THINK_START) +
p.reasoning(p.until_one_of({ THINK_END, TEXT_START, ACTION_START })) +
p.optional(p.literal(THINK_END)));
} else {
reasoning = p.optional(p.content(p.literal(THINK_START) +
p.until_one_of({ THINK_END, TEXT_START, ACTION_START }) +
p.optional(p.literal(THINK_END))));
}
auto text_content = has_response_format
? p.literal(TEXT_START) +
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) +
p.optional(p.literal(TEXT_END))
: p.literal(TEXT_START) + p.content(p.until(TEXT_END)) + p.optional(p.literal(TEXT_END));
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return generation_prompt + reasoning + text_content + p.optional(p.literal(TURN_END)) + end;
}
auto require_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;
// <|START_ACTION|>[ {"tool_call_id": "0", "tool_name": "f", "parameters": {...}}, ... ]<|END_ACTION|>
auto tool_calls = p.standard_json_tools(ACTION_START, ACTION_END, inputs.tools, inputs.parallel_tool_calls,
/* force_tool_calls = */ true,
/* name_key = */ "tool_name",
/* args_key = */ "parameters",
/* array_wrapped = */ true,
/* function_is_key = */ false,
/* call_id_key = */ "",
/* gen_call_id_key = */ "tool_call_id",
/* parameters_order = */ { "tool_call_id", "tool_name", "parameters" });
// Content and tool calls are mutually exclusive in this format.
common_peg_parser body = require_tools ? tool_calls : p.choice({ tool_calls, text_content });
return generation_prompt + reasoning + body + p.optional(p.literal(TURN_END)) + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !has_response_format && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, ACTION_START }
};
}
return data;
}
-287
View File
@@ -1,287 +0,0 @@
#include "parsers.h"
// The DeepSeek V4 reference implementation renders consecutive tool results into a single
// user block, ordered by the tool call order of the preceding assistant message (matched
// by tool call id) rather than by the order they appear in the conversation.
static json deepseek_v4_sort_tool_results(const json & messages) {
json adjusted = messages;
std::map<std::string, size_t> call_order;
for (size_t i = 0; i < adjusted.size();) {
const auto & msg = adjusted[i];
const auto role = msg.value("role", "");
if (role == "assistant" && msg.contains("tool_calls") &&
msg.at("tool_calls").is_array() && !msg.at("tool_calls").empty()) {
call_order.clear();
const auto & tool_calls = msg.at("tool_calls");
for (size_t idx = 0; idx < tool_calls.size(); idx++) {
auto id = tool_calls[idx].value("id", "");
if (!id.empty()) {
call_order[id] = idx;
}
}
i++;
continue;
}
if (role != "user" && role != "tool") {
i++;
continue;
}
// collect a maximal run of user/tool messages - they render into one user block
std::vector<size_t> tool_positions;
size_t run_end = i;
for (; run_end < adjusted.size(); run_end++) {
const auto r = adjusted[run_end].value("role", "");
if (r == "tool") {
tool_positions.push_back(run_end);
} else if (r != "user") {
break;
}
}
if (tool_positions.size() > 1 && !call_order.empty()) {
std::vector<json> results;
results.reserve(tool_positions.size());
for (auto pos : tool_positions) {
results.push_back(adjusted[pos]);
}
std::stable_sort(results.begin(), results.end(), [&](const json & a, const json & b) {
const auto order = [&](const json & m) {
auto it = call_order.find(m.value("tool_call_id", ""));
return it == call_order.end() ? (size_t) 0 : it->second;
};
return order(a) < order(b);
});
for (size_t k = 0; k < tool_positions.size(); k++) {
adjusted[tool_positions[k]] = std::move(results[k]);
}
}
i = run_end;
}
return adjusted;
}
common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
// V4 uses the same DSML markup as V3.2, but names the tool call block "tool_calls"
// instead of "function_calls", renders tool results in tool call order and its
// non-thinking generation prompt ends with a bare </think> instead of an empty
// <think></think> pair.
const bool is_v4 = tmpl.source().find("function_calls") == std::string::npos;
std::optional<json> adjusted_messages;
if (is_v4) {
adjusted_messages = deepseek_v4_sort_tool_results(inputs.messages);
}
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
std::optional<json> additional_context;
if (is_v4 && has_response_format) {
additional_context = json{ { "response_format", inputs.json_schema } };
}
const std::string DSML = "|DSML|";
const std::string THINK_START = "<think>";
const std::string THINK_END = "</think>";
const std::string TC_BLOCK = is_v4 ? "tool_calls" : "function_calls";
const std::string FC_START = "<" + DSML + TC_BLOCK + ">";
const std::string FC_END = "</" + DSML + TC_BLOCK + ">";
const std::string INVOKE_START = "<" + DSML + "invoke";
const std::string INVOKE_END = "</" + DSML + "invoke>";
const std::string PARAM_START = "<" + DSML + "parameter";
const std::string PARAM_END = "</" + DSML + "parameter>";
const std::string GEN_PROMPT = "<|Assistant|>";
const std::string TC_SEPARATOR = "\n\n";
data.prompt = common_chat_template_direct_apply_impl(
tmpl, inputs, adjusted_messages, std::nullopt, additional_context);
data.generation_prompt = common_chat_template_generation_prompt_impl(
tmpl, inputs, adjusted_messages, std::nullopt, additional_context);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = {THINK_END, FC_START};
data.preserved_tokens = {
DSML,
THINK_START,
THINK_END,
};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
if (is_v4 && msg.reasoning_content.empty()) {
data.generation_prompt = GEN_PROMPT + THINK_END;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += msg.render_content();
}
} else {
data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + msg.render_content();
}
}
data.prompt += data.generation_prompt;
}
bool require_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;
bool has_tool_calls = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal(GEN_PROMPT);
auto end = p.end();
// build tool call section first since we might need it in reasoning
auto tool_choice = p.choice();
if (has_tool_calls) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
const auto & props = params.contains("properties") ? params.at("properties") : json::object();
std::set<std::string> required;
if (params.contains("required")) {
required = params.at("required").get<std::set<std::string>>();
}
auto schema_info = common_schema_info();
schema_info.resolve_refs(params);
std::vector<common_peg_parser> required_parsers;
std::vector<common_peg_parser> optional_parsers;
for (const auto & [param_name, param_schema] : props.items()) {
bool is_required = required.find(param_name) != required.end();
bool is_string = schema_info.resolves_to_string(param_schema);
auto arg = p.tool_arg(
p.tool_arg_open(p.literal(PARAM_START + " name=\"") + p.tool_arg_name(p.literal(param_name)) +
p.literal("\" string=\"" + std::string(is_string ? "true" : "false") + "\">")) +
(is_string ?
p.tool_arg_string_value(p.until(PARAM_END)) :
p.tool_arg_json_value(p.schema(p.json(), "tool-" + name + "-arg-" + param_name + "-schema",
param_schema, false))) +
p.tool_arg_close(p.literal(PARAM_END)));
auto named_arg = p.rule("tool-" + name + "-arg-" + param_name, arg);
if (is_required) {
required_parsers.push_back(named_arg);
} else {
optional_parsers.push_back(named_arg);
}
}
common_peg_parser args_seq = p.eps();
for (size_t i = 0; i < required_parsers.size(); i++) {
if (i > 0) {
args_seq = args_seq + p.space();
}
args_seq = args_seq + required_parsers[i];
}
if (!optional_parsers.empty()) {
common_peg_parser any_opt = p.choice();
for (const auto & opt : optional_parsers) {
any_opt |= opt;
}
args_seq = args_seq + p.repeat(p.space() + any_opt, 0, -1);
}
common_peg_parser invoke_body = args_seq;
auto func_parser = p.tool(p.tool_open(p.literal(INVOKE_START + " name=\"") +
p.tool_name(p.literal(name)) + p.literal("\">\n")) +
invoke_body + p.space() + p.tool_close(p.literal(INVOKE_END)));
tool_choice |= p.rule("tool-" + name, func_parser);
});
}
common_peg_parser tool_calls = p.eps();
if (inputs.parallel_tool_calls) {
tool_calls = p.trigger_rule("tool-call",
p.literal(FC_START) + p.space() + tool_choice +
p.zero_or_more(p.space() + tool_choice) + p.space() + p.literal(FC_END));
} else {
tool_calls = p.trigger_rule("tool-call",
p.literal(FC_START) + p.space() + tool_choice + p.space() + p.literal(FC_END));
}
auto reasoning = p.eps();
auto reasoning_with_tc = p.eps();
auto obligatory_tool_calls = tool_calls;
bool allow_reasoning_with_tc = false;
if (!require_tools) {
tool_calls = p.optional(tool_calls);
}
if (extract_reasoning && inputs.enable_thinking) {
reasoning = p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
reasoning_with_tc = THINK_START +
p.reasoning(p.until_one_of({ TC_SEPARATOR + FC_START, FC_START, THINK_END })) +
p.space() + obligatory_tool_calls;
allow_reasoning_with_tc = true;
} else if (extract_reasoning) {
// Thinking disabled but reasoning extraction requested: the generation prompt
// contains an empty <think></think> pair (V3.2) or a bare </think> (V4) that
// must still be consumed.
reasoning = is_v4
? p.optional(p.literal(THINK_END))
: p.optional(p.literal(THINK_START) + p.until(THINK_END) + p.literal(THINK_END));
}
if (has_response_format) {
auto response_format = p.rule("response-format",
p.literal("```json") + p.space() +
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) +
p.space() + p.literal("```"));
return generation_prompt + reasoning + response_format + end;
}
if (!has_tool_calls) {
return generation_prompt + reasoning + p.content(p.rest()) + end;
}
auto content_before_tools = p.negate(p.literal(THINK_START)) +
p.content(p.until_one_of({ TC_SEPARATOR + FC_START, FC_START })) +
p.space();
return allow_reasoning_with_tc ? generation_prompt + (reasoning_with_tc | (reasoning + content_before_tools + tool_calls)) + end :
generation_prompt + reasoning + content_before_tools + tool_calls + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = has_tools && !require_tools;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, FC_START },
};
}
return data;
}
-101
View File
@@ -1,101 +0,0 @@
#include "parsers.h"
// Functionary v3.2 - uses recipient-based format: >>>recipient\n{content}
common_chat_params common_chat_params_init_functionary_v3_2(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.preserved_tokens = {
">>>all",
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "<|start_header_id|>assistant<|end_header_id|>\n\n>>>all\n" + msg.render_content();
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
// Functionary v3.2 format:
// - Normal content: >>>all\n{content}
// - Tool calls: >>>function_name\n{json_args}
// Generation prompt ends with ">>>" so model outputs recipient immediately
// Build content parser for >>>all\n{content}
// When tools are present, content stops before the next ">>>" (tool call)
// When no tools, content goes until end
auto content_until_tool = p.literal("all\n") + p.content(p.until(">>>"));
auto content_until_end = p.literal("all\n") + p.content(p.rest());
auto generation_prompt = p.literal("<|start_header_id|>assistant<|end_header_id|>\n\n>>>");
// If no tools or tool_choice is NONE, just parse content
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
// When no tools, just match the prefix and capture everything after
return generation_prompt + content_until_end + p.end();
}
// Build tool call parsers for each available function
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & schema = function.at("parameters");
// Tool format: >>>function_name\n{json_args}
auto tool_parser = p.tool(
p.tool_open(p.tool_name(p.literal(name)) + p.literal("\n")) +
p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema))
);
tool_choice |= p.rule("tool-" + name, tool_parser);
});
auto content_only = content_until_end;
auto tools_only = p.trigger_rule("tools", p.one_or_more(tool_choice));
auto content_and_tools = content_until_tool + tools_only;
auto ret = p.eps();
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
if (inputs.parallel_tool_calls) {
ret = p.choice({ content_and_tools, tools_only }) + p.end();
} else {
ret = p.choice({ content_until_tool + tool_choice, tools_only }) + p.end();
}
} else if (inputs.parallel_tool_calls) {
ret = p.choice({ content_and_tools, content_only, tools_only }) + p.end();
} else {
auto content_and_tool = content_until_tool + tool_choice;
ret = p.choice({ content_and_tool, content_only, tool_choice }) + p.end();
}
return generation_prompt + ret;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
parser.build_grammar(builder, data.grammar_lazy);
});
// Grammar trigger for when the model starts outputting a tool call
// (after the initial ">>>" in the generation prompt but recipient other than "all")
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, ">>>(?!all)" }
};
}
return data;
}
-312
View File
@@ -1,312 +0,0 @@
#include "parsers.h"
namespace workaround {
// Gemma4 uses a custom tool_responses field instead of role:tool messages.
//
// This will transform a sequence of messages:
// assistant(tool_call+) -> tool+ -> assistant(content)
//
// Into a single assistant message containing a tool_responses field:
// assistant(content + tool_call + tool_responses)
//
// This is necessary for the Gemma4 chat template to properly format the prompt.
// See https://ai.google.dev/gemma/docs/core/prompt-formatting-gemma4
struct gemma4_model_turn_builder {
json & messages;
size_t pos;
json tool_calls = json::array();
json tool_responses = json::array();
json content;
json reasoning_content;
gemma4_model_turn_builder(json & msgs, size_t pos) : messages(msgs), pos(pos) {}
void collect() {
// Collect the first assistant message
auto & msg = messages[pos];
if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
// According to the prompt formatting guide, we need to preserve reasoning_content
// between function calls. The current chat templates do not support this, but we will do it anyway.
reasoning_content = msg.at("reasoning_content");
}
for (auto & tc : msg.at("tool_calls")) {
tool_calls.push_back(tc);
}
pos++;
// Collect tool call results
while (pos < messages.size() && messages[pos].value("role", "") == "tool") {
collect_result(messages[pos]);
pos++;
}
// Check if the next assistant message is the final message
if (pos < messages.size() && messages[pos].value("role", "") == "assistant") {
auto & next = messages[pos];
if (!has_tool_calls(next) && has_content(next)) {
content = next.at("content");
pos++;
}
}
}
void collect_result(const json & curr) {
json response;
if (curr.contains("content")) {
const auto & content = curr.at("content");
if (content.is_string()) {
// Try to parse the content as JSON; fall back to raw string
try {
response = json::parse(content.get<std::string>());
} catch (...) {
response = content;
}
} else {
response = content;
}
}
std::string name;
// Match name with corresponding tool call
size_t idx = tool_responses.size();
if (idx < tool_calls.size()) {
auto & tc = tool_calls[idx];
if (tc.contains("function")) {
name = tc.at("function").value("name", "");
}
}
// Fallback to the tool call id
if (name.empty()) {
name = curr.value("tool_call_id", "");
}
tool_responses.push_back({{"name", name}, {"response", response}});
}
json build() {
collect();
json msg = {
{"role", "assistant"},
{"tool_calls", tool_calls},
};
if (!tool_responses.empty()) {
msg["tool_responses"] = tool_responses;
}
if (!content.is_null()) {
msg["content"] = content;
}
if (!reasoning_content.is_null()) {
msg["reasoning_content"] = reasoning_content;
}
return msg;
}
static bool has_content(const json & msg) {
if (!msg.contains("content") || msg.at("content").is_null()) {
return false;
}
const auto & content = msg.at("content");
if (content.is_string() && !content.get<std::string>().empty()) {
return true;
}
if (content.is_array() && !content.empty()) {
return true;
}
return false;
}
static bool has_tool_calls(const json & msg) {
return msg.contains("tool_calls") && msg.at("tool_calls").is_array() && !msg.at("tool_calls").empty();
}
};
void convert_tool_responses_gemma4(json & messages) {
json result = json::array();
size_t i = 0;
while (i < messages.size()) {
auto & msg = messages[i];
if (msg.value("role", "") != "assistant" || !msg.contains("tool_calls") ||
!msg.at("tool_calls").is_array() || msg.at("tool_calls").empty()) {
result.push_back(msg);
i++;
continue;
}
gemma4_model_turn_builder builder(messages, i);
result.push_back(builder.build());
i = builder.pos;
}
messages = result;
}
}
common_chat_params common_chat_params_init_gemma4(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
if (inputs.add_generation_prompt && string_ends_with(data.prompt, "<turn|>\n")) {
// This may happen if the model generates content + tool_call, the
// template does not add the model's next turn and confuses the model
// from emitting its proper reasoning token sequence.
data.generation_prompt = "<|turn>model\n";
data.prompt += data.generation_prompt;
}
data.message_delimiters = {
{ COMMON_CHAT_ROLE_USER, "<|turn>user" },
{ COMMON_CHAT_ROLE_ASSISTANT, "<|turn>model" },
};
data.format = COMMON_CHAT_FORMAT_PEG_GEMMA4;
data.supports_thinking = true;
data.thinking_start_tag = "<|channel>thought";
data.thinking_end_tags = {"<channel|>"};
data.preserved_tokens = {
"<|channel>",
"<channel|>",
"<|tool_call>",
"<tool_call|>",
"<|turn>",
};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = string_ends_with(data.prompt, "<turn|>\n") ? "<|turn>model\n" : "";
data.generation_prompt += "<|channel>thought\n" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "<channel|>" + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto start = p.rule("start", p.optional(p.literal("<|turn>model\n")));
if (extract_reasoning) {
p.rule("thought", p.literal("<|channel>thought") + p.space() + p.reasoning(p.until("<channel|>")) + p.literal("<channel|>"));
} else {
p.rule("thought", p.content(p.literal("<|channel>thought") + p.space() + p.until("<channel|>") + p.literal("<channel|>")));
}
auto consume_empty_channels = p.gbnf(p.zero_or_more(p.literal("<|channel>") + p.negate(p.literal("thought"))), "");
auto thought = (p.peek(p.literal("<|channel>")) + consume_empty_channels + p.ref("thought")) | p.negate(p.literal("<|channel>"));
if (has_response_format) {
auto response_format = p.literal("```json") <<
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) <<
p.literal("```");
return start + p.optional(thought) + response_format;
}
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
// Gemma4 tool calling syntax
// Rules should match traversal logic in gemma4_to_json()
p.rule("gemma4-string-content", p.until("<|\"|>"));
p.rule("gemma4-string", p.literal("<|\"|>") + p.ref("gemma4-string-content") + p.literal("<|\"|>"));
p.rule("gemma4-bool", p.json_bool());
p.rule("gemma4-null", p.json_null());
p.rule("gemma4-number", p.json_number());
p.rule("gemma4-dict-key", p.rule("gemma4-dict-key-name", p.chars("[^:}]", 1, -1)) + p.literal(":"));
p.rule("gemma4-dict-kv", p.ref("gemma4-dict-key") + p.space() + p.ref("gemma4-value"));
p.rule("gemma4-dict", [&]() {
auto ws = p.space();
auto member = p.ref("gemma4-dict-kv");
auto members = p.sequence({member, p.zero_or_more(p.sequence({p.literal(","), ws, member}))});
return p.sequence({
p.literal("{"), ws,
p.choice({p.literal("}"), p.sequence({members, ws, p.literal("}")})})
});
});
p.rule("gemma4-array", [&]() {
auto ws = p.space();
auto value = p.ref("gemma4-value");
auto elements = p.sequence({value, p.zero_or_more(p.sequence({p.literal(","), ws, value}))});
return p.sequence({
p.literal("["), ws,
p.choice({p.literal("]"), p.sequence({elements, ws, p.literal("]")})})
});
});
p.rule("gemma4-value", [&]() {
return p.choice({
p.ref("gemma4-string"), p.ref("gemma4-dict"), p.ref("gemma4-array"),
p.ref("gemma4-number"), p.ref("gemma4-bool"), p.ref("gemma4-null")
});
});
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
// TODO @aldehir : need to extend json-schema-to-grammar to produce more than JSON rules
// const auto & params = function.at("parameters");
tool_choice |= p.rule("tool-" + name, p.tool(p.sequence({
p.tool_open(p.tool_name(p.literal(name)) + p.peek(p.literal("{"))),
p.tool_args(p.ref("gemma4-dict")),
})));
});
auto tool_call = p.trigger_rule("tool-call", p.repeat(
"<|tool_call>call:" + tool_choice + "<tool_call|>",
/* min = */ inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0,
/* max = */ inputs.parallel_tool_calls ? -1 : 1
));
auto scan_to_toolcall = p.rule("scan-to-toolcall", p.until("<|tool_call>"));
auto content = p.rule("content", p.content(p.until_one_of({"<|channel>", "<channel|>", "<|tool_call>"})));
auto message = p.rule("message", thought + content);
return start + p.zero_or_more(message) + scan_to_toolcall + tool_call;
}
// Gemma 4 may emit an extra <|channel>thought\n<channel|> at the end of the content. It may
// also emit a single trailing <channel|> token. Consume all complete reasoning blocks and
// then stop at the first unmatched <channel|> token.
auto content = p.rule("content", p.content(p.until_one_of({"<|channel>", "<channel|>"})));
auto message = p.rule("message", thought + content);
return start + p.one_or_more(message);
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<|tool_call>" },
};
}
return data;
}
-81
View File
@@ -1,81 +0,0 @@
#include "parsers.h"
common_chat_params common_chat_params_init_gigachat_v3(
const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = false;
data.preserved_tokens = {
"<|message_sep|>\n\n",
"<|role_sep|>\n",
};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "assistant<|role_sep|>\n" + msg.render_content();
data.prompt += data.generation_prompt;
}
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
const auto *tool_call_start_prefix = "<|message_sep|>\n\nfunction call<|role_sep|>\n";
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto ret = p.eps();
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
// Build a choice of all available tools
auto tool_choice = p.choice();
for (const auto & tool : inputs.tools) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & schema = function.at("parameters");
auto tool_name = p.json_member("name", "\"" + p.tool_name(p.literal(name)) + "\"");
auto tool_args = p.json_member("arguments", p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)));
auto tool_open = p.tool_open(p.literal("{") << tool_name);
tool_choice |= p.rule("tool-" + name, tool_open << "," << tool_args << "}");
}
// Define the tool call structure
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
auto max_calls = 1; // parallel toolcalls are not supported
auto tool_call = p.rule("tool-call", p.literal(tool_call_start_prefix) + tool_choice);
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(tool_call, /* min = */ min_calls, /* max = */ max_calls));
ret = p.content(p.until("<|message_sep|>\n\n")) << tool_calls;
} else {
// Content only parser
include_grammar = false;
ret = p.content(p.rest());
}
return p.literal("assistant<|role_sep|>\n") + ret;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{COMMON_GRAMMAR_TRIGGER_TYPE_WORD, tool_call_start_prefix}
};
}
return data;
}
-167
View File
@@ -1,167 +0,0 @@
#include "parsers.h"
common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
// Copy reasoning to the "thinking" field as expected by the gpt-oss template
auto adjusted_messages = json::array();
for (auto msg : inputs.messages) {
if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
msg["thinking"] = msg.at("reasoning_content");
if (msg.contains("tool_calls") && msg.at("tool_calls").is_array() && !msg.at("tool_calls").empty()) {
msg.erase("content");
}
}
adjusted_messages.push_back(msg);
}
auto prompt = common_chat_template_direct_apply_impl(tmpl, inputs, /* messages_override= */ adjusted_messages);
// Check if we need to replace the return token with end token during
// inference and without generation prompt. For more details see:
// https://github.com/ggml-org/llama.cpp/issues/15417
if (inputs.is_inference && !inputs.add_generation_prompt) {
static constexpr std::string_view return_token = "<|return|>";
static constexpr std::string_view end_token = "<|end|>";
if (size_t pos = prompt.rfind(return_token); pos != std::string::npos) {
prompt.replace(pos, return_token.length(), end_token);
}
}
data.prompt = prompt;
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs, /* messages_override= */ adjusted_messages);
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>developer" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
{ COMMON_CHAT_ROLE_TOOL, "<|start|>functions" },
};
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.thinking_start_tag = "<|channel|>analysis<|message|>";
data.thinking_end_tags = {"<|end|>"};
// These special tokens are required to parse properly, so we include them
// even if parse_tool_calls is false.
data.preserved_tokens = {
"<|channel|>", "<|constrain|>", "<|message|>", "<|start|>", "<|end|>",
};
// Adjust prompt for continuation
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "<|start|>assistant<|channel|>analysis<|message|>" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "<|end|><|start|>assistant<|channel|>final<|message|>" + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto start = p.rule("start", p.literal("<|start|>assistant"));
auto end = p.rule("end", p.literal("<|end|>"));
auto content = p.rule("message-content", p.until("<|end|>"));
auto channel = p.literal("<|channel|>") + (p.literal("commentary") | p.literal("analysis"));
auto constrain_type = p.chars("[A-Za-z0-9_-]", 1, -1);
// Occasionally, gpt-oss-20b will prefix channels with this commentary
auto stray_commentary = p.optional(p.literal("<|channel|>commentary") + p.optional(p.literal(" to=assistant")));
auto start_analysis = stray_commentary + p.literal("<|channel|>analysis<|message|>");
if (extract_reasoning) {
p.rule("analysis", start_analysis + p.reasoning(content) + end);
} else {
p.rule("analysis", p.content(start_analysis + content + end));
}
auto analysis = p.ref("analysis");
auto preamble = p.rule("preamble", p.literal("<|channel|>commentary<|message|>") + p.content(content) + end);
auto final_msg = p.rule("final", stray_commentary + p.literal("<|channel|>final<|message|>") + p.content(content));
// Consume any unsolicited tool calls, e.g. builtin functions
auto unsolicited = p.rule("unsolicited", p.atomic(p.optional(channel) + p.literal(" to=") + content + end));
auto any = p.rule("any", preamble | analysis);
if (has_response_format) {
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
auto response_format = p.rule("response-format",
p.literal("<|channel|>final") + constraint + p.literal("<|message|>") +
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)));
return p.zero_or_more(start + analysis) + start + response_format;
}
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & params = function.at("parameters");
auto func_name = p.literal(" to=functions.") + p.tool_name(p.literal(name));
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
auto args = p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", params));
// recipient in role header
// <|start|>assistant to=functions.NAME<|channel|>(commentary|analysis)[constraint]<|message|>ARGS
auto tool_in_role = p.tool(p.tool_open(func_name + channel + constraint + p.literal("<|message|>")) + args);
// recipient in channel header
// <|channel|>(commentary|analysis) to=functions.NAME[constraint]<|message|>ARGS
auto tool_in_channel = p.tool(p.tool_open(channel + func_name + constraint + p.literal("<|message|>")) + args);
tool_choice |= p.rule("tool-" + name, tool_in_role | tool_in_channel);
});
auto tool_call = p.trigger_rule("tool-call", tool_choice);
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
return p.zero_or_more(start + any) + start + tool_call;
}
return p.zero_or_more(start + any) + start + (tool_call | final_msg);
}
return p.zero_or_more(start + any) + start + (final_msg | unsolicited);
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^\\s+to$" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^<\\|channel\\|>(?:commentary|analysis)\\s+to=functions$" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>assistant(\\s+to)" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>assistant(<\\|channel\\|>(?:commentary|analysis)\\s+to)" }
};
}
return data;
}
-133
View File
@@ -1,133 +0,0 @@
#include "parsers.h"
// Kimi K2 Thinking - uses unique tool call ID format: functions.<name>:<index>
// The ID contains both the function name and an incrementing counter
common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.preserved_tokens = {
"<|tool_calls_section_begin|>",
"<|tool_calls_section_end|>",
"<|tool_call_begin|>",
"<|tool_call_argument_begin|>",
"<|tool_call_end|>",
"<think>",
"</think>",
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
const std::string SECTION_BEGIN = "<|tool_calls_section_begin|>";
const std::string SECTION_END = "<|tool_calls_section_end|>";
const std::string CALL_BEGIN = "<|tool_call_begin|>";
const std::string ARGS_BEGIN = "<|tool_call_argument_begin|>";
const std::string CALL_END = "<|tool_call_end|>";
const std::string THINK_START = "<think>";
const std::string THINK_END = "</think>";
const std::string GEN_PROMPT = "<|im_assistant|>assistant<|im_middle|>";
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = {THINK_END};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
// Kimi K2 Thinking format:
// - Reasoning: <think>{reasoning}</think>
// - Content: text after reasoning
// - Tool calls section:
// <|tool_calls_section_begin|>
// <|tool_call_begin|>functions.<name>:<index><|tool_call_argument_begin|>{json_args}<|tool_call_end|>
// ...
// <|tool_calls_section_end|>
// The ID format is: functions.<function_name>:<counter> where counter is 0, 1, 2, ...
// Tool call markers
auto end = p.end();
// Note: this model is CRAZY. It can diverge from its supposed tool calling pattern in so many ways it's not funny.
// For example, it can call tools at the end of reasoning without closing reasoning...
auto reasoning = extract_reasoning ? p.optional(THINK_START + p.reasoning(
p.until_one_of({ THINK_END, "<|tool_calls_section_begin|>", "<|tool_call_begin|>" })) +
p.optional(p.literal(THINK_END))) : p.eps();
auto generation_prompt = p.literal(GEN_PROMPT);
// Content only parser (no tools)
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return generation_prompt + reasoning + p.content(p.rest()) + end;
}
// Build tool call parsers for each available function
// The ID format is: functions.<name>:<index>
// We need to match: functions.<name>:<digits>
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & schema = function.at("parameters");
// Match: functions.<name>:<digits>
// Capture the full call id (functions.<name>:<digits>) using tool_id tag
auto tool_id = p.tool_id(p.literal("functions.") + p.tool_name(p.literal(name)) + p.literal(":") + p.chars("[0-9]", 1, -1));
auto tool_parser = p.tool(
p.tool_open(tool_id + p.literal(ARGS_BEGIN)) +
p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)) +
p.tool_close(p.optional((p.literal(CALL_END))))
);
tool_choice |= p.rule("tool-" + name, tool_parser);
});
// Tool calls section: <|tool_calls_section_begin|> tool_calls <|tool_calls_section_end|>
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
auto max_calls = inputs.parallel_tool_calls ? -1 : 1;
// Use trigger_rule so grammar generator knows where to start generating rules
auto tool_calls = p.rule("tool-calls",
p.optional(p.literal(SECTION_BEGIN)) +
p.trigger_rule("tool-call", p.repeat(CALL_BEGIN + tool_choice, min_calls, max_calls) +
p.optional(p.literal(SECTION_END)))
);
auto content_before_tools = p.content(p.until_one_of({ SECTION_BEGIN, CALL_BEGIN }));
return generation_prompt + reasoning + content_before_tools + tool_calls + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<|tool_call_begin|>" }
};
}
return data;
}
-174
View File
@@ -1,174 +0,0 @@
#include "parsers.h"
// Kimi K3 - XTML tagged format, built by open_tag/close_tag macros:
// open_tag(t, attrs) = <|open|>t k="v"...<|sep|> close_tag(t) = <|close|>t<|sep|>
// assistant := [think] [response] [tools] close_tag(message) <|end_of_msg|>
// the generation prompt already opens the think (or response) section, so the
// section opener is optional here - same as Kimi K2 Thinking
common_chat_params common_chat_params_init_kimi_k3(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
const std::string SEP = "<|sep|>";
const std::string MSG_START = "<|open|>message role=\"assistant\"<|sep|>";
const std::string THINK_START = "<|open|>think<|sep|>";
const std::string THINK_END = "<|close|>think<|sep|>";
const std::string RESP_START = "<|open|>response<|sep|>";
const std::string RESP_END = "<|close|>response<|sep|>";
const std::string TOOLS_START = "<|open|>tools<|sep|>";
const std::string TOOLS_END = "<|close|>tools<|sep|>";
const std::string CALL_START = "<|open|>call tool=\"";
const std::string CALL_END = "<|close|>call<|sep|>";
const std::string ARG_START = "<|open|>argument key=\"";
const std::string ARG_END = "<|close|>argument<|sep|>";
const std::string MSG_END = "<|close|>message<|sep|>";
const std::string EOM_TOKEN = "<|end_of_msg|>";
// only the markers are special tokens. tag names ("think", "response", ...) are
// normal tokens and must not be preserved, or prose with those words is broken
data.preserved_tokens = {
"<|open|>",
"<|close|>",
"<|sep|>",
"<|end_of_msg|>",
};
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = { THINK_END };
// per-role message-start delimiters. user/assistant messages only have the role
// attribute, so the full opener is used. system and tool messages have more
// attributes, so those delimiters stop after the closing quote of the role
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|open|>message role=\"assistant\"<|sep|>" },
{ COMMON_CHAT_ROLE_USER, "<|open|>message role=\"user\"<|sep|>" },
{ COMMON_CHAT_ROLE_TOOL, "<|open|>message role=\"tool\"" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|open|>message role=\"system\"" },
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = MSG_START + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + RESP_START + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto end = p.end();
auto start = p.optional(p.literal(MSG_START));
// the think section is always consumed, even with reasoning extraction off:
// the generation prompt ends with open_tag('think'), so it is always present.
// reasoning stops at its own closer, or at the response opener if the model
// skips the closer
auto think_body = extract_reasoning ? p.reasoning(p.until_one_of({ THINK_END, RESP_START })) :
p.content(p.until_one_of({ THINK_END, RESP_START }));
auto reasoning = p.optional(p.optional(p.literal(THINK_START)) + think_body +
p.optional(p.literal(THINK_END)));
// content runs to the response closer, or to the next section if truncated
auto response = p.optional(p.literal(RESP_START)) +
p.content(p.until_one_of({ RESP_END, TOOLS_START, MSG_END })) +
p.optional(p.literal(RESP_END));
// the EOG token after the message closer reaches the parser as text,
// so it must be consumed or the parse stays incomplete
auto trailer = p.optional(p.literal(MSG_END)) + p.optional(p.literal(EOM_TOKEN));
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return start + reasoning + response + trailer + end;
}
auto tool_choices = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const json schema = function.contains("parameters") ? function.at("parameters") : json::object();
// arguments come one tag per key, with the JSON type in a type="..."
// attribute. the type is taken from the tool schema instead, as it tells
// us if the value is JSON or a literal string
auto args = p.eps();
if (schema.contains("properties") && !schema.at("properties").empty()) {
auto arg_choices = p.choice();
for (const auto & prop : schema.at("properties").items()) {
const std::string & key = prop.key();
std::string type = "string";
if (prop.value().is_object() && prop.value().contains("type") &&
prop.value().at("type").is_string()) {
type = prop.value().at("type").get<std::string>();
}
auto value = type == "string" ? p.tool_arg_string_value(p.until(ARG_END)) :
p.tool_arg_value(p.until(ARG_END));
// skip the trailing type="..." attribute: anything up to <|sep|>
arg_choices |= p.rule("kimi-k3-arg-" + name + "-" + key,
p.tool_arg(p.tool_arg_open(p.literal(ARG_START)) +
p.tool_arg_name(p.literal(key)) + p.literal("\"") +
p.until(SEP) + p.literal(SEP) + value +
p.tool_arg_close(p.literal(ARG_END))));
}
args = p.zero_or_more(arg_choices);
}
// skip the trailing index="N" attribute the same way
auto call = p.tool(p.tool_open(p.literal(CALL_START) + p.tool_name(p.literal(name)) + p.literal("\"") +
p.until(SEP) + p.literal(SEP)) +
p.tool_args(args) + p.tool_close(p.literal(CALL_END)));
tool_choices |= p.rule("kimi-k3-tool-" + name, call);
});
// all calls go inside one tools section, then the message is closed. the
// message closer is part of the trigger rule, or else the lazy grammar
// rejects it once tool calls have started
auto tools_section =
p.trigger_rule("kimi-k3-tool-call", p.literal(TOOLS_START) + p.one_or_more(tool_choices) +
p.literal(TOOLS_END) + p.optional(p.literal(MSG_END)) +
p.optional(p.literal(EOM_TOKEN)));
auto tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? tools_section :
p.optional(tools_section);
return start + reasoning + response + tools + trailer + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
if (function.contains("parameters")) {
auto schema = function.at("parameters");
builder.resolve_refs(schema);
}
});
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, TOOLS_START },
};
}
return data;
}
-119
View File
@@ -1,119 +0,0 @@
#include "parsers.h"
// LFM2 format detection: template uses <|tool_list_start|>[...]<|tool_list_end|> around the tool list
// and <|tool_call_start|>[...]<|tool_call_end|> around each tool call
bool is_lfm2_template(const std::string & src) {
return src.find("<|tool_list_start|>") != std::string::npos &&
src.find("<|tool_list_end|>") != std::string::npos;
}
// LFM2/LFM2.5 parser. Tool calls are almost Python-style and parallel-capable
// (except dotted names and JSON literals true/false/null).
// Always wrapped in <|tool_call_start|>[name(args)]<|tool_call_end|> with optional <think> reasoning.
// tool_list_tokens preserves LFM2 system tool-list markers.
common_chat_params common_chat_params_init_lfm2(const common_chat_template & tmpl,
const autoparser::generation_params & inputs,
bool tool_list_tokens) {
common_chat_params data;
const std::string TOOL_CALL_START = "<|tool_call_start|>";
const std::string TOOL_CALL_END = "<|tool_call_end|>";
const std::string TOOL_LIST_START = "<|tool_list_start|>";
const std::string TOOL_LIST_END = "<|tool_list_end|>";
const std::string THINK_START = "<think>";
const std::string THINK_END = "</think>";
const std::string GEN_PROMPT = "<|im_start|>assistant\n";
// Copy reasoning to the "thinking" field the template expects
auto adjusted_messages = json::array();
for (auto msg : inputs.messages) {
if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
msg["thinking"] = msg.at("reasoning_content");
}
adjusted_messages.push_back(msg);
}
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs, adjusted_messages);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs, adjusted_messages);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.preserved_tokens = { TOOL_CALL_START, TOOL_CALL_END, THINK_START, THINK_END };
if (tool_list_tokens) {
data.preserved_tokens.push_back(TOOL_LIST_START);
data.preserved_tokens.push_back(TOOL_LIST_END);
}
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = {THINK_END};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
// Gate by reasoning format and whether the template supports <think>
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE &&
tmpl.source().find(THINK_START) != std::string::npos;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal(GEN_PROMPT);
auto end = p.end();
auto reasoning = p.eps();
if (extract_reasoning) {
reasoning = p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
}
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
if (has_response_format) {
auto response_format = p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema));
return generation_prompt + reasoning + response_format + end;
}
return generation_prompt + reasoning + p.content(p.rest()) + end;
}
auto tool_calls = p.rule("tool-calls",
p.trigger_rule("tool-call",
p.literal(TOOL_CALL_START) +
p.python_style_tool_calls(inputs.tools, inputs.parallel_tool_calls, /* allow_json_literals = */ true) +
p.literal(TOOL_CALL_END)
)
);
auto content = p.content(p.until(TOOL_CALL_START));
return generation_prompt + reasoning + content + tool_calls + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, TOOL_CALL_START }
};
}
return data;
}
-144
View File
@@ -1,144 +0,0 @@
#include "parsers.h"
// MiniCPM5 format:
// - Reasoning: <think>{reasoning}</think> (optional)
// - Tool calls: <function name="foo"><param name="bar">value</param></function>
common_chat_params common_chat_params_init_minicpm5(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.preserved_tokens = {
"<function",
"<param",
"</function>",
"</param>",
"<think>",
"</think>",
};
data.thinking_start_tag = "<think>";
data.thinking_end_tags = {"</think>"};
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|im_start|>assistant" },
{ COMMON_CHAT_ROLE_TOOL, "<|im_start|>user\n<tool_response>" },
{ COMMON_CHAT_ROLE_USER, "<|im_start|>user" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|im_start|>system" },
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "<|im_start|>assistant\n<think>\n" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "\n</think>\n\n" + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal("<|im_start|>assistant\n");
auto reasoning = p.eps();
if (extract_reasoning) {
reasoning = ("<think>" << p.reasoning(p.until("</think>")) << "</think>") + p.space();
}
// Response format parser
if (has_response_format) {
return generation_prompt + reasoning + p.content(p.schema(p.json(), "response-format", inputs.json_schema));
}
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
// CDATA lets a value carry characters that would otherwise close the tag (e.g.
// </param>); capture the inner text only, excluding the CDATA markers.
auto string_value = p.choice({
p.literal("<![CDATA[") + p.ac(p.tool_arg_string_value(p.until("]]>")) + p.literal("]]>"), "]]>") + p.tool_arg_close(p.literal("</param>")),
p.negate(p.literal("<![CDATA[")) + p.ac(p.tool_arg_string_value(p.until("</param>")) + p.tool_arg_close(p.literal("</param>")), "</param>")
});
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
const std::string name = function.at("name");
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto args = p.eps();
if (params.contains("properties") && params.at("properties").is_object() && !params.at("properties").empty()) {
auto schema_info = common_schema_info();
schema_info.resolve_refs(params);
auto arg_choice = p.choice();
for (const auto & [prop_name, prop_schema] : params.at("properties").items()) {
auto value_parser = p.eps();
if (schema_info.resolves_to_string(prop_schema)) {
value_parser = string_value;
} else {
value_parser = p.tool_arg_json_value(
p.schema(p.json(), "tool-" + name + "-arg-" + prop_name + "-schema", prop_schema, false)
) + p.tool_arg_close(p.literal("</param>"));
}
auto arg_rule = p.tool_arg(
p.tool_arg_open(p.literal("<param name=\"") + p.tool_arg_name(p.literal(prop_name)) + p.literal("\">")) +
value_parser
);
arg_choice |= arg_rule;
}
args = p.zero_or_more(arg_choice + p.space());
}
auto tool_parser = p.tool(
p.tool_open(p.literal("<function name=\"") + p.tool_name(p.literal(name)) + p.literal("\">"))
<< p.tool_args(args)
<< p.tool_close(p.literal("</function>")));
tool_choice |= p.rule("tool-" + name, tool_parser);
});
auto max_calls = inputs.parallel_tool_calls ? -1 : 1;
auto tool_calls = p.trigger_rule("tool-call", p.repeat(tool_choice + p.space(), 1, max_calls));
auto content = p.content(p.until("<function"));
return generation_prompt + reasoning + content + tool_calls + p.end();
}
return generation_prompt + reasoning + p.content(p.rest()) + p.end();
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<function" },
};
}
return data;
}
-259
View File
@@ -1,259 +0,0 @@
#include "parsers.h"
common_chat_params common_chat_params_init_minimax_m3(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_MINIMAX_M3;
data.supports_thinking = true;
data.thinking_start_tag = "<mm:think>";
data.thinking_end_tags = {"</mm:think>"};
// M3 prefixes every tool tag with the namespace token "]<]minimax[>[";
// params use the parameter name as the tag (<file_path>...</file_path>).
const std::string NS = "]<]minimax[>[";
const std::string THINK_START = "<mm:think>";
const std::string THINK_END = "</mm:think>";
const std::string FC_START = NS + "<tool_call>";
const std::string FC_END = NS + "</tool_call>";
const std::string INVOKE_END = NS + "</invoke>";
data.preserved_tokens = {
NS,
"<tool_call>",
"</tool_call>",
THINK_START,
THINK_END,
};
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "]~b]ai" },
{ COMMON_CHAT_ROLE_USER, "]~b]user" },
{ COMMON_CHAT_ROLE_TOOL, "]~b]tool" },
{ COMMON_CHAT_ROLE_SYSTEM, "]~b]developer" },
{ COMMON_CHAT_ROLE_SYSTEM, "]~b]system" },
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
const std::string GEN_PROMPT = data.generation_prompt;
using mm3 = common_chat_peg_minimax_m3_mapper;
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += THINK_END + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.prefix(GEN_PROMPT, THINK_START);
auto end = p.end();
auto reasoning = p.eps();
if (extract_reasoning) {
auto block = inputs.enable_thinking
? p.literal(THINK_START) + p.space() +
p.ac(p.reasoning(p.until(THINK_END)) + p.literal(THINK_END), THINK_END)
: p.literal(THINK_START) + p.ac(p.until(THINK_END) + p.literal(THINK_END), THINK_END);
// A turn without reasoning is prefixed with a bare </mm:think>, written either by the
// generation prompt (thinking_mode = "disabled") or by the model itself.
reasoning = p.optional(p.choice({ block, p.literal(THINK_END) }));
}
if (has_response_format) {
auto response_format = p.rule("response-format",
p.literal("```json") + p.space() +
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) +
p.space() + p.literal("```"));
return generation_prompt + reasoning + response_format + end;
}
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return generation_prompt + reasoning + p.content(p.rest()) + end;
}
auto alternatives_of = [](const json & schema) -> std::optional<json> {
for (const auto * keyword : { "oneOf", "anyOf" }) {
if (schema.contains(keyword) && schema.at(keyword).is_array() && !schema.at(keyword).empty()) {
return schema.at(keyword);
}
}
return std::nullopt;
};
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto schema_info = common_schema_info();
schema_info.resolve_refs(params);
// The template expands argument values recursively in XML (see the to_xml() macro)
std::function<common_peg_parser(const json &, const std::string &, const std::string &)> value_of;
std::function<common_peg_parser(const json &, const std::string &)> members_of;
auto element_of = [&](const std::string & tag, const json & schema, const std::string & rule_name) {
const std::string close = NS + "</" + tag + ">";
return p.rule(rule_name,
p.tool_arg(
p.tool_arg_open(
p.literal(NS + "<") +
p.tool_arg_name(p.literal(tag)) +
p.literal(">")) +
value_of(schema, rule_name, close)));
};
value_of = [&](const json & schema,
const std::string & rule_name,
const std::string & close) -> common_peg_parser {
auto close_tag = p.tool_arg_close(p.literal(close));
// A string accepts anything, so a union with a string alternative is a string
if (schema_info.resolves_to_string(schema)) {
return p.ac(p.tool_arg_string_value(p.until(close)) + close_tag, close);
}
if (auto alternatives = alternatives_of(schema)) {
std::vector<common_peg_parser> choices;
size_t index = 0;
for (const auto & alternative : *alternatives) {
const std::string alt_name = rule_name + "-" + std::to_string(index++);
// There is a risk that this breaks streaming deltas, but that's a risk we
// assume to provide tool arg streaming.
choices.push_back(value_of(alternative, alt_name, close));
}
return p.choice(choices);
}
const std::string type = schema.contains("type") && schema.at("type").is_string()
? schema.at("type").get<std::string>()
: "";
if (type == "object" && schema.contains("properties")) {
return p.tag(mm3::TOOL_ARG_OBJECT, members_of(schema, rule_name)) + p.space() + close_tag;
}
if (type == "array" && schema.contains("items")) {
const std::string item_close = NS + "</item>";
auto item = p.rule(rule_name + "-item",
p.tag(mm3::TOOL_ARG_ITEM,
p.literal(NS + "<item>") +
value_of(schema.at("items"), rule_name + "-item", item_close)));
return p.tag(mm3::TOOL_ARG_ARRAY, p.repeat(p.space() + item, 0, -1)) + p.space() + close_tag;
}
return p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", schema, false)) + close_tag;
};
// Required properties in schema order, then any number of optional ones in any order.
members_of = [&](const json & schema, const std::string & rule_prefix) -> common_peg_parser {
const auto & props = schema.at("properties");
std::set<std::string> required;
if (schema.contains("required")) {
required = schema.at("required").get<std::set<std::string>>();
}
std::vector<common_peg_parser> required_elements;
std::vector<common_peg_parser> optional_elements;
for (const auto & [key, key_schema] : props.items()) {
auto element = element_of(key, key_schema, rule_prefix + "-" + key);
if (required.find(key) != required.end()) {
required_elements.push_back(element);
} else {
optional_elements.push_back(element);
}
}
common_peg_parser members = p.eps();
for (size_t i = 0; i < required_elements.size(); i++) {
if (i > 0) {
members = members + p.space();
}
members = members + required_elements[i];
}
if (!optional_elements.empty()) {
common_peg_parser any_optional = p.choice();
for (const auto & element : optional_elements) {
any_optional |= element;
}
members = members + p.repeat(p.space() + any_optional, 0, -1);
}
return members;
};
common_peg_parser invoke_body =
params.contains("properties") ? members_of(params, "tool-" + name + "-arg") : p.eps();
auto func_parser = p.tool(
p.tool_open(p.literal(NS + "<invoke name=\"") +
p.tool_name(p.literal(name)) + p.literal("\">")) +
p.space() + invoke_body + p.space() +
p.tool_close(p.literal(INVOKE_END)));
tool_choice |= p.rule("tool-" + name, func_parser);
});
auto require_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;
common_peg_parser tool_calls = p.eps();
if (inputs.parallel_tool_calls) {
tool_calls = p.trigger_rule("tool-call",
p.literal(FC_START) + p.space() + tool_choice +
p.zero_or_more(p.space() + tool_choice) + p.space() + p.literal(FC_END));
} else {
tool_calls = p.trigger_rule("tool-call",
p.literal(FC_START) + p.space() + tool_choice + p.space() + p.literal(FC_END));
}
if (!require_tools) {
tool_calls = p.optional(tool_calls);
}
auto content_before_tools = p.content(p.until(FC_START));
return generation_prompt + reasoning + content_before_tools + tool_calls + end;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, FC_START },
};
}
return data;
}
-135
View File
@@ -1,135 +0,0 @@
#include "parsers.h"
common_chat_params common_chat_params_init_ministral_3(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
// Build up messages to follow the format: https://huggingface.co/mistralai/Ministral-3-14B-Reasoning-2512/blob/main/chat_template.jinja
auto adjusted_messages = json::array();
for (const auto & msg : inputs.messages) {
auto role = msg.value("role", "");
if (role != "system" && role != "assistant") {
// Only adjust system and assistant messages. Interestingly, the system message may contain thinking.
adjusted_messages.push_back(msg);
continue;
}
auto content = json::array();
// If message contains `reasoning_content`, add it as a block of type `thinking`
if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
content.push_back({
{ "type", "thinking" },
{ "thinking", msg.at("reasoning_content").get<std::string>() },
});
}
// If message contains `content`, add it as a block of type `text`
if (msg.contains("content")) {
if (msg.at("content").is_string()) {
content.push_back({
{ "type", "text" },
{ "text", msg.at("content").get<std::string>() },
});
} else if (msg.at("content").is_array()) {
auto blocks = msg.at("content");
content.insert(blocks);
}
}
auto adjusted = msg;
adjusted["content"] = content;
adjusted.erase("reasoning_content");
adjusted_messages.push_back(adjusted);
}
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = true;
data.supports_thinking = true;
data.thinking_start_tag = "[THINK]";
data.thinking_end_tags = {"[/THINK]"};
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs, /* messages_override = */ adjusted_messages);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs, /* messages_override = */ adjusted_messages);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.preserved_tokens = {
"[THINK]",
"[/THINK]",
"[TOOL_CALLS]",
"[ARGS]",
};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "[THINK]" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "[/THINK]" + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.eps();
auto reasoning =
extract_reasoning ? p.optional("[THINK]" + p.reasoning(p.until("[/THINK]")) + "[/THINK]") : p.eps();
// Response format parser
if (has_response_format) {
// Ministral wants to emit json surrounded by code fences
return generation_prompt + (reasoning << "```json" << p.content(p.schema(p.json(), "response-format", inputs.json_schema)) << "```");
}
// Tool call parser
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
const auto & schema = function.at("parameters");
tool_choice |=
p.rule("tool-" + name, p.tool_open(p.tool_name(p.literal(name)) + "[ARGS]") +
p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)));
});
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
auto max_calls = inputs.parallel_tool_calls ? -1 : 1;
auto tool_calls = p.trigger_rule("tool-call", p.repeat("[TOOL_CALLS]" + tool_choice, min_calls, max_calls));
return generation_prompt + (reasoning << p.content(p.until("[TOOL_CALLS]")) << tool_calls);
}
// Content only parser
include_grammar = false;
return generation_prompt + (reasoning << p.content(p.rest()));
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.at("parameters");
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "[TOOL_CALLS]" }
};
}
return data;
}
-148
View File
@@ -1,148 +0,0 @@
#include "parsers.h"
// An assistant turn is rendered as one or more messages, each
// "<|start|>assistant to=<recipient><|message|>{content}{END}" where END is
// <|eom|> (more messages follow) or <|eot|> (end of turn):
// - chain-of-thought: to=self, terminated by <|eom|>
// - final answer: to=user, terminated by <|eot|>
// The generation prompt is just "<|start|>assistant"; the model emits its own
// " to=...<|message|>".
common_chat_params common_chat_params_init_muse_glimmer(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = "<|start|>assistant";
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
data.preserved_tokens = {
"<|start|>", "<|message|>", "<|eom|>", "<|eot|>",
// ATEM tool-call markup emitted on " to=<tool>" turns.
"<atem:function_calls>", "<atem:invoke", "<atem:parameter", "</atem:parameter>",
"</atem:invoke>", "</atem:function_calls>",
};
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
{ COMMON_CHAT_ROLE_TOOL, "<|start|>tool" },
};
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = "<|start|>assistant to=self<|message|>" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "<|eom|><|start|>assistant to=user<|message|>" + msg.render_content();
}
data.prompt += data.generation_prompt;
}
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
// Constrained grammar whenever tools are offered.
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto start = p.rule("start", p.literal("<|start|>assistant"));
if (!extract_reasoning && !include_grammar) {
return start + p.content(p.rest());
}
if (extract_reasoning) {
p.rule("analysis", p.literal(" to=self<|message|>") + p.reasoning(p.until("<|eom|>")) + p.literal("<|eom|>"));
} else {
p.rule("analysis", p.literal(" to=self<|message|>") + p.content(p.until("<|eom|>")) + p.literal("<|eom|>"));
}
auto analysis = p.ref("analysis");
auto recipient = p.optional(p.literal(" to=user"));
auto final_msg = p.rule("final", recipient + p.literal("<|message|>") +
p.content(p.until_one_of({ "<|eot|>", "<|eom|>" })));
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
auto string_value = p.ac(
p.tool_arg_string_value(p.until("</atem:parameter>")) + p.tool_arg_close(p.literal("</atem:parameter>")),
"</atem:parameter>");
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
const std::string name = function.at("name");
auto params = function.contains("parameters") ? function.at("parameters") : json::object();
auto args = p.eps();
if (params.contains("properties") && params.at("properties").is_object() && !params.at("properties").empty()) {
auto schema_info = common_schema_info();
schema_info.resolve_refs(params);
auto arg_choice = p.choice();
for (const auto & [prop_name, prop_schema] : params.at("properties").items()) {
auto value_parser = p.eps();
if (schema_info.resolves_to_string(prop_schema)) {
value_parser = string_value;
} else {
value_parser = p.tool_arg_json_value(
p.schema(p.json(), "tool-" + name + "-arg-" + prop_name + "-schema", prop_schema, false))
+ p.tool_arg_close(p.literal("</atem:parameter>"));
}
auto arg_rule = p.tool_arg(
p.tool_arg_open(p.literal("<atem:parameter name=\"") + p.tool_arg_name(p.literal(prop_name)) + p.literal("\">")) +
value_parser);
arg_choice |= arg_rule;
}
args = p.zero_or_more(arg_choice + p.space());
}
auto tool_parser = p.tool(
p.tool_open(p.literal(" to=") + p.until("<|message|>") +
p.literal("<|message|><atem:function_calls>") + p.space() +
p.literal("<atem:invoke name=\"") + p.tool_name(p.literal(name)) + p.literal("\">") + p.space())
<< p.tool_args(args)
<< p.tool_close(p.literal("</atem:invoke>") + p.space() + p.literal("</atem:function_calls>")));
tool_choice |= p.rule("tool-" + name, tool_parser);
});
auto tool_calls = inputs.parallel_tool_calls
? p.trigger_rule("tool-call", tool_choice + p.zero_or_more(p.literal("<|eom|>") + start + tool_choice))
: p.trigger_rule("tool-call", tool_choice);
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
return p.zero_or_more(start + analysis) + start + tool_calls;
}
auto trailing_calls = p.optional(p.literal("<|eom|>") + start + tool_calls);
return p.zero_or_more(start + analysis) + start + (tool_calls | (final_msg + trailing_calls));
}
return p.zero_or_more(start + analysis) + start + final_msg;
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
builder.resolve_refs(schema);
});
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
"<\\|start\\|>assistant( to=(?!self<\\|message\\|>)(?!user<\\|message\\|>)[^<]*?<\\|message\\|>)" },
};
}
return data;
}
-34
View File
@@ -1,34 +0,0 @@
#include "parsers.h"
#include "log.h"
#include <set>
void foreach_function(const json & tools, const std::function<void(const json &)> & fn) {
for (const auto & tool : tools) {
if (!tool.contains("type") || tool.at("type") != "function" || !tool.contains("function")) {
LOG_INF("Skipping tool without function: %s", tool.dump(2).c_str());
continue;
}
fn(tool);
}
}
void foreach_parameter(const json & function, const std::function<void(const std::string &, const json &, bool)> & fn) {
if (!function.contains("parameters") || !function.at("parameters").is_object()) {
return;
}
const auto & params = function.at("parameters");
if (!params.contains("properties") || !params.at("properties").is_object()) {
return;
}
const auto & props = params.at("properties");
std::set<std::string> required;
if (params.contains("required") && params.at("required").is_array()) {
required = params.at("required").get<std::set<std::string>>();
}
for (const auto & [name, prop] : props.items()) {
bool is_required = (required.find(name) != required.end());
fn(name, prop, is_required);
}
}
-77
View File
@@ -1,77 +0,0 @@
#pragma once
#include "chat.h"
#include "chat-auto-parser.h"
#include "chat-auto-parser-helpers.h"
#include "chat-peg-parser.h"
#include "common.h"
#include "ggml.h"
#include "json-schema-to-grammar.h"
#include "json.h"
#include <functional>
#include <optional>
#include <set>
#include <string>
#include <vector>
using json = common_json;
// iterate over the function tools of an OpenAI-style tools array
void foreach_function(const json & tools, const std::function<void(const json &)> & fn);
// iterate over the parameters of a function tool, flagging the ones listed as required
void foreach_parameter(const json & function, const std::function<void(const std::string &, const json &, bool)> & fn);
// render a template; the override arguments let a parser feed in messages, tools or context it has rewritten
std::string common_chat_template_direct_apply_impl(
const common_chat_template & tmpl,
const autoparser::generation_params & inputs,
const std::optional<json> & messages_override = std::nullopt,
const std::optional<json> & tools_override = std::nullopt,
const std::optional<json> & additional_context = std::nullopt);
// the suffix a template appends when add_generation_prompt is set
std::string common_chat_template_generation_prompt_impl(
const common_chat_template & tmpl,
const autoparser::generation_params & inputs,
const std::optional<json> & messages_override = std::nullopt,
const std::optional<json> & tools_override = std::nullopt,
const std::optional<json> & additional_context = std::nullopt);
bool is_lfm2_template(const std::string & src);
namespace workaround {
void convert_tool_responses_gemma4(json & messages);
}
common_chat_params common_chat_params_init_cohere2moe(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_functionary_v3_2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_gemma4(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_gigachat_v3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_kimi_k3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
// tool_list_tokens preserves the LFM2 system tool-list markers; LFM2.5 renders without them
common_chat_params common_chat_params_init_lfm2(const common_chat_template & tmpl, const autoparser::generation_params & inputs, bool tool_list_tokens);
common_chat_params common_chat_params_init_minicpm5(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_minimax_m3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_ministral_3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_muse_glimmer(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
common_chat_params common_chat_params_init_qwen3_coder(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
-181
View File
@@ -1,181 +0,0 @@
#include "parsers.h"
common_chat_params common_chat_params_init_qwen3_coder(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
const std::string GEN_PREFIX = "<|im_start|>assistant\n";
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
auto supports_reasoning = tmpl.source().find("<think>") != std::string::npos;
data.supports_thinking = supports_reasoning;
data.preserved_tokens = {
"<tool_call>",
"</tool_call>",
};
auto is_qwen3_coder = !supports_reasoning;
if (supports_reasoning) {
data.thinking_start_tag = "<think>";
// Support both </think> and <tool_call> as reasoning end sequences.
// <function= is omitted, as it is a workaround for Qwen3-Coder which is not a thinking model
data.thinking_end_tags = { "</think>", "<tool_call>" };
data.preserved_tokens.insert(data.preserved_tokens.end(), { "<think>", "</think>" });
}
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|im_start|>assistant" },
{ COMMON_CHAT_ROLE_TOOL, "<|im_start|>user\n<tool_response>" }, // Qwen3-Coder, Qwen3.5, Nemotron Nano 3
{ COMMON_CHAT_ROLE_TOOL, "<|im_start|>tool_response" }, // StepFun-3.5-Flash
{ COMMON_CHAT_ROLE_USER, "<|im_start|>user" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|im_start|>system" },
};
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
if (inputs.has_continuation()) {
const auto & msg = inputs.continue_msg;
data.generation_prompt = GEN_PREFIX;
if (supports_reasoning) {
data.generation_prompt += "<think>\n" + msg.reasoning_content;
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += "\n</think>\n\n";
}
}
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
data.generation_prompt += msg.render_content();
}
data.prompt += data.generation_prompt;
}
std::vector<std::string> tool_call_starts = { "<tool_call>" };
if (is_qwen3_coder) {
// Match complete <function=name> opener for Qwen3-Coder models that occasionally omit the
// starting <tool_call>. The model may hallucinate a tool name, but it is preferable over
// constraining on <function which may occur in valid content generation, e.g. #include <functional>
foreach_function(inputs.tools, [&](const json & tool) {
const std::string name = tool.at("function").at("name");
tool_call_starts.push_back("<function=" + name + ">");
});
}
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto generation_prompt = p.literal(GEN_PREFIX);
auto reasoning = p.eps();
if (supports_reasoning && extract_reasoning) {
reasoning = p.optional("<think>" + p.space() +
p.reasoning(p.until_one_of({ "</think>", "<tool_call>" })) +
(p.literal("</think>") | p.peek(p.literal("<tool_call>"))));
}
// Response format parser
if (has_response_format) {
return generation_prompt + (reasoning << p.content(p.schema(p.json(), "response-format", inputs.json_schema)));
}
// Tool call parser
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
auto arg_close = p.tool_arg_close(p.literal("\n</parameter>\n"));
auto arg_string = p.rule("xml-arg-string",
p.ac(p.tool_arg_string_value(p.until("\n</parameter>\n")) + arg_close, "\n</parameter>\n"));
auto tool_choice = p.choice();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
auto parameters = function.contains("parameters") ? function.at("parameters") : json::object();
auto schema_info = common_schema_info();
schema_info.resolve_refs(parameters);
std::vector<common_peg_parser> required_args;
std::vector<common_peg_parser> optional_args;
foreach_parameter(function, [&](const std::string & param_name, const json & param_schema, bool is_required) {
auto rule_name = "tool-" + name + "-arg-" + param_name;
auto arg_open = p.tool_arg_open("<parameter=" + p.tool_arg_name(p.literal(param_name)) + ">\n");
auto arg_value = schema_info.resolves_to_string(param_schema) ?
arg_string :
p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", param_schema)) + arg_close;
auto arg_rule = p.rule(rule_name, p.tool_arg(arg_open + arg_value));
(is_required ? required_args : optional_args).push_back(arg_rule);
});
// Accept required arguments in any order, as Qwen does not always adhere to the
// order provided.
auto args = p.permute("tool-" + name + "-args", required_args);
if (!optional_args.empty()) {
args = args + p.zero_or_more(p.choice(optional_args));
}
auto func = p.tool(p.tool_open("<function=" + p.tool_name(p.literal(name)) + ">\n") +
p.tool_args(args) +
p.tool_close(p.literal("</function>\n")));
tool_choice |= p.rule("tool-" + name, func);
});
auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
auto tool_call_body = tool_choice + "</tool_call>" + p.space();
auto tool_call = p.rule("tool-call", "<tool_call>\n" + tool_call_body);
// Qwen3-Coder models may occasionally omit the <tool_call> token.
auto tool_call_first = is_qwen3_coder ?
p.rule("tool-call-first", p.optional(p.literal("<tool_call>\n")) + tool_call_body) :
tool_call;
auto calls = inputs.parallel_tool_calls ? tool_call_first + p.zero_or_more(tool_call) : tool_call_first;
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(calls, min_calls, 1));
return generation_prompt +
(reasoning << p.content(p.until_one_of(tool_call_starts)) << tool_calls);
}
// Content only parser
return generation_prompt + (reasoning << p.content(p.rest()));
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
builder.resolve_refs(schema);
});
if (has_response_format) {
auto schema = inputs.json_schema;
builder.resolve_refs(schema);
}
parser.build_grammar(builder, data.grammar_lazy);
});
if (data.grammar_lazy) {
for (const auto & start : tool_call_starts) {
data.grammar_triggers.push_back({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, start });
}
}
}
return data;
}
-20
View File
@@ -1,20 +0,0 @@
# Specialized chat template parsers, listed explicitly so that adding or removing one re-runs CMake instead of leaving an incremental build stale.
set(LLAMA_CHAT_PARSERS_SOURCES
${CMAKE_CURRENT_LIST_DIR}/parsers.cpp
${CMAKE_CURRENT_LIST_DIR}/parsers.h
${CMAKE_CURRENT_LIST_DIR}/cohere2moe.cpp
${CMAKE_CURRENT_LIST_DIR}/deepseek.cpp
${CMAKE_CURRENT_LIST_DIR}/functionary-v3-2.cpp
${CMAKE_CURRENT_LIST_DIR}/gemma4.cpp
${CMAKE_CURRENT_LIST_DIR}/gigachat-v3.cpp
${CMAKE_CURRENT_LIST_DIR}/gpt-oss.cpp
${CMAKE_CURRENT_LIST_DIR}/kimi-k2.cpp
${CMAKE_CURRENT_LIST_DIR}/kimi-k3.cpp
${CMAKE_CURRENT_LIST_DIR}/lfm2.cpp
${CMAKE_CURRENT_LIST_DIR}/minicpm5.cpp
${CMAKE_CURRENT_LIST_DIR}/minimax-m3.cpp
${CMAKE_CURRENT_LIST_DIR}/ministral3.cpp
${CMAKE_CURRENT_LIST_DIR}/muse-glimmer.cpp
${CMAKE_CURRENT_LIST_DIR}/qwen3-coder.cpp
)
+54 -235
View File
@@ -14,7 +14,6 @@
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <map>
@@ -139,7 +138,6 @@ struct common_speculative_impl {
const common_speculative_type type;
uint32_t n_seq;
int32_t n_max; // maximum draft length after implementation-specific limits
size_t n_call_begin = 0; // number of times this implementation was called for refresh.
size_t n_call_draft = 0; // number of times this implementation was called for generation.
@@ -159,7 +157,7 @@ struct common_speculative_impl {
int64_t t_draft_us = 0; // total time spent in generating drafts in this implementation in microseconds.
int64_t t_accept_us = 0; // total time spent in accumulation of this implementation in microseconds.
common_speculative_impl(common_speculative_type type, uint32_t n_seq, int32_t n_max) : type(type), n_seq(n_seq), n_max(n_max) {}
common_speculative_impl(common_speculative_type type, uint32_t n_seq) : type(type), n_seq(n_seq) {}
virtual ~common_speculative_impl() = default;
@@ -184,7 +182,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
std::vector<common_sampler_ptr> smpls;
common_speculative_impl_draft_simple(const common_params_speculative & params, uint32_t n_seq)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_SIMPLE, n_seq, params.draft.n_max)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_SIMPLE, n_seq)
, params(params.draft)
{
auto * ctx_dft = this->params.ctx_dft;
@@ -454,7 +452,7 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
std::vector<float> g_embd_buf;
common_speculative_impl_draft_eagle3(const common_params_speculative & params, uint32_t n_seq)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_EAGLE3, n_seq, params.draft.n_max)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_EAGLE3, n_seq)
, params(params.draft)
{
SPC_TRC("%s", "adding speculative implementation 'draft-eagle3'\n");
@@ -925,25 +923,21 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
int32_t block_size = 0;
llama_token mask_token_id = 0;
bool is_dflash2 = false;
bool is_mrope = false;
int32_t selector_top_k = 0;
// draft-dspark: the draft carries a Markov head and uses an anchor-first block layout
const bool is_dspark;
// dspark speculators
bool sample_from_anchor = true;
// block-internal attention
bool causal_attn = false;
const int32_t * target_layer_ids = nullptr; // model_dft's extract layer indices
uint32_t target_layer_ids_n = 0;
// scratch buffer for concatenated target features [n_tokens, n_embd_enc]
std::vector<float> features_buf;
common_speculative_impl_draft_dflash(const common_params_speculative & params, uint32_t n_seq,
common_speculative_type type = COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH)
: common_speculative_impl(type, n_seq, params.draft.n_max)
: common_speculative_impl(type, n_seq)
, params(params.draft)
, is_dspark(type == COMMON_SPECULATIVE_TYPE_DRAFT_DSPARK)
{
@@ -972,25 +966,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
if (llama_model_meta_val_str(model_dft, "dflash.sample_from_anchor", buf, sizeof(buf)) >= 0) {
sample_from_anchor = std::strcmp(buf, "true") == 0;
}
if (llama_model_meta_val_str(model_dft, "dflash.attention.causal", buf, sizeof(buf)) >= 0) {
causal_attn = std::strcmp(buf, "true") == 0;
}
}
selector_top_k = llama_model_dflash_selector_top_k(model_dft);
is_dflash2 = selector_top_k > 0;
mask_token_id = llama_vocab_mask(llama_model_get_vocab(model_dft));
if (is_dspark && this->params.p_min > 0.0f) {
char buf[16] = {};
const bool has_conf =
llama_model_meta_val_str(model_dft, "dflash.has_confidence_head", buf, sizeof(buf)) < 0 ||
std::strcmp(buf, "true") == 0;
if (!has_conf) {
throw std::runtime_error("DSpark draft has no confidence head: please set --spec-draft-p-min 0");
}
}
LOG_INF("%s: adding speculative implementation '%s'\n", __func__, common_speculative_type_to_str(type).c_str());
LOG_INF("%s: - n_max=%d, n_min=%d, p_min=%.2f\n", __func__, this->params.n_max, this->params.n_min, this->params.p_min);
LOG_INF("%s: - block_size=%d, mask_token_id=%d, n_extract=%u, sample_from_anchor=%s\n", __func__,
@@ -1005,17 +983,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
this->params.n_max = std::min(this->params.n_max, n_draft_max);
this->params.n_min = std::min(this->params.n_min, n_draft_max);
}
this->n_max = this->params.n_max;
batch = llama_batch_init(llama_n_batch(ctx_dft), 0, n_seq);
batch_inject = llama_batch_init(llama_n_ubatch(ctx_dft), n_embd_enc, n_seq);
// embd batches on an M-RoPE draft need 4 position rows per token
is_mrope = llama_model_rope_type(model_dft) == LLAMA_ROPE_TYPE_MROPE;
if (is_mrope) {
free(batch_inject.pos);
batch_inject.pos = (llama_pos *) malloc(sizeof(llama_pos) * 4 * llama_n_batch(ctx_dft));
}
batch_inject = llama_batch_init(llama_n_batch(ctx_dft), n_embd_dec, n_seq);
smpls.resize(n_seq);
for (auto & s : smpls) {
@@ -1028,7 +998,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
// offload draft sampling to the backend
backend_chains.assign(n_seq, nullptr);
if (this->params.backend_sampling && !is_dflash2) {
if (this->params.backend_sampling) {
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
llama_sampler * chain = llama_sampler_chain_init(llama_sampler_chain_default_params());
llama_sampler_chain_add(chain, llama_sampler_init_top_k(10));
@@ -1047,9 +1017,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true);
}
// DFlash2 reads its selector lattice from h_nextn and never consumes raw logits.
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ !is_dflash2);
llama_set_causal_attn(ctx_dft, causal_attn); // DFlash needs non-causal attention unless the model says otherwise
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true);
llama_set_causal_attn(ctx_dft, false); // DFlash needs non-causal attention
}
~common_speculative_impl_draft_dflash() override {
@@ -1134,34 +1103,52 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
for (int32_t offset = 0; offset < n_rows; offset += n_ubatch) {
const int32_t n_chunk = std::min(n_ubatch, n_rows - offset);
// gather target features per extract layer; the fused decode encodes and
// injects them into the K/V cache at the target positions
batch_inject.n_tokens = n_chunk;
// gather this chunk's target features, interleaved by extract layer
features_buf.resize((size_t) n_chunk * n_embd_enc);
for (uint32_t k = 0; k < target_layer_ids_n; ++k) {
const float * layer = llama_get_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k]);
if (!layer) {
GGML_ABORT("DFlash: target layer %d input not extracted.", target_layer_ids[k]);
}
for (int32_t i = 0; i < n_chunk; ++i) {
float * dst = batch_inject.embd + (size_t) i * n_embd_enc + k * (size_t) n_embd_tgt;
float * dst = features_buf.data() + (size_t) i * n_embd_enc + k * (size_t) n_embd_tgt;
const float * src = layer + (size_t) (i_batch_beg[seq_id] + offset + i) * n_embd_tgt;
std::memcpy(dst, src, (size_t) n_embd_tgt * sizeof(float));
}
}
// fuse extracted features through DFlash encoder
llama_batch enc_batch = {
/*.n_tokens =*/ n_chunk,
/*.token =*/ nullptr,
/*.embd =*/ features_buf.data(),
/*.pos =*/ nullptr,
/*.n_seq_id =*/ nullptr,
/*.seq_id =*/ nullptr,
/*.logits =*/ nullptr,
};
int32_t rc = llama_encode(ctx_dft, enc_batch);
if (rc != 0) {
LOG_ERR("%s: llama_encode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
__func__, rc, (int) n_chunk, (int) offset);
return false;
}
const float * inp_g = llama_get_embeddings_nextn(ctx_dft);
GGML_ASSERT(inp_g && "DFlash encoder produced no output.");
// inject the DFlash decoder K/V cache at the tokens' target positions
batch_inject.n_tokens = n_chunk;
std::memcpy(batch_inject.embd, inp_g, (size_t) n_chunk * n_embd_dec * sizeof(float));
for (int32_t i = 0; i < n_chunk; ++i) {
const llama_pos p = batch_in.pos[i_batch_beg[seq_id] + offset + i];
batch_inject.pos[i] = p;
if (is_mrope) {
batch_inject.pos[1 * n_chunk + i] = p;
batch_inject.pos[2 * n_chunk + i] = p;
batch_inject.pos[3 * n_chunk + i] = 0;
}
batch_inject.pos[i] = batch_in.pos[i_batch_beg[seq_id] + offset + i];
batch_inject.n_seq_id[i] = 1;
batch_inject.seq_id[i][0] = seq_id;
batch_inject.logits[i] = false;
}
const int32_t rc = llama_decode(ctx_dft, batch_inject);
rc = llama_decode(ctx_dft, batch_inject);
if (rc != 0) {
LOG_ERR("%s: llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
__func__, rc, (int) n_chunk, (int) offset);
@@ -1199,7 +1186,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
i_block_beg[seq_id] = batch.n_tokens;
n_block [seq_id] = n_block_tokens;
for (int32_t i = 0; i < n_block_tokens; ++i) {
common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, !is_dflash2);
common_batch_add(batch, i == 0 ? dp.id_last : mask_token_id, n + i, { seq_id }, true);
}
}
@@ -1227,36 +1214,6 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
auto & result = *dp.result;
if (is_dflash2) {
const float * lattice = llama_get_embeddings_nextn(ctx_dft);
GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
int32_t predecessor = 0;
for (int32_t i = 1; i < n_block_tokens; ++i) {
const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
const float * scores = row + selector_top_k + (size_t) predecessor * selector_top_k;
predecessor = (int32_t) std::distance(scores,
std::max_element(scores, scores + selector_top_k));
if (params.p_min > 0.0f) {
// softmax(scores) at the argmax, i.e. 1 / sum(exp(s_k - s_max))
float sum = 0.0f;
for (int32_t k = 0; k < selector_top_k; ++k) {
sum += std::exp(scores[k] - scores[predecessor]);
}
if (1.0f / sum < params.p_min) {
break;
}
}
result.push_back((llama_token) row[predecessor]);
}
if (result.size() < (size_t) params.n_min) {
result.clear();
}
continue;
}
if (is_dspark) {
// DSpark: read from the first draft slot, truncate below the confidence threshold
const float * conf = params.p_min > 0.0f ? llama_get_embeddings_nextn(ctx_dft) : nullptr;
@@ -1358,7 +1315,7 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
std::vector<std::vector<float>> chain_h;
common_speculative_impl_draft_mtp(const common_params_speculative & params, uint32_t n_seq)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_MTP, n_seq, params.draft.n_max)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_MTP, n_seq)
, params(params.draft)
{
auto * ctx_tgt = this->params.ctx_tgt;
@@ -1425,7 +1382,6 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
c.reserve((size_t) (this->params.n_max + 1) * n_embd);
}
}
this->n_max = this->params.n_max;
pending_h.assign(n_seq, std::vector<float>(n_embd, 0.0f));
@@ -1770,7 +1726,7 @@ struct common_speculative_impl_ngram_simple : public common_speculative_impl {
common_speculative_impl_ngram_simple(
const common_params_speculative & params, uint32_t n_seq,
common_ngram_simple_config config)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_SIMPLE, n_seq, params.ngram_simple.size_m)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_SIMPLE, n_seq)
, params(params.ngram_simple)
, config(config)
{
@@ -1814,7 +1770,7 @@ struct common_speculative_impl_ngram_map_k : public common_speculative_impl {
const common_ngram_map & config,
uint32_t n_seq)
: common_speculative_impl(config.key_only ? COMMON_SPECULATIVE_TYPE_NGRAM_MAP_K
: COMMON_SPECULATIVE_TYPE_NGRAM_MAP_K4V, n_seq, config.size_value)
: COMMON_SPECULATIVE_TYPE_NGRAM_MAP_K4V, n_seq)
{
for (uint32_t i = 0; i < n_seq; i++) {
this->config.push_back(config);
@@ -1885,7 +1841,7 @@ struct common_speculative_impl_ngram_mod : public common_speculative_impl {
common_speculative_impl_ngram_mod(
const common_params_speculative & params,
uint32_t n_seq)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_MOD, n_seq, params.ngram_mod.n_max)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_MOD, n_seq)
, params(params.ngram_mod)
, mod(params.ngram_mod.n_match, 4*1024*1024)
, verbose(std::getenv("LLAMA_TRACE") != nullptr) {
@@ -2061,7 +2017,7 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
const std::string & path_dynamic,
bool save_dynamic,
bool save_static)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_CACHE, n_seq, n_draft)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_CACHE, n_seq)
, params(params.ngram_cache)
, n_draft(n_draft)
, save_dynamic(save_dynamic)
@@ -2182,8 +2138,6 @@ struct common_speculative {
// which implementaion was used for a given seq_id
std::vector<common_speculative_impl *> impl_last;
std::vector<double> synth_probs;
};
static common_ngram_map get_common_ngram_map(
@@ -2362,101 +2316,6 @@ int32_t common_speculative_n_max(const common_params_speculative * spec) {
return n_max;
}
int32_t common_speculative_n_max(const common_speculative * spec) {
int32_t n_max = 0;
if (spec == nullptr) {
return n_max;
}
for (const auto & impl : spec->impls) {
n_max = std::max(n_max, std::max(0, impl->n_max));
}
return n_max;
}
std::vector<double> common_speculative_synth_rates_resolve(const common_params_speculative * spec, int32_t n_max) {
const bool has_length = spec->synth_len != -1.0;
const bool has_rates = !spec->synth_rates.empty();
if (!has_length && !has_rates) {
return {};
}
if (has_length && has_rates) {
throw std::invalid_argument("synthetic acceptance length and rates are mutually exclusive");
}
if (n_max <= 0) {
throw std::invalid_argument("synthetic acceptance requires at least one speculative token");
}
if (has_rates) {
const auto & rates = spec->synth_rates;
if (rates.size() != (size_t) n_max) {
throw std::invalid_argument(string_format(
"synthetic acceptance rates must contain %d values, got %zu", n_max, rates.size()));
}
for (size_t i = 0; i < rates.size(); ++i) {
if (!std::isfinite(rates[i]) || rates[i] < 0.0 || rates[i] > 1.0) {
throw std::invalid_argument("synthetic acceptance rates must be finite and within [0, 1]");
}
if (i > 0 && rates[i] > rates[i - 1]) {
throw std::invalid_argument("synthetic acceptance rates must be monotonically non-increasing");
}
}
return rates;
}
const double length = spec->synth_len;
const double length_max = (double) n_max + 1.0;
if (!std::isfinite(length) || length < 1.0 || length > length_max) {
throw std::invalid_argument(string_format(
"synthetic acceptance length must be finite and within [1, %.0f]", length_max));
}
double p = 0.0;
if (length == length_max) {
p = 1.0;
} else if (length > 1.0) {
double p_min = 0.0;
double p_max = 1.0;
for (int i = 0; i < 32; ++i) {
const double p_mid = 0.5 * (p_min + p_max);
double sum = 0.0;
double term = p_mid;
for (int32_t j = 0; j < n_max; ++j) {
sum += term;
term *= p_mid;
}
if (sum < length - 1.0) {
p_min = p_mid;
} else {
p_max = p_mid;
}
}
p = 0.5 * (p_min + p_max);
}
std::vector<double> rates;
rates.reserve(n_max);
double rate = p;
for (int32_t i = 0; i < n_max; ++i) {
rates.push_back(rate);
rate *= p;
}
return rates;
}
const std::vector<double> & common_speculative_get_synth_probs(const common_speculative * spec) {
GGML_ASSERT(spec);
return spec->synth_probs;
}
common_params common_base_params_to_speculative(const common_params & params) {
const bool has_draft = params.speculative.has_dft();
@@ -2467,22 +2326,11 @@ common_params common_base_params_to_speculative(const common_params & params) {
result.pooling_type = LLAMA_POOLING_TYPE_UNSPECIFIED;
if (has_draft) {
// default to global devices value
if (!params_spec.devices.empty()) {
result.devices = params_spec.devices;
}
result.devices = params_spec.devices;
result.model = params_spec.mparams;
result.n_gpu_layers = params_spec.n_gpu_layers;
result.tensor_buft_overrides = params_spec.tensor_buft_overrides;
// a draft pinned to a single device doesn't need the meta wrapper an inherited -sm tensor would give it
// (the device list is null-terminated, so a single device means size 2)
const size_t n_devs = std::count_if(params_spec.devices.begin(), params_spec.devices.end(),
[](ggml_backend_dev_t d) { return d != nullptr; });
if (n_devs == 1) {
result.split_mode = LLAMA_SPLIT_MODE_LAYER;
}
if (params_spec.cpuparams.n_threads > 0) {
result.cpuparams.n_threads = params_spec.cpuparams.n_threads;
result.cpuparams_batch.n_threads = params_spec.cpuparams_batch.n_threads;
@@ -2540,9 +2388,6 @@ common_speculative_init_result::common_speculative_init_result(
cparams.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
}
// the draft context holds as many tokens per sequence as the target context
cparams.n_ctx = llama_n_ctx(ctx_tgt);
// note: for small models maybe we can set this to the maximum possible draft from all speculative types
// the extra memory for small models is likely negligible?
cparams.n_rs_seq = 0;
@@ -2720,39 +2565,13 @@ common_speculative * common_speculative_init(common_params_speculative & params,
return nullptr;
}
common_speculative_ptr result(new common_speculative {
/* .dparams = */ common_speculative_draft_params_vec(n_seq),
/* .impls = */ std::move(impls),
/* .impl_last = */ std::vector<common_speculative_impl *>(n_seq, nullptr),
/* .synth_probs = */ {},
});
auto * result = new common_speculative {
/* .dparams = */ common_speculative_draft_params_vec(n_seq),
/* .impls = */ std::move(impls),
/* .impl_last = */ std::vector<common_speculative_impl *>(n_seq, nullptr)
};
const int32_t n_max_configured = common_speculative_n_max(&params);
const int32_t n_max_effective = common_speculative_n_max(result.get());
const auto rates = common_speculative_synth_rates_resolve(&params, n_max_effective);
std::vector<std::string> rates_str;
rates_str.reserve(rates.size());
result->synth_probs.reserve(rates.size());
double rate_prev = 1.0;
double acceptance_length = 1.0;
for (const double rate : rates) {
result->synth_probs.push_back(rate_prev > 0.0 ? rate / rate_prev : 0.0);
rates_str.push_back(string_format("%.6g", rate));
rate_prev = rate;
acceptance_length += rate;
}
if (!result->synth_probs.empty()) {
SPC_WRN("%s", "synthetic speculative acceptance is enabled for benchmarking; generated output is not valid\n");
if (n_max_effective != n_max_configured) {
SPC_WRN("synthetic acceptance draft limit was reduced from %d to %d by the initialized speculative implementations\n",
n_max_configured, n_max_effective);
}
SPC_INF("synthetic acceptance: n_max = %zu, mean length = %.6f, rates = [%s]\n",
rates.size(), acceptance_length, string_join(rates_str, ", ").c_str());
}
return result.release();
return result;
}
void common_speculative_free(common_speculative * spec) {
-9
View File
@@ -26,15 +26,6 @@ std::string common_speculative_type_to_str(enum common_speculative_type type);
// return the max number of draft tokens based on the speculative parameters
int32_t common_speculative_n_max(const common_params_speculative * spec);
// return the max number of draft tokens from the initialized implementations
int32_t common_speculative_n_max(const common_speculative * spec);
// validate and resolve the unconditional synthetic acceptance rates
std::vector<double> common_speculative_synth_rates_resolve(const common_params_speculative * spec, int32_t n_max);
// return the conditional synthetic acceptance probabilities
const std::vector<double> & common_speculative_get_synth_probs(const common_speculative * spec);
common_params common_base_params_to_speculative(const common_params & params);
struct common_speculative_output_limits {
-8
View File
@@ -54,7 +54,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"DeepseekV3ForCausalLM": "deepseek",
"DeepseekV32ForCausalLM": "deepseek",
"DFlashDraftModel": "qwen",
"DFlash2DraftModel": "qwen",
"Qwen3DSparkModel": "qwen",
"DSparkDraftModel": "qwen",
"DSparkSpeculator": "qwen",
@@ -124,7 +123,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"HunYuanMoEV1ForCausalLM": "hunyuan",
"HunYuanVLForConditionalGeneration": "hunyuan",
"HYV3ForCausalLM": "hunyuan",
"HYV4ForCausalLM": "hy_v4",
"IQuestCoderForCausalLM": "llama",
"InternLM2ForCausalLM": "internlm",
"InternLM3ForCausalLM": "internlm",
@@ -189,7 +187,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"NanbeigeForCausalLM": "nanbeige",
"NemotronForCausalLM": "nemotron",
"NemotronHForCausalLM": "nemotron",
"NemotronHPuzzleForCausalLM": "nemotron",
"NeoBERT": "bert",
"NeoBERTForSequenceClassification": "bert",
"NeoBERTLMHead": "bert",
@@ -238,8 +235,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"Qwen3_5ForConditionalGeneration": "qwen",
"Qwen3_5MoeForCausalLM": "qwen",
"Qwen3_5MoeForConditionalGeneration": "qwen",
"Qwen4ExpForCausalLM": "qwen4exp",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
"RND1": "qwen",
"RWForCausalLM": "falcon",
"RWKV6Qwen2ForCausalLM": "rwkv",
@@ -255,7 +250,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"SeedOssForCausalLM": "olmo",
"SmallThinkerForCausalLM": "smallthinker",
"SmolLM3ForCausalLM": "llama",
"Spark2_5ForCausalLM": "spark2_5",
"SolarOpenForCausalLM": "glm",
"StableLMEpochForCausalLM": "stablelm",
"StableLmForCausalLM": "stablelm",
@@ -289,7 +283,6 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"CogVLMForCausalLM": "cogvlm",
"DeepseekOCR2ForCausalLM": "deepseek",
"DeepseekOCRForCausalLM": "deepseek",
"DeepseekV4ForCausalLM": "deepseek",
"Dots3NoteForCausalLM": "dots3",
"Dots3NoteForConditionalGeneration": "dots3",
"DotsOCRForCausalLM": "dotsocr",
@@ -339,7 +332,6 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
"Qwen3_5ForConditionalGeneration": "qwen3vl",
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
"RADIOModel": "nemotron",
"Sarashina2VisionForCausalLM": "sarashina2",
"SmolVLMForConditionalGeneration": "smolvlm",
+3 -103
View File
@@ -130,8 +130,7 @@ class ModelBase:
sentence_transformers_dense_modules: bool = False,
target_model_dir: Path | None = None,
fuse_gate_up_exps: bool = False,
fp8_as_q8: bool = False,
fuse_qkv: bool = False):
fp8_as_q8: bool = False):
if type(self) is ModelBase or \
type(self) is TextModel or \
type(self) is MmprojModel:
@@ -154,15 +153,6 @@ class ModelBase:
self.fuse_gate_up_exps = fuse_gate_up_exps
self._gate_exp_buffer: dict[int, Tensor] = {}
self._up_exp_buffer: dict[int, Tensor] = {}
self.fuse_qkv = fuse_qkv
self._q_buffer: dict[int, Tensor] = {}
self._k_buffer: dict[int, Tensor] = {}
self._v_buffer: dict[int, Tensor] = {}
self._q_bias_buffer: dict[int, Tensor] = {}
self._k_bias_buffer: dict[int, Tensor] = {}
self._v_bias_buffer: dict[int, Tensor] = {}
self._fusable_qkv_weight_layers: set[int] = set()
self._fusable_qkv_bias_layers: set[int] = set()
self.hparams = ModelBase.load_hparams(self.dir_model, self.is_mistral_format) if hparams is None else hparams
self.model_tensors = self.index_tensors(remote_hf_model_id=remote_hf_model_id)
self.metadata_override = metadata_override
@@ -627,43 +617,6 @@ class ModelBase:
raise ValueError(f"Can not map tensor {name!r}")
return new_name
def prepare_qkv_fusion(self) -> None:
self._fusable_qkv_weight_layers.clear()
self._fusable_qkv_bias_layers.clear()
if not self.fuse_qkv or gguf.MODEL_TENSOR.ATTN_QKV not in gguf.MODEL_TENSORS[self.model_arch]:
return
qkv_types = {
gguf.MODEL_TENSOR.ATTN_Q,
gguf.MODEL_TENSOR.ATTN_K,
gguf.MODEL_TENSOR.ATTN_V,
}
weights: dict[int, set[gguf.MODEL_TENSOR]] = {}
biases: dict[int, set[gguf.MODEL_TENSOR]] = {}
for name in self.model_tensors:
mapped = self.tensor_map.get_type_and_name(name, try_suffixes=(".weight", ".bias"))
if mapped is None:
continue
tensor_type, new_name = mapped
if tensor_type not in qkv_types:
continue
bid = next((int(part) for part in new_name.split(".") if part.isdecimal()), None)
if bid is None:
continue
if new_name.endswith(".weight"):
weights.setdefault(bid, set()).add(tensor_type)
elif new_name.endswith(".bias"):
biases.setdefault(bid, set()).add(tensor_type)
for bid, weight_types in weights.items():
bias_types = biases.get(bid, set())
if weight_types == qkv_types and (not bias_types or bias_types == qkv_types):
self._fusable_qkv_weight_layers.add(bid)
if bias_types:
self._fusable_qkv_bias_layers.add(bid)
def set_gguf_parameters(self):
raise NotImplementedError("set_gguf_parameters() must be implemented in subclasses")
@@ -692,40 +645,6 @@ class ModelBase:
self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.FFN_UP_EXP, bid):
return []
# Handle Q/K/V tensor fusion if enabled
qkv_bid = next((int(part) for part in new_name.split(".") if part.isdecimal()), None) if self.fuse_qkv else None
if qkv_bid is not None:
is_bias = new_name.endswith('.bias')
suffix = '.bias' if is_bias else '.weight'
fusable_layers = self._fusable_qkv_bias_layers if is_bias else self._fusable_qkv_weight_layers
if qkv_bid not in fusable_layers:
return [(new_name, data_torch)]
buf_q = self._q_bias_buffer if is_bias else self._q_buffer
buf_k = self._k_bias_buffer if is_bias else self._k_buffer
buf_v = self._v_bias_buffer if is_bias else self._v_buffer
if self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_Q, qkv_bid, suffix):
buf_q[qkv_bid] = data_torch
elif self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_K, qkv_bid, suffix):
buf_k[qkv_bid] = data_torch
elif self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_V, qkv_bid, suffix):
buf_v[qkv_bid] = data_torch
if qkv_bid in buf_q and qkv_bid in buf_k and qkv_bid in buf_v:
q_data = buf_q.pop(qkv_bid)
k_data = buf_k.pop(qkv_bid)
v_data = buf_v.pop(qkv_bid)
fused_data = torch.cat([q_data, k_data, v_data], dim=0)
fused_name = self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_QKV, qkv_bid, suffix=suffix)
logger.info(f"Fused Q, K, V {suffix[1:]} into QKV for layer {qkv_bid}")
return [(fused_name, fused_data)]
if self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_Q, qkv_bid, suffix) or \
self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_K, qkv_bid, suffix) or \
self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.ATTN_V, qkv_bid, suffix):
return []
return [(new_name, data_torch)]
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
@@ -980,8 +899,6 @@ class ModelBase:
self.dequant_model()
self.prepare_qkv_fusion()
# Handle empty tensor_map for models with block_count=0 (like MobileNetV5)
if self.tensor_map.mapping:
max_name_len = max(len(s) for _, s in self.tensor_map.mapping.values()) + len(".weight,")
@@ -1089,16 +1006,12 @@ class ModelBase:
else:
raise ValueError(f"Unknown file type: {self.ftype.name}")
# a chunked tensor quantizes as one chunk at a time, while it is written
quantize = data.quantize if isinstance(data, gguf.LazyChunkedTensor) else (
lambda qtype, d=data: gguf.quants.quantize(d, qtype))
try:
data = quantize(data_qtype)
data = gguf.quants.quantize(data, data_qtype)
except gguf.QuantError as e:
logger.warning("%s, %s", e, "falling back to F16")
data_qtype = gguf.GGMLQuantizationType.F16
data = quantize(data_qtype)
data = gguf.quants.quantize(data, data_qtype)
shape = gguf.quant_shape_from_byte_shape(data.shape, data_qtype) if data.dtype == np.uint8 else data.shape
@@ -1110,13 +1023,6 @@ class ModelBase:
self.gguf_writer.add_tensor(new_name, data, raw_dtype=data_qtype)
qkv_buffers = (
self._q_buffer, self._k_buffer, self._v_buffer,
self._q_bias_buffer, self._k_bias_buffer, self._v_bias_buffer,
)
if any(qkv_buffers):
raise ValueError("QKV fusion did not consume all buffered tensors")
def set_type(self):
self.gguf_writer.add_type(gguf.GGUFType.MODEL)
@@ -1597,9 +1503,6 @@ class TextModel(ModelBase):
if chkhsh == "bba3b3366b646dbdded5dbc42d59598b849371afc42f7beafa914afaa5b70aa6":
# ref: https://huggingface.co/tencent/Hunyuan-4B-Instruct
res = "hunyuan-dense"
if chkhsh == "e6ddf9c6686791c12d698d34c31ab9be1fea9af5a3d9a6909783ab382198ae1c":
# ref: https://huggingface.co/tencent/Hy4-preview
res = "hy_v4"
if chkhsh == "a6b57017d60e6edb4d88ecc2845188e0eb333a70357e45dcc9b53964a73bbae6":
# ref: https://huggingface.co/tiiuae/Falcon-H1-0.5B-Base
res = "falcon-h1"
@@ -1633,9 +1536,6 @@ class TextModel(ModelBase):
if chkhsh == "9e454714343b69b99b71795c1d27a68c2a1d15dab111f4d353109f966af29da7":
# ref: https://huggingface.co/LiquidAI/LFM2.5-8B-A1B
res = "lfm2"
if chkhsh == "0a766d034107bc736a3f2dc4968fd62e54a3570f1454443e0c5a4cc6bd7941ed":
# ref: https://huggingface.co/XHToken/Spark-X2.5-1.7B
res = "spark2_5"
if chkhsh == "0ef9807a4087ebef797fc749390439009c3b9eda9ad1a097abbe738f486c01e5":
# ref: https://huggingface.co/meta-llama/Meta-Llama-3-8B
res = "llama-bpe"
-84
View File
@@ -578,8 +578,6 @@ class DeepseekV4Model(TextModel):
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
name, gen = item
if name.startswith(("aligner.", "image_")):
return None
if name.startswith("mtp."):
if not cls.mtp_only:
cls._skipped_mtp_tensors += 1
@@ -855,7 +853,6 @@ class DeepseekV4Model(TextModel):
"ffn_norm.weight": (gguf.MODEL_TENSOR.FFN_NORM, ".weight"),
"ffn.gate.weight": (gguf.MODEL_TENSOR.FFN_GATE_INP, ".weight"),
"ffn.gate.bias": (gguf.MODEL_TENSOR.FFN_EXP_PROBS_B, ".bias"),
"ffn.gate.bias_vl": (gguf.MODEL_TENSOR.FFN_EXP_PROBS_B_VL, ".bias"),
"ffn.gate.tid2eid": (gguf.MODEL_TENSOR.FFN_GATE_TID2EID, ".weight"),
"ffn.shared_experts.w1.weight": (gguf.MODEL_TENSOR.FFN_GATE_SHEXP, ".weight"),
"ffn.shared_experts.w2.weight": (gguf.MODEL_TENSOR.FFN_DOWN_SHEXP, ".weight"),
@@ -881,10 +878,6 @@ class DeepseekV4Model(TextModel):
if re.match(r"layers\.\d+\.ffn\.experts\.\d+\.w[123]\.(weight|scale)$", name):
return []
# hash layers route text tokens via tid2eid and image tokens via bias_vl; gate.bias is unused
if name.endswith(".ffn.gate.bias") and bid is not None and bid < self.hparams["num_hash_layers"]:
return []
tensor_key, suffix = self._map_dsv4_tensor_name(name, bid)
if tensor_key == gguf.MODEL_TENSOR.FFN_GATE_TID2EID:
return []
@@ -1007,13 +1000,6 @@ class DeepseekV4DSparkModel(DeepseekV4Model):
return self._DSPARK_ROOT_MAP[name]
return super()._map_dsv4_tensor_name(name, bid)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# the DFlash draft uses the plain exp-probs bias (ffn.gate.bias -> FFN_EXP_PROBS_B);
# the mtmd-only hash routing tensors (bias_vl, tid2eid) are not part of the DFLASH arch
if name.endswith(".ffn.gate.bias_vl"):
return
yield from super().modify_tensors(data_torch, name, bid)
def set_vocab(self):
if self.target_model_dir is None:
raise ValueError("DeepSeek-V4 DSpark requires --target-model-dir with the target tokenizer")
@@ -1032,73 +1018,3 @@ class DeepseekV4DSparkModel(DeepseekV4Model):
self.gguf_writer.add_block_size(self.hparams["dspark_block_size"])
self.gguf_writer.add_target_layers([layer + 1 for layer in self.hparams["dspark_target_layer_ids"]])
@ModelBase.register("DeepseekV4ForCausalLM")
@ModelBase.example("deepseek-ai/DeepSeek-V4-Flash-Vision-Exp")
class DeepseekV4FlashVisionModel(MmprojModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
assert self.hparams_vision is not None
# no preprocessor_config.json in the repo; normalization is (x/255 - 0.5) / 0.5
# ref: inference/image_processor.py (load_image)
self.preprocessor_config = {
"image_mean": [0.5, 0.5, 0.5],
"image_std": [0.5, 0.5, 0.5],
**self.preprocessor_config,
}
def get_vision_config(self) -> dict[str, Any] | None:
cfg = self.global_config
if cfg.get("vision_n_layers", 0) == 0:
raise ValueError("DeepseekV4FlashVisionModel requires vision_n_layers > 0 in the model config")
return {
"num_hidden_layers": cfg["vision_n_layers"],
"hidden_size": cfg["vision_dim"],
"num_attention_heads": cfg["vision_n_heads"],
"intermediate_size": cfg["vision_inter_dim"],
"patch_size": cfg["vision_patch_size"],
# dynamic resolution; only used for compat / warmup
"image_size": cfg["vision_patch_size"] * cfg["vision_downsample_ratio"] * 16,
"rope_theta": cfg.get("vision_rope_theta", 10000.0),
"downsample_ratio": cfg["vision_downsample_ratio"],
"min_pixels": cfg["vision_min_pixels"],
}
def set_gguf_parameters(self):
super().set_gguf_parameters()
assert self.hparams_vision is not None
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.DEEPSEEK4V)
# vision RMSNorm eps is the pytorch default, NOT the LLM's rms_norm_eps (1e-20)
# ref: inference/vision.py (RMSNorm)
self.gguf_writer.add_vision_attention_layernorm_eps(1e-6)
self.gguf_writer.add_vision_use_silu(True) # SwiGLU MLP
self.gguf_writer.add_vision_projector_scale_factor(self.hparams_vision["downsample_ratio"])
self.gguf_writer.add_vision_min_pixels(self.hparams_vision["min_pixels"])
# hardcoded on the C++ side (see PROJECTOR_TYPE_DEEPSEEK4V in clip.cpp)
# if future models use different values, add GGUF keys for those
assert self.global_config["vision_max_n_token"] == 384
assert self.global_config["vision_max_wh_ratio"] == 8
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
name, _ = item
if not (name.startswith(("vision.", "aligner.", "image_"))):
return None
return super().filter_tensors(item)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
assert self.hparams_vision is not None
if name == "vision.patch_embed.proj.weight":
# nn.Linear over flattened (3, p, p) patches == conv2d weight
p = self.hparams_vision["patch_size"]
data_torch = data_torch.reshape(data_torch.shape[0], 3, p, p)
if ".mlp.w1." in name:
# fused SwiGLU gate+up
gate, up = data_torch.chunk(2, dim=0)
yield from super().modify_tensors(gate, name.replace("w1", "w1_gate"), bid)
yield from super().modify_tensors(up, name.replace("w1", "w1_up"), bid)
return
yield from super().modify_tensors(data_torch, name, bid)
+5 -44
View File
@@ -112,38 +112,12 @@ class GlmOCRModel(Glm4Model):
@ModelBase.example("zai-org/GLM-4.5-Air")
class Glm4MoeModel(TextModel):
model_arch = gguf.MODEL_ARCH.GLM4_MOE
supports_mtp_export = True
_n_main_layers: int | None = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.no_mtp:
self.block_count += self.hparams.get("num_nextn_predict_layers", 0)
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
def index_tensors(self, remote_hf_model_id: str | None = None):
hparams = {**self.hparams, **self.hparams.get("text_config", {})}
key = next((k for k in ["n_layers", "num_hidden_layers", "n_layer", "num_layers"] if k in hparams), None)
type(self)._n_main_layers = hparams.get(key)
return super().index_tensors(remote_hf_model_id=remote_hf_model_id)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
if (titem := super().filter_tensors(item)) is None:
return None
name, gen = titem
assert cls._n_main_layers is not None
is_mtp = (m := re.match(r"model\.layers\.(\d+)\.", name)) is not None and int(m.group(1)) >= cls._n_main_layers
if is_mtp and cls.no_mtp:
return None
if cls.mtp_only and not is_mtp and name not in (
"model.embed_tokens.weight", "model.norm.weight", "lm_head.weight",
):
return None
return name, gen
# GLM4_MOE has num_hidden_layers + 1 actual layers (including NextN layer)
self.block_count = self.hparams["num_hidden_layers"] + self.hparams.get("num_nextn_predict_layers", 0)
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
def set_vocab(self):
return self._set_vocab_glm()
@@ -179,22 +153,10 @@ class Glm4MoeModel(TextModel):
if (norm_topk_prob := self.hparams.get("norm_topk_prob")) is not None:
self.gguf_writer.add_expert_weights_norm(norm_topk_prob)
if not self.no_mtp and (num_nextn_predict_layers := self.hparams.get("num_nextn_predict_layers")) is not None:
# NextN/MTP prediction layers
if (num_nextn_predict_layers := self.hparams.get("num_nextn_predict_layers")) is not None:
self.gguf_writer.add_nextn_predict_layers(num_nextn_predict_layers)
def prepare_metadata(self, vocab_only: bool):
from_dir = self.fname_out.is_dir()
super().prepare_metadata(vocab_only=vocab_only)
if not self.mtp_only or not from_dir:
return
output_type: str = self.ftype.name.partition("_")[2]
fname_default: str = gguf.naming_convention(
self.metadata.name, self.metadata.basename, self.metadata.finetune,
self.metadata.version, size_label=None, output_type=output_type, model_type=None)
self.fname_out = self.fname_out.parent / f"mtp-{fname_default}.gguf"
_experts: list[dict[str, Tensor]] | None = None
# note: unlike GLM4V non-MoE, we don't need to permute Q/K here since GLM4V_MOE uses Neox ordering already
@@ -386,7 +348,6 @@ class GlmMoeDsaModel(DeepseekV2Model):
@ModelBase.example("upstage/Solar-Open-100B")
class SolarOpenModel(Glm4MoeModel):
model_arch = gguf.MODEL_ARCH.GLM4_MOE
supports_mtp_export = False
def set_vocab(self):
from transformers import AutoTokenizer
-244
View File
@@ -1,244 +0,0 @@
from __future__ import annotations
import re
from typing import Iterable
import torch
from .base import ModelBase, gguf, logger
from .deepseek import DeepseekV2Model
def split_gate_up(weight: torch.Tensor, moe_intermediate_size: int):
"""Split a fused stacked gate_up expert tensor into (gate, up).
weight: [n_expert, 2*moe_intermediate_size, hidden] (gate first, up second).
Returns (gate, up) each [n_expert, moe_intermediate_size, hidden].
"""
assert weight.shape[1] == 2 * moe_intermediate_size, f"{weight.shape[1]} != 2*{moe_intermediate_size}"
gate = weight[:, :moe_intermediate_size, :].contiguous()
up = weight[:, moe_intermediate_size:, :].contiguous()
return gate, up
@ModelBase.register("HYV4ForCausalLM")
@ModelBase.example("tencent/Hy4-preview")
class HYV4Model(DeepseekV2Model):
"""HY_V4: DeepSeek-V3 style MLA + MoE with iHC, a gated MLA output and a learnable sink.
Reuses DeepseekV2Model for the vocab and the MLA metadata, but overrides the tensor mapping
because HY_V4 ships pre-stacked / fused experts plus extra iHC, gate and sink tensors. The
rope rows are mapped straight through (no permute) - the graph rotates consecutive pairs.
DSA is supported: indexer weights are exported for the layers marked "full" in indexer_types.
"shared" layers reuse the top-k of the last preceding full layer at inference time, so they
carry no indexer weights.
MTP (num_nextn_predict_layers) is dropped, so the GGUF cannot be used for speculative
decoding. The reference only runs the MTP layers while training or while speculating, so they
cannot change single-token logits.
"""
model_arch = gguf.MODEL_ARCH.HY_V4
merge_expert = False
# tensors a "full" indexer layer must carry
INDEXER_SUFFIXES = frozenset({
"self_attn.indexer.wq_b.weight",
"self_attn.indexer.wk.weight",
"self_attn.indexer.k_norm.weight",
"self_attn.indexer.k_norm.bias",
"self_attn.indexer.weights_proj.weight",
})
@classmethod
def filter_tensors(cls, item):
# drop MTP here, not in modify_tensors, so the weights are never read
if item[0].startswith("model.mtp_layers."):
return None
return super().filter_tensors(item)
def _check_indexer_hparams(self):
for key in ("index_n_heads", "index_head_dim", "index_topk"):
if key not in self.hparams:
raise ValueError(f"HY_V4 has DSA layers but no {key}")
def indexer_is_full(self) -> list[bool] | None:
"""Per-layer indexer ownership, or None when the checkpoint has no DSA.
indexer_types entries are "full" (owns an indexer) or "shared" (reuses the preceding
full layer's top-k). Missing indexer_types with sparse layers means every sparse layer
owns one.
"""
hparams = self.hparams
n_layer = hparams["num_hidden_layers"]
indexer_types = hparams.get("indexer_types")
# the reference drives DSA off indexer_types alone; layer_types is only a fallback for
# checkpoints predating it (it was renamed to deepseek_sparse_attention upstream)
if indexer_types is None:
layer_types = hparams.get("layer_types") or []
sparse = {"sparse_attention", "deepseek_sparse_attention"}
if not any(t in sparse for t in layer_types):
return None
if len(layer_types) < n_layer:
raise ValueError(f"HY_V4 layer_types has {len(layer_types)} entries, need {n_layer}")
self._check_indexer_hparams()
return [t in sparse for t in layer_types[:n_layer]]
self._check_indexer_hparams()
if len(indexer_types) < n_layer:
raise ValueError(f"HY_V4 indexer_types has {len(indexer_types)} entries, need {n_layer}")
unknown = {t for t in indexer_types[:n_layer]} - {"full", "shared"}
if unknown:
raise ValueError(f"HY_V4 unknown indexer_types values: {sorted(unknown)}")
is_full = [t == "full" for t in indexer_types[:n_layer]]
if is_full and not is_full[0]:
raise ValueError("HY_V4 layer 0 must be indexer_types 'full' (nothing precedes it to share)")
return is_full
def set_gguf_parameters(self):
hparams = self.hparams
# HY4 has n_group == topk_group == 1 (no group routing). Drop the keys so the base does
# not emit expert_group_count/used; llama.cpp then takes the ungrouped MoE path.
if hparams.get("n_group") == 1 and hparams.get("topk_group") == 1:
hparams.pop("n_group", None)
hparams.pop("topk_group", None)
# HY_V4 config expresses dense/sparse layers via mlp_layer_types, but DeepseekV2Model
# needs first_k_dense_replace. Derive it as the contiguous leading "dense" block
# (the real config.json also carries first_k_dense_replace; prefer it when present,
# but assert the two agree so a mismatch fails loudly).
mlp_types = hparams.get("mlp_layer_types")
explicit = hparams.get("first_k_dense_replace")
derived = None
if mlp_types is not None:
lead = 0
for t in mlp_types:
if t == "dense":
lead += 1
else:
break
if any(t == "dense" for t in mlp_types[lead:]):
raise NotImplementedError("HY_V4 converter expects a contiguous leading dense block")
derived = lead
if explicit is not None and derived is not None and explicit != derived:
raise ValueError(
f"HY_V4 first_k_dense_replace ({explicit}) disagrees with mlp_layer_types "
f"leading-dense count ({derived})"
)
if explicit is None:
if derived is None:
raise ValueError("HY_V4 needs first_k_dense_replace or mlp_layer_types to place dense layers")
hparams["first_k_dense_replace"] = derived
# reuse DeepseekV2 MLA + MoE metadata (forces num_key_value_heads=1, writes q/kv lora,
# key/value lengths, expert counts, weights scale/norm, rope dims, etc.)
super().set_gguf_parameters()
# HY4 uses DeepSeek-V3 sigmoid routing with e_score_correction_bias. The config has no
# scoring_func key, so the base does not write a gating func; set it explicitly.
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
# routed-expert SwiGLU logits clamp (only routed experts; shared/dense are not clamped,
# so swiglu_clamp_shexp is intentionally not written). 0.0 disables the clamp.
swiglu_limit = float(hparams.get("swiglu_limit", 0.0) or 0.0)
if swiglu_limit > 0.0:
self.gguf_writer.add_swiglu_clamp_exp([swiglu_limit] * self.block_count)
# iHC (independent Hyper-Connections)
self.gguf_writer.add_hyper_connection_count(hparams["hc_mult"])
self.gguf_writer.add_hyper_connection_epsilon(hparams["hc_eps"])
self.gguf_writer.add_hyper_connection_magnitude(hparams["hc_magnitude"])
# is_full is written explicitly; the graph must not infer it from tensor presence
is_full = self.indexer_is_full()
if is_full is not None:
self.gguf_writer.add_indexer_head_count(hparams["index_n_heads"])
self.gguf_writer.add_indexer_key_length(hparams["index_head_dim"])
self.gguf_writer.add_indexer_top_k(hparams["index_topk"])
self.gguf_writer.add_indexer_types(is_full)
logger.info(
"HY_V4 DSA: %d/%d layers own an indexer (top_k=%d, n_heads=%d, head_dim=%d)",
sum(is_full), len(is_full), hparams["index_topk"],
hparams["index_n_heads"], hparams["index_head_dim"],
)
if hparams.get("num_nextn_predict_layers", 0):
logger.warning(
"HY_V4: dropping %d MTP (nextn) layer(s) - the reference runs them only under "
"training / speculative decoding. This GGUF cannot be used for speculative decoding.",
hparams["num_nextn_predict_layers"],
)
def prepare_tensors(self):
# Hy4-preview for some reason has num_key_value_heads equal to 8, so override it here
# without this conversion/deepseek.py fails on assert
self.hparams["num_key_value_heads"] = self.hparams["num_attention_heads"]
# validate before the base materializes tensors, so a mismatch fails early
is_full = self.indexer_is_full()
if is_full is not None:
present: dict[int, set[str]] = {}
for name in self.model_tensors:
m = re.match(r"model\.layers\.(\d+)\.(self_attn\.indexer\..+)$", name)
if m:
present.setdefault(int(m.group(1)), set()).add(m.group(2))
for il, expect_full in enumerate(is_full):
seen = present.get(il, set())
if expect_full and seen != self.INDEXER_SUFFIXES:
raise ValueError(
f"HY_V4 layer {il} is indexer_types 'full' but is missing indexer tensors: "
f"{sorted(self.INDEXER_SUFFIXES - seen)}"
)
if not expect_full and seen:
raise ValueError(
f"HY_V4 layer {il} is indexer_types 'shared' but carries indexer tensors: "
f"{sorted(seen)}"
)
super().prepare_tensors()
def tensor_force_quant(self, name, new_name, bid, n_dims):
# iHC mixing matrices are 2D .weight tensors that the reference keeps in fp32
# (_keep_in_fp32_modules_strict). 1D tensors (hc_base/scale, attn_sinks,
# e_score_correction_bias) and the router (FFN_GATE_INP) are already forced F32 by the
# base rules. Force the HC *_fn matrices here.
if new_name.endswith(("hc_attn_fn.weight", "hc_ffn_fn.weight", "output_hc_fn.weight")):
return gguf.GGMLQuantizationType.F32
# indexer k_norm is fp32 in the reference; the base rules already cover
# *_norm.weight and INDEXER_PROJ, but not this bias
if self.match_model_tensor_name(new_name, gguf.MODEL_TENSOR.INDEXER_K_NORM, bid, suffix=".bias"):
return gguf.GGMLQuantizationType.F32
# enable_lm_head_fp32: mirror the reference fp32 LM-head matmul by keeping output F32.
if new_name == "output.weight" and self.hparams.get("enable_lm_head_fp32", False):
return gguf.GGMLQuantizationType.F32
return super().tensor_force_quant(name, new_name, bid, n_dims)
def modify_tensors(self, data_torch: torch.Tensor, name: str, bid: int | None) -> Iterable[tuple[str, torch.Tensor]]:
hparams = self.hparams
moe_inter = hparams["moe_intermediate_size"]
tn = self.format_tensor_name
# fused stacked experts: split gate_up into gate/up
if name.endswith("mlp.experts.gate_up_proj"):
gate, up = split_gate_up(data_torch, moe_inter)
yield from super().modify_tensors(gate, tn(gguf.MODEL_TENSOR.FFN_GATE_EXP, bid), bid)
yield from super().modify_tensors(up, tn(gguf.MODEL_TENSOR.FFN_UP_EXP, bid), bid)
return
# add .weight suffixes
if name.endswith("mlp.experts.down_proj") or name.endswith(".self_attn.learnable_sink_param"):
name += ".weight"
if re.search(r"\.hc_head\.hc_head_(?:fn|base|scale)$", name):
name += ".weight"
if re.search(r"\.hc_(?:attn|mlp)_layer\.hc_pre\.hc_(?:fn|base|scale)$", name):
name += ".weight"
yield from super().modify_tensors(data_torch, name, bid)
+1 -1
View File
@@ -25,7 +25,7 @@ class MiniMaxText01Model(TextModel):
# they get in the way of the token sampling process and must be suppressed
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
tokenizer_vocab_size = tokenizer.vocab_size # ty: ignore[unresolved-attribute]
tokenizer_vocab_size = tokenizer.vocab_size
with open(self.dir_model / "model.safetensors.index.json", "r", encoding="utf-8") as f:
weight_map = json.load(f)["weight_map"]
+1 -1
View File
@@ -37,7 +37,7 @@ class MuseGlimmerModel(TextModel):
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained(self.dir_model)
eot_id = tok.convert_tokens_to_ids("<|eot|>") # ty: ignore[unresolved-attribute]
eot_id = tok.convert_tokens_to_ids("<|eot|>")
if isinstance(eot_id, int) and eot_id >= 0:
self.gguf_writer.add_eot_token_id(eot_id)
+3 -98
View File
@@ -5,7 +5,6 @@ from typing import Any, Callable, Iterable, TYPE_CHECKING
import torch
if TYPE_CHECKING:
from pathlib import Path
from torch import Tensor
from .base import MmprojModel, ModelBase, TextModel, gguf, logger
@@ -202,11 +201,6 @@ class NemotronHModel(GraniteHybridModel):
model_arch = gguf.MODEL_ARCH.NEMOTRON_H
is_moe: bool = False
supports_mtp_export = True
_experts: list[dict[str, Tensor]] | None = None
_SSM_LAYER_TYPES = {"mamba", "linear_attention"}
_ATTN_LAYER_TYPES = {"attention", "full_attention"}
_MLP_LAYER_TYPES = {"moe"}
def __init__(self, *args, **kwargs):
# We have to determine the correct model architecture (MoE vs non-MoE) before
@@ -248,8 +242,8 @@ class NemotronHModel(GraniteHybridModel):
self._ssm_layers = [i for i, val in enumerate(pattern) if val == "M"]
self._mlp_layers = [i for i, val in enumerate(pattern) if val == ("E" if self.is_moe else "-")]
else:
self._ssm_layers = [i for i, val in enumerate(pattern) if val in self._SSM_LAYER_TYPES]
self._mlp_layers = [i for i, val in enumerate(pattern) if val in self._MLP_LAYER_TYPES]
self._ssm_layers = [i for i, val in enumerate(pattern) if val == "mamba"]
self._mlp_layers = [i for i, val in enumerate(pattern) if val == "moe"]
# `--no-mtp` drops it entirely; `--mtp` exports only the MTP head
self._mtp_bid: int | None = None
@@ -278,7 +272,7 @@ class NemotronHModel(GraniteHybridModel):
if isinstance(pattern, str):
return [i for i, val in enumerate(pattern) if val == "*"]
return [i for i, val in enumerate(pattern) if val in self._ATTN_LAYER_TYPES]
return [i for i, val in enumerate(pattern) if val == "attention"]
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
@@ -304,10 +298,6 @@ class NemotronHModel(GraniteHybridModel):
)
if not keep:
return None
# PEFT names adapter tensors using model.layers.*, while Nemotron-H checkpoints
# and the GGUF tensor map use backbone.layers.*
if name.startswith("model.layers.") and ".mixer." in name:
name = name.replace("model.layers.", "backbone.layers.", 1)
return super().filter_tensors((name, gen))
def prepare_metadata(self, vocab_only: bool):
@@ -515,88 +505,3 @@ class NemotronHModel(GraniteHybridModel):
experts = [k for d in self._experts for k in d.keys()]
if len(experts) > 0:
raise ValueError(f"Unprocessed experts: {experts}")
@ModelBase.register("NemotronHPuzzleForCausalLM")
@ModelBase.example("nvidia/NVIDIA-Nemotron-Labs-3-Puzzle-75B-A9B-BF16")
class NemotronHPuzzleModel(NemotronHModel):
"""NVIDIA Puzzle: NemotronH with a per-block MoE config (block_configs).
The checkpoint also ships an MTP draft head (mtp.safetensors). It is skipped
here: there is no Puzzle MTP inference path in tree, and the head is laid out
by mtp_block_configs rather than the mtp.layers.* form NemotronHModel maps."""
model_arch = gguf.MODEL_ARCH.NEMOTRON_H_MOE
is_moe: bool = True
supports_mtp_export = False
def __init__(self, dir_model: "Path", *args, **kwargs):
hparams = dict(kwargs.pop("hparams", None) or ModelBase.load_hparams(dir_model, self.is_mistral_format))
self.block_configs: list[dict] = hparams["block_configs"]
self.n_layer_trunk = len(self.block_configs)
# block_configs carries the per-block MoE shape, and is the authority on the
# block pattern too: the layers_block_type the HF config wrapper computes is
# not sized to it.
hparams["num_hidden_layers"] = self.n_layer_trunk
hparams["layers_block_type"] = [bc["block_type"] for bc in self.block_configs]
self.model_arch = gguf.MODEL_ARCH.NEMOTRON_H_MOE
# Bypass NemotronHModel.__init__: it assumes a flat num_experts_per_tok /
# moe_intermediate_size and a layers_block_type sized to block_count, neither
# of which hold for Puzzle's per-block config.
GraniteHybridModel.__init__(self, dir_model, *args, hparams=hparams, **kwargs)
self.head_dim = self.find_hparam(["head_dim", "attention_head_dim"])
self.d_inner = self.find_hparam(["num_heads"]) * self.d_model
# NemotronHModel.__init__ folds an MTP block into block_count when the
# config carries num_nextn_predict_layers; Puzzle's config does, but its
# head has a different layout and no inference path, so stay opted out.
self._mtp_bid = None
def set_gguf_parameters(self):
GraniteHybridModel.set_gguf_parameters(self)
head_dim = self.head_dim
if head_dim is None:
raise ValueError("Could not find the attention head dim in config")
self.gguf_writer.add_key_length(head_dim)
self.gguf_writer.add_value_length(head_dim)
ffn_lengths = [bc.get("moe_intermediate_size") or 0 for bc in self.block_configs]
experts_used = [bc.get("num_experts_per_tok") or 0 for bc in self.block_configs]
self.gguf_writer.add_feed_forward_length(ffn_lengths)
self.gguf_writer.add_expert_feed_forward_length(ffn_lengths)
self.gguf_writer.add_expert_used_count(experts_used)
self.gguf_writer.add_expert_shared_feed_forward_length(self.hparams["moe_shared_expert_intermediate_size"])
self.gguf_writer.add_expert_count(self.hparams["n_routed_experts"])
self.gguf_writer.add_expert_shared_count(self.hparams["n_shared_experts"])
self.gguf_writer.add_expert_weights_norm(self.hparams["norm_topk_prob"])
self.gguf_writer.add_expert_weights_scale(self.hparams["routed_scaling_factor"])
self.gguf_writer.add_expert_group_count(self.hparams["n_group"])
self.gguf_writer.add_moe_latent_size(self.hparams["moe_latent_size"])
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# The official BF16 checkpoint (NVIDIA-Nemotron-Labs-3-Puzzle-75B-A9B-BF16)
# names the trunk "model.*" (model.layers.*, model.embeddings, model.norm_f)
# where the original release used the NemotronH-style "backbone.*", and spells
# the router bias "e_score_correction_bias" instead of "e_score_correction.bias";
# normalize so both convert identically.
if name.startswith("model."):
name = "backbone." + name[len("model."):]
if name.endswith("mixer.gate.e_score_correction_bias"):
name = name[: -len("e_score_correction_bias")] + "e_score_correction.bias"
yield from super().modify_tensors(data_torch, name, bid)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
# Drop the MTP head unconditionally; see the class docstring.
if item[0].startswith("mtp."):
return None
return super().filter_tensors(item)
+6 -81
View File
@@ -379,13 +379,6 @@ class Qwen3NextModel(_QwenMtpMixin, Qwen2MoeModel):
self.gguf_writer.add_ssm_group_count(self.hparams["linear_num_key_heads"])
self.gguf_writer.add_ssm_time_step_rank(self.hparams["linear_num_value_heads"])
self.gguf_writer.add_ssm_inner_size(self.hparams["linear_value_head_dim"] * self.hparams["linear_num_value_heads"])
if (layer_types := self.hparams.get("layer_types")) is not None:
n_layer = self.hparams["num_hidden_layers"]
if len(layer_types) != n_layer:
raise ValueError(f"layer_types has {len(layer_types)} entries, expected num_hidden_layers ({n_layer})")
recurrent = [t == "linear_attention" for t in layer_types]
recurrent += [False] * (self.block_count - n_layer)
self.gguf_writer.add_recurrent_layers(recurrent)
self.gguf_writer.add_full_attention_interval(self.hparams.get("full_attention_interval", 4))
if (rope_dim := self.hparams.get("head_dim")) is None:
rope_dim = self.hparams["hidden_size"] // self.hparams["num_attention_heads"]
@@ -646,7 +639,7 @@ class Qwen3_5MoeTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
model_arch = gguf.MODEL_ARCH.QWEN35MOE
@ModelBase.register("DFlashDraftModel", "DFlash2DraftModel")
@ModelBase.register("DFlashDraftModel")
@ModelBase.example("z-lab/Qwen3.5-9B-DFlash")
class DFlashModel(Qwen3Model):
model_arch = gguf.MODEL_ARCH.DFLASH
@@ -685,98 +678,34 @@ class DFlashModel(Qwen3Model):
def set_gguf_parameters(self):
super().set_gguf_parameters()
dflash_config = self.hparams.get("dflash_config", {})
block_size = dflash_config.get("block_size", self.hparams.get("block_size", 16))
block_size = self.hparams.get("block_size", 16)
self.gguf_writer.add_block_size(block_size)
if "conv_kernel_size" in dflash_config:
self.gguf_writer.add_conv_kernel_size(int(dflash_config["conv_kernel_size"]))
self.gguf_writer.add_conv_group_size(int(dflash_config["conv_group_size"]))
self.gguf_writer.add_selector_rank(int(dflash_config["selector_rank"]))
self.gguf_writer.add_selector_top_k(int(dflash_config["selector_top_k"]))
output_multiplier = dflash_config.get(
"output_multiplier", self.hparams.get("output_multiplier")
)
if output_multiplier is not None:
self.gguf_writer.add_logit_scale(float(output_multiplier))
softcap = dflash_config.get(
"final_logit_softcapping", self.hparams.get("final_logit_softcapping")
)
if softcap is not None and float(softcap) > 0:
self.gguf_writer.add_final_logit_softcapping(float(softcap))
embedding_scale = dflash_config.get(
"input_embedding_scale", self.hparams.get("input_embedding_scale")
)
if embedding_scale is not None:
self.gguf_writer.add_embedding_scale(float(embedding_scale))
dflash_config = self.hparams.get("dflash_config", {})
target_layer_ids = dflash_config.get("target_layer_ids", [])
if target_layer_ids:
extract_layer_ids = [i + 1 for i in target_layer_ids]
self.gguf_writer.add_target_layers(extract_layer_ids)
use_sliding_window = self.hparams.get("use_sliding_window", False) or dflash_config.get("use_swa", False)
sliding_window = dflash_config.get("swa_window_size") or self.hparams.get("sliding_window")
use_sliding_window = self.hparams.get("use_sliding_window", False)
sliding_window = self.hparams.get("sliding_window")
layer_types = self.hparams.get("layer_types")
if use_sliding_window and sliding_window and layer_types:
is_swa = [lt == "sliding_attention" for lt in layer_types]
self.gguf_writer.add_sliding_window(sliding_window)
self.gguf_writer.add_sliding_window_pattern(is_swa)
causal = self.hparams.get("is_causal")
if causal is None:
causal = dflash_config.get("causal")
if causal is not None:
self.gguf_writer.add_causal_attention(bool(causal))
# M-RoPE target: the draft ropes on the temporal dim only, so write
# degenerate sections [n_rot/2, 0, 0, 0]
if self._target_uses_mrope():
head_dim = self.hparams.get("head_dim") or self.hparams["hidden_size"] // self.hparams["num_attention_heads"]
self.gguf_writer.add_rope_dimension_sections([head_dim // 2, 0, 0, 0])
def _target_uses_mrope(self) -> bool:
if self.target_model_dir is None:
return False
with open(self.target_model_dir / "config.json", "r", encoding="utf-8") as f:
cfg = json.load(f)
cfg = cfg.get("text_config", cfg)
rope = cfg.get("rope_parameters") or cfg.get("rope_scaling") or {}
return "mrope_section" in rope
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
name, gen = item
if not name.startswith("model."):
name = "model." + name
if "sink" in name and not name.endswith(".weight"):
name += ".weight"
return super().filter_tensors((name, gen))
_ROPE_PERMUTE_SUFFIXES = (
"self_attn.q_proj.weight",
"self_attn.k_proj.weight",
"self_attn.q_norm.weight",
"self_attn.k_norm.weight",
)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name == "model.embed_tokens.weight" and not self.hparams.get("has_embed_tokens", True):
return
# interleaved-rope checkpoints (rope_is_neox_style = false) -> NeoX layout: per head, even dims first then odd
if not self.hparams.get("rope_is_neox_style", True) and name.endswith(self._ROPE_PERMUTE_SUFFIXES):
head_dim = self.hparams["head_dim"]
shape = data_torch.shape
data_torch = data_torch.reshape(-1, head_dim // 2, 2, *shape[1:]).transpose(1, 2).reshape(shape)
if name in (
"model.candidate_selector.predecessor_codebook",
"model.candidate_selector.successor_codebook",
):
name += ".weight"
yield from super().modify_tensors(data_torch, name, bid)
@@ -830,10 +759,6 @@ class DSparkModel(DFlashModel):
super().set_gguf_parameters()
self.gguf_writer.add_sample_from_anchor(self._sample_from_anchor)
# confidence head is optional: vanilla-markov exports ship without it
has_conf = any("confidence_head.proj" in name for name in self.model_tensors)
self.gguf_writer.add_has_confidence_head(has_conf)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
if item[0] == "t2d": # not used at runtime
@@ -852,7 +777,7 @@ class DSparkModel(DFlashModel):
self._d2t = data_torch
return
if self._n_vocab_draft == self.hparams["vocab_size"] and name.endswith("lm_head.weight"):
if self._n_vocab_draft == self.hparams["vocab_size"] and name.endswith(("embed_tokens.weight", "lm_head.weight")):
return
# interleaved-rope checkpoints (rope_is_neox_style = false) -> NeoX layout: per head, even dims first then odd
-4
View File
@@ -276,10 +276,6 @@ class Qwen3TTSSpeakerEncoderModel(MmprojModel):
# ConvTranspose1d kernels: only F16/F32 are implemented, no BF16
if new_name.endswith(".conv.weight") and (".up.blk." in new_name or ".dac.blk." in new_name):
return gguf.GGMLQuantizationType.F32
# the code predictor FFN intermediate peaks around 1.5e5, above the F16 range, and mul_mat
# casts its input to the weight type
if new_name.startswith("a.gen.code.blk.") and new_name.endswith(".ffn_down.weight"):
return gguf.GGMLQuantizationType.F32
return super().tensor_force_quant(name, new_name, bid, n_dims)
@classmethod
-195
View File
@@ -1,195 +0,0 @@
from __future__ import annotations
from typing import Iterable, cast
import torch
from torch import Tensor
import gguf
import numpy as np
from .base import ModelBase
from .qwen import _LinearAttentionVReorderBase, _Qwen35MRopeMixin
from .qwen3vl import Qwen3VLVisionModel
@ModelBase.register("Qwen4ExpForConditionalGeneration", "Qwen4ExpForCausalLM")
@ModelBase.example("Qwen/Qwen3.8-Flash-Next")
class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
"""Qwen3.8-Flash-Next.
Shares the Qwen3.5 gated delta net and interleaved mrope, and adds three things:
hyper-connections in place of every layer norm, QSA sparse attention on the full
attention layers, and PLE n-gram hash embeddings on a single layer.
"""
model_arch = gguf.MODEL_ARCH.QWEN4EXP
# the MTP block is a separate draft head; vLLM drops it too
supports_mtp_export = False
no_mtp = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# only the shard names, so the table itself is never held
self._ple_shards: dict[int, str] = {}
self._ple_row_dim: int | None = None
def _read_hash_constants(self, suffix: str) -> list[int]:
"""Read an int64 PLE constant straight from the checkpoint.
prepare_tensors() casts every non-float dtype to float32 before
modify_tensors() sees it (base.py), which would silently round these
45-bit multipliers. Reading the lazy tensor here bypasses that.
"""
for name, gen in self.model_tensors.items():
if name.endswith(suffix):
t = gen()
if t.dtype != torch.int64:
t = t.to(torch.int64)
return [int(x) for x in t.tolist()]
raise ValueError(f"PLE constant {suffix!r} missing from the checkpoint")
def set_gguf_parameters(self):
super().set_gguf_parameters()
hp = self.hparams
self.gguf_writer.add_hyper_connection_count(hp["hc_count"])
self.gguf_writer.add_hyper_connection_low_rank(hp["hc_lowrank"])
n_layer = hp["num_hidden_layers"]
self.gguf_writer.add_indexer_head_count(hp["indexer_n_heads"])
self.gguf_writer.add_indexer_key_length(hp["indexer_head_dim"])
self.gguf_writer.add_indexer_top_k(hp["indexer_budget"])
ratio = hp["indexer_compress_ratio"]
layer_types = hp["layer_types"]
self.gguf_writer.add_attention_compress_ratios(
[ratio if layer_types[i] == "full_attention" else 0 for i in range(n_layer)]
)
# ple_layer_ids is 1-based in the HF config; empty means no n-gram table,
# so emit no PLE keys rather than optional ones
ple_layers = [i - 1 for i in hp["ple_layer_ids"]]
if not ple_layers:
return
self.gguf_writer.add_ple_layers(ple_layers)
self.gguf_writer.add_ple_ngram_size(hp["ngram_size"])
self.gguf_writer.add_ple_heads_per_ngram(hp["heads_per_ngram"])
self.gguf_writer.add_ple_conv_kernel(hp["ple_conv_kernel_size"])
self.gguf_writer.add_ple_eos_token_id(self._eos_token_id())
# an image is decoded as an embeddings-only batch, so the graph has no placeholder
# ids to hash; carry the id and let it stand in for those positions
_img = self._image_token_id()
if _img is not None:
self.gguf_writer.add_ple_image_token_id(int(_img))
if self._ple_row_dim is not None:
self.gguf_writer.add_embedding_length_per_layer_input(self._ple_row_dim)
self.gguf_writer.add_ple_layer_multipliers(
self._read_hash_constants("ple_embedding.layer_multipliers"))
self.gguf_writer.add_ple_head_offsets(
self._read_hash_constants("ple_embedding.ngram_heads_offsets"))
self.gguf_writer.add_ple_head_vocab_sizes(
self._read_hash_constants("ple_embedding.ngram_heads_vocab_sizes"))
def _image_token_id(self) -> int | None:
img = self.hparams.get("image_token_id")
return None if img is None else int(img)
def _eos_token_id(self) -> int:
eos = self.hparams.get("eos_token_id")
if isinstance(eos, list):
# the PLE hash resets n-grams on the primary EOS
return int(eos[-1])
if eos is None:
raise ValueError("eos_token_id is required: the PLE hash resets its n-grams on it")
return int(eos)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# int64 hash constants must stay exact; 1-D tensors force F32, so use KV
if name.endswith("ple_embedding.layer_multipliers"):
self._ple_multipliers = [int(x) for x in data_torch.tolist()]
return []
if name.endswith("ple_embedding.ngram_heads_offsets"):
self._ple_head_offsets = [int(x) for x in data_torch.tolist()]
return []
if name.endswith("ple_embedding.ngram_heads_vocab_sizes"):
self._ple_head_vocab_sizes = [int(x) for x in data_torch.tolist()]
return []
if ".ngram_embedding.shard_" in name:
return self._place_ple_shard(data_torch, name)
# one projection feeds indexer q and k; split it, as minimax-m3 does
if ".indexer.index_qk_proj.weight" in name:
n_q = self.hparams["indexer_n_heads"] * self.hparams["indexer_head_dim"]
q = data_torch[:n_q]
k = data_torch[n_q:]
return [
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), q),
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), k),
]
# Gemma zero-centred gammas the inherited norm.weight rule misses
if name.endswith((".ple.norm_key.weight", ".ple.norm_query.weight", ".ple.norm_conv.weight",
".indexer.q_layernorm.weight", ".indexer.k_layernorm.weight")):
return [(self.map_tensor_name(name), data_torch + 1)]
if name.endswith(".ple.conv1d.weight"):
return [(self.map_tensor_name(name), data_torch.squeeze())]
return super().modify_tensors(data_torch, name, bid)
# the shards concatenate into a tensor of well over 100 GB
# use LazyChunkedTensor here, a single shard resident at a time
def _place_ple_shard(self, data_torch: Tensor, name: str) -> Iterable[tuple[str, Tensor]]:
idx = int(name.rpartition(".shard_")[2].partition(".")[0])
n_parts = self.hparams["split_ngram_parts"]
self._ple_shards[idx] = name
self._ple_row_dim = int(data_torch.shape[-1])
if len(self._ple_shards) < n_parts:
return []
# the checkpoint may yield the shards in any order, the row order is by index
shards = [self._ple_shards[i] for i in sorted(self._ple_shards)]
rows = 0
for shard in shards:
shape = self.model_tensors[shard]().shape
if int(shape[-1]) != self._ple_row_dim:
raise ValueError(
f"PLE shard {shard} has row dim {int(shape[-1])}, expected {self._ple_row_dim}")
rows += int(shape[0])
table = gguf.LazyChunkedTensor(
[self._load_ple_shard(shard) for shard in shards],
shape=(rows, self._ple_row_dim),
dtype=np.float32,
)
gguf_name = gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD]
return [(gguf_name + ".weight", cast(Tensor, table))]
def _load_ple_shard(self, name: str):
def load() -> np.ndarray:
from .base import LazyTorchTensor
# a fresh lazy tensor every call, or to_eager() memoizes every shard
eager = LazyTorchTensor.to_eager(self.model_tensors[name]())
return eager.to(torch.float32).contiguous().numpy()
return load
def prepare_tensors(self):
super().prepare_tensors()
n_parts = self.hparams.get("split_ngram_parts", 0)
if self._ple_shards and len(self._ple_shards) != n_parts:
raise ValueError(
f"got {len(self._ple_shards)} PLE embedding shards, expected {n_parts}"
)
@ModelBase.register("Qwen4ExpForConditionalGeneration")
@ModelBase.example("Qwen/Qwen3.8-Flash-Next")
class Qwen4ExpVisionModel(Qwen3VLVisionModel):
"""The vision tower is an unmodified Qwen3-VL ViT."""
-65
View File
@@ -1,65 +0,0 @@
from __future__ import annotations
from collections.abc import Iterable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from torch import Tensor
from .base import ModelBase, TextModel, gguf
@ModelBase.register("Spark2_5ForCausalLM")
@ModelBase.example("XHToken/Spark-X2.5-1.7B")
class Spark2_5Model(TextModel):
model_arch = gguf.MODEL_ARCH.SPARK2_5
def set_gguf_parameters(self) -> None:
super().set_gguf_parameters()
hparams = self.hparams
layer_types = hparams["layer_types"]
if len(layer_types) != self.block_count:
raise ValueError(
f"Spark2_5 layer_types length {len(layer_types)} != num_hidden_layers {self.block_count}"
)
if any(layer_type not in ("sliding_attention", "full_attention") for layer_type in layer_types):
raise ValueError(f"Spark2_5 has unsupported layer_types: {layer_types}")
if hparams.get("gate_attn_act_mode") != "sigmoid" or hparams.get("headwise_attn_output_gate") is not True:
raise ValueError("Spark2_5 conversion requires head-wise sigmoid attention gates")
if hparams.get("hidden_act") != "gelu":
raise ValueError(f"Spark2_5 conversion requires GELU, got {hparams.get('hidden_act')!r}")
self.gguf_writer.add_vocab_size(hparams["vocab_size"])
self.gguf_writer.add_sliding_window(hparams["sliding_window"])
self.gguf_writer.add_sliding_window_pattern(
[layer_type == "sliding_attention" for layer_type in layer_types]
)
head_dim = hparams["head_dim"]
full_rope = self.rope_parameters["full_attention"]
swa_rope = self.rope_parameters["sliding_attention"]
self.gguf_writer.add_rope_dimension_count(
int(head_dim * float(full_rope["partial_rotary_factor"]))
)
self.gguf_writer.add_rope_dimension_count_swa(
int(head_dim * float(swa_rope["partial_rotary_factor"]))
)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.endswith(".self_attn.q_k_v_proj.weight"):
if bid is None:
raise ValueError(f"Spark2_5 fused QKV tensor has no block id: {name}")
yield self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_QKV, bid), data_torch
return
if name.endswith(".self_attn.g_proj.weight"):
if bid is None:
raise ValueError(f"Spark2_5 attention gate tensor has no block id: {name}")
expected = self.hparams["num_attention_heads"]
if data_torch.shape[0] != expected:
raise ValueError(
f"Spark2_5 layer {bid} attention gate width {data_torch.shape[0]} != head count {expected}"
)
yield from super().modify_tensors(data_torch, name, bid)
-5
View File
@@ -157,10 +157,6 @@ def parse_args() -> argparse.Namespace:
help="Store tensors dequantized from FP8 as Q8_0 instead of BF16/F16.",
)
parser.add_argument(
"--fuse-qkv", action="store_true",
help="Fuse separate Q, K, V weight tensors into a single QKV tensor.",
)
parser.add_argument(
"--target-model-dir", type=str, default=None,
help=(
@@ -294,7 +290,6 @@ def main() -> None:
target_model_dir=Path(args.target_model_dir) if args.target_model_dir else None,
fuse_gate_up_exps=args.fuse_gate_up_exps,
fp8_as_q8=args.fp8_as_q8,
fuse_qkv=args.fuse_qkv,
)
if args.vocab_only:
-2
View File
@@ -176,7 +176,6 @@ pre_computed_hashes = [
{"name": "minerva-7b", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0", "chkhsh": "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35"},
{"name": "hunyuan", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-A13B-Instruct", "chkhsh": "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664"},
{"name": "hunyuan-dense", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-4B-Instruct", "chkhsh": "bba3b3366b646dbdded5dbc42d59598b849371afc42f7beafa914afaa5b70aa6"},
{"name": "hy_v4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hy4-preview", "chkhsh": "e6ddf9c6686791c12d698d34c31ab9be1fea9af5a3d9a6909783ab382198ae1c"},
# falcon-h1 series uses 4 different tokenizers across model sizes (0.5b - 34b), hence we need to define 4 different hashes
{"name": "falcon-h1", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tiiuae/Falcon-H1-0.5B-Base", "chkhsh": "a6b57017d60e6edb4d88ecc2845188e0eb333a70357e45dcc9b53964a73bbae6"},
{"name": "falcon-h1", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tiiuae/Falcon-H1-1B-Base", "chkhsh": "60476e1243776c4fb1b993dbd7a5f15ac22f83c80afdf425fa5ae01c8d44ef86"},
@@ -191,7 +190,6 @@ pre_computed_hashes = [
{"name": "gpt-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/evilfreelancer/ruGPT3XL", "chkhsh": "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4"},
# lfm2 variants
{"name": "lfm2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/LiquidAI/LFM2.5-8B-A1B", "chkhsh": "9e454714343b69b99b71795c1d27a68c2a1d15dab111f4d353109f966af29da7"},
{"name": "spark2_5", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/XHToken/Spark-X2.5-1.7B", "chkhsh": "0a766d034107bc736a3f2dc4968fd62e54a3570f1454443e0c5a4cc6bd7941ed"},
]
+4 -8
View File
@@ -53,7 +53,7 @@ To see what it might look like visually, here's an old demo of an interactive se
https://user-images.githubusercontent.com/271616/225014776-1d567049-ad71-4ef2-b050-55b0b3b9274c.mp4
## Cross-compile CLI using Android NDK
It's possible to build `llama.cpp` for Android on your host system via CMake and the Android NDK. If you are interested in this path, ensure you already have an environment prepared to cross-compile programs for Android (i.e., install the Android SDK/NDK and set `ANDROID_NDK` to the NDK root). Note that, unlike desktop environments, the Android environment ships with a limited set of native libraries, and so only those libraries are available to CMake when building with the Android NDK (see: https://developer.android.com/ndk/guides/stable_apis.)
It's possible to build `llama.cpp` for Android on your host system via CMake and the Android NDK. If you are interested in this path, ensure you already have an environment prepared to cross-compile programs for Android (i.e., install the Android SDK). Note that, unlike desktop environments, the Android environment ships with a limited set of native libraries, and so only those libraries are available to CMake when building with the Android NDK (see: https://developer.android.com/ndk/guides/stable_apis.)
Once you're ready and have cloned `llama.cpp`, invoke the following in the project directory:
@@ -62,22 +62,18 @@ $ cmake \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-28 \
-DGGML_NATIVE=OFF \
-DCMAKE_C_FLAGS="-march=armv8.7a" \
-DCMAKE_CXX_FLAGS="-march=armv8.7a" \
-DGGML_OPENMP=OFF \
-DGGML_LLAMAFILE=OFF \
-DLLAMA_OPENSSL=OFF \
-B build-android
```
Notes:
- `GGML_NATIVE=OFF` is required for cross-compilation because the host CPU is not the Android target CPU
- While later versions of Android NDK ship with OpenMP, it must still be installed by CMake as a dependency, which is not supported at this time
- `llamafile` does not appear to support Android devices (see: https://github.com/Mozilla-Ocho/llamafile/issues/325)
- `LLAMA_OPENSSL=OFF` avoids depending on OpenSSL, which is not part of the Android NDK stable native API set
The above command configures a portable Android `arm64-v8a` build. Do not add a global `-march` flag unless you intentionally want to raise the baseline instruction set for every compiled source.
For optional KleidiAI acceleration on Android `arm64-v8a`, see the [Arm KleidiAI section in build.md](./build.md#arm-kleidiai).
The above command should configure `llama.cpp` with the most performant options for modern devices. Even if your device is not running `armv8.7a`, `llama.cpp` includes runtime checks for available CPU features it can use.
Feel free to adjust the Android ABI for your target. Once the project is configured:
+7 -8
View File
@@ -443,21 +443,21 @@ Each returned parser is wrapped by `wrap_for_generation_prompt()`, which prepend
| | `wrap_for_generation_prompt()`, string helpers |
| `common/chat-peg-parser.h/cpp` | `common_chat_peg_builder`, `common_chat_peg_mapper`, and helpers |
| `common/chat.cpp` | Entry point: `common_chat_templates_apply_jinja()` |
| `tests/test-chat-auto-parser.cpp` | Auto-parser unit tests; also a debug tool when given a template path |
| `tests/test-chat-analysis.cpp` | Template differential analysis debug tool |
| `tools/parser/debug-template-parser.cpp` | Debug tool for template analysis |
| `tools/parser/template-analysis.cpp` | Template analysis tool |
## Testing & Debugging
### Debug Tools
**Template Debugger**: `tests/test-chat-auto-parser.cpp`
**Template Debugger**: `tools/parser/debug-template-parser.cpp`
- Usage: `./bin/test-chat-auto-parser path/to/template.jinja` (without a path, it runs the automated tests)
- Usage: `./bin/llama-debug-template-parser path/to/template.jinja`
- Shows detected format, markers, generated parser, and GBNF grammar
**Template Analysis**: `tests/test-chat-analysis.cpp`
**Template Analysis**: `tools/parser/template-analysis.cpp`
- Usage: `./bin/test-chat-analysis --template-file path/to/template.jinja` (without arguments, it runs on all templates from the test suite)
- Usage: `./bin/llama-template-analysis path/to/template.jinja`
**Debug Logging**: Enable with `LLAMA_ARG_LOG_VERBOSITY=2`
@@ -514,13 +514,12 @@ The following templates have active tests in `tests/test-chat.cpp`:
| Mistral Small 3.2 | JSON_NATIVE | `[TOOL_CALLS]func[ARGS]{...}` with call ID |
| Devstral | JSON_NATIVE | `[TOOL_CALLS]func[ARGS]{...}` without call ID |
| StepFun 3.5 Flash | TAG_WITH_TAGGED | `<function=X><parameter=Y>` format |
| Spark2.5 | TAG_WITH_TAGGED | `<tool_call>name<arg_key>...<arg_value>...` format |
## Adding Support for New Templates
To support a new template format:
1. **If it follows standard patterns** — The auto-parser should detect it automatically. Run `test-chat-auto-parser <template_path>` to verify markers are correctly extracted.
1. **If it follows standard patterns** — The auto-parser should detect it automatically. Run `llama-debug-template-parser` to verify markers are correctly extracted.
2. **If differential analysis extracts incorrect markers** — Add a workaround lambda to the `workarounds` vector in `common/chat-diff-analyzer.cpp`. Inspect the template source for a unique identifying substring.
3. **If it needs fundamentally different handling** — Add a dedicated handler function in `chat.cpp` before the auto-parser block (as done for GPT-OSS, Functionary v3.2, and Ministral).
+40 -46
View File
@@ -22,8 +22,8 @@ The OpenVINO backend is implemented in `ggml/src/ggml-openvino` and provides a t
- [0. Prerequisites](#0-prerequisites)
- [1. Install OpenVINO Runtime](#1-install-openvino-runtime)
- [2. Build llama.cpp with OpenVINO Backend](#2-build-llamacpp-with-openvino-backend)
- [Ubuntu Build Script](#ubuntu-build-script)
- [Windows Build Script](#windows-build-script)
- [Automated Ubuntu Build Script](#automated-ubuntu-build-script)
- [Automated Windows Build Script](#automated-windows-build-script)
- [3. Download Sample Model](#3-download-sample-model)
- [4. Run Inference with OpenVINO Backend](#4-run-inference-with-openvino-backend)
- [5. Docker Build](#5-docker-build)
@@ -96,7 +96,7 @@ Although, the validated models below were tested with `llama-cli` using the `Q4_
- **SL** = Stateless (`GGML_OPENVINO_STATEFUL_EXECUTION=0`)
- **SF** = Stateful (`GGML_OPENVINO_STATEFUL_EXECUTION=1`)
- Note: The NPU operates in stateless mode only.
- **Validation system:** Intel® Core™ Ultra 5 238V (Lunar Lake) | 32 GB RAM | Ubuntu 24.04 | Intel OpenCL GPU Driver 26.31.39395.13-0 | Intel NPU Driver 1.35.0.
- **Validation system:** Intel® Core™ Ultra 5 238V (Lunar Lake) | 32 GB RAM | Ubuntu 24.04 | Intel OpenCL GPU Driver 26.18.38308.1 | Intel NPU Driver 1.33.0.
- See [Known Limitations](#known-limitations) for context on observed failures.
| Model | CPU (SL / SF) | GPU (SL / SF) | NPU (SL) |
@@ -105,32 +105,27 @@ Although, the validated models below were tested with `llama-cli` using the `Q4_
| [bartowski/Llama-3.2-3B-Instruct-Q4_K_M](https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Meta-Llama-3.1-8B-Instruct-Q4_K_M](https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| | | | |
| [Qwen/qwen2.5-1.5b-instruct-q4_k_m](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [Qwen/qwen2.5-coder-7b-instruct-q4_k_m](https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Qwen_Qwen3-0.6B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Qwen_Qwen3-1.7B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3-1.7B-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [Qwen/Qwen3-4B-Q4_K_M](https://huggingface.co/Qwen/Qwen3-4B-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [lm-kit/Qwen3-8B-Q4_K_M](https://huggingface.co/lm-kit/qwen-3-8b-instruct-gguf) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Qwen_Qwen3.5-0.8B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3.5-0.8B-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| [bartowski/Qwen_Qwen3.5-2B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3.5-2B-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| [bartowski/Qwen_Qwen3.5-4B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3.5-4B-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| [lmstudio-community/Qwen3.5-9B-Q4_K_M](https://huggingface.co/lmstudio-community/Qwen3.5-9B-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| [Qwen/qwen2.5-1.5b-instruct-q4_k_m](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [Qwen/qwen2.5-coder-7b-instruct-q4_k_m](https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/Qwen_Qwen3-0.6B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3-0.6B-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/Qwen_Qwen3-1.7B-Q4_K_M](https://huggingface.co/bartowski/Qwen_Qwen3-1.7B-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [Qwen/Qwen3-4B-Q4_K_M](https://huggingface.co/Qwen/Qwen3-4B-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [lm-kit/Qwen3-8B-Q4_K_M](https://huggingface.co/lm-kit/qwen-3-8b-instruct-gguf) | ✓ / ✓ | ✓ / ✗ | ✓ |
| | | | |
| [unsloth/gemma-3-4b-it-Q4_K_M](https://huggingface.co/unsloth/gemma-3-4b-it-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/google_gemma-4-E2B-it-Q4_K_M](https://huggingface.co/bartowski/google_gemma-4-E2B-it-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| [unsloth/gemma-3-4b-it-Q4_K_M](https://huggingface.co/unsloth/gemma-3-4b-it-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/google_gemma-4-E2B-it-Q4_K_M](https://huggingface.co/bartowski/google_gemma-4-E2B-it-GGUF) | ✓ / ✗ | ✓ / ✗ | ✓ |
| [bartowski/google_gemma-4-E4B-it-Q4_K_M](https://huggingface.co/bartowski/google_gemma-4-E4B-it-GGUF) | ✓ / ✗ | ✓ / ✗ | ✓ |
| [bartowski/gemma-4-12B-it-Q4_K_M](https://huggingface.co/bartowski/gemma-4-12B-it-GGUF) | ✓ / ✗ | ✓ / ✗ | ✓ |
| [bartowski/gemma-4-12B-it-Q4_K_M](https://huggingface.co/bartowski/gemma-4-12B-it-GGUF) | ✓ / ✗ | ✓ / ✗ | ✗ |
| | | | |
| [bartowski/Phi-3-mini-4k-instruct-Q4_K_M](https://huggingface.co/bartowski/Phi-3-mini-4k-instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Phi-3.5-mini-instruct-Q4_K_M](https://huggingface.co/bartowski/Phi-3.5-mini-instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/microsoft_Phi-4-mini-instruct-Q4_K_M](https://huggingface.co/bartowski/microsoft_Phi-4-mini-instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Phi-3-mini-4k-instruct-Q4_K_M](https://huggingface.co/bartowski/Phi-3-mini-4k-instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/Phi-3.5-mini-instruct-Q4_K_M](https://huggingface.co/bartowski/Phi-3.5-mini-instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| | | | |
| [bartowski/Mistral-7B-Instruct-v0.3-Q4_K_M](https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [QuantFactory/Ministral-3b-instruct.Q4_K_M](https://huggingface.co/QuantFactory/Ministral-3b-instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/Ministral-8B-Instruct-2410-Q4_K_M](https://huggingface.co/bartowski/Ministral-8B-Instruct-2410-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| | | | |
| [bartowski/DeepSeek-R1-Distill-Llama-8B-Q4_K_M](https://huggingface.co/bartowski/DeepSeek-R1-Distill-Llama-8B-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/DeepSeek-R1-Distill-Qwen-7B-Q4_K_M](https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/DeepSeek-R1-Distill-Qwen-7B-Q4_K_M](https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| | | | |
| [ibm-granite/granite-4.0-350m-Q4_K_M](https://huggingface.co/ibm-granite/granite-4.0-350m-GGUF) | ✓ / ✓ | ✗ / ✗ | ✓ |
| [ibm-granite/granite-4.0-micro-Q4_K_M](https://huggingface.co/ibm-granite/granite-4.0-micro-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
@@ -138,10 +133,10 @@ Although, the validated models below were tested with `llama-cli` using the `Q4_
| [ibm-research/granite-3.2-8b-instruct-Q4_K_M](https://huggingface.co/ibm-research/granite-3.2-8b-instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| | | | |
| [HuggingFaceTB/smollm2-1.7b-instruct-q4_k_m](https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [openbmb/MiniCPM-V-2_6-Q4_K_M](https://huggingface.co/openbmb/MiniCPM-V-2_6-gguf) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/tencent_Hunyuan-7B-Instruct-Q4_K_M](https://huggingface.co/bartowski/tencent_Hunyuan-7B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-Q4_K_M](https://huggingface.co/LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [bartowski/prism-ml_Bonsai-8B-unpacked-Q4_K_M](https://huggingface.co/bartowski/prism-ml_Bonsai-8B-unpacked-GGUF) | ✓ / ✓ | ✓ / ✓ | ✓ |
| [openbmb/MiniCPM-V-2_6-Q4_K_M](https://huggingface.co/openbmb/MiniCPM-V-2_6-gguf) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/tencent_Hunyuan-7B-Instruct-Q4_K_M](https://huggingface.co/bartowski/tencent_Hunyuan-7B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-Q4_K_M](https://huggingface.co/LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| [bartowski/prism-ml_Bonsai-8B-unpacked-Q4_K_M](https://huggingface.co/bartowski/prism-ml_Bonsai-8B-unpacked-GGUF) | ✓ / ✓ | ✓ / ✗ | ✓ |
| | | | |
| [gpustack/bge-m3-Q4_K_M.gguf](https://huggingface.co/gpustack/bge-m3-GGUF) | ✓ | ✗ | ✗ |
@@ -222,18 +217,18 @@ cmake --build build\ReleaseOV --parallel
> [!NOTE]
> The Windows install path is `C:\Intel\openvino` (no spaces) to avoid quoting problems some CMake/Ninja toolchains have with `C:\Program Files (x86)\...`. Adjust to wherever you installed OpenVINO Runtime. From `cmd`, run `C:\Intel\openvino\setupvars.bat`; from PowerShell, run `& "C:\Intel\openvino\setupvars.ps1"` instead. Once the build is finished you can launch the binaries from any `cmd` or `PowerShell` window after sourcing the matching `setupvars` script for that shell.
#### Ubuntu Build Script
#### Automated Ubuntu Build Script
For Ubuntu24 users, the following shell script automates the prerequisite installs (build tools, OpenCL ICD), the OpenVINO Runtime download/extract/setup, and the Ninja-based llama.cpp build.
Save the following as `build-llamacpp-ov.sh` next to where you want the `llama.cpp` folder to land, then run it:
Save the following as `ubuntu-llamacpp-ov-install.sh` next to where you want the `llama.cpp` folder to land, then run it:
```bash
chmod +x build-llamacpp-ov.sh
./build-llamacpp-ov.sh
chmod +x ubuntu-llamacpp-ov-install.sh
./ubuntu-llamacpp-ov-install.sh
```
<details>
<summary>Click to expand <code>build-llamacpp-ov.sh</code></summary>
<summary>Click to expand <code>ubuntu-llamacpp-ov-install.sh</code></summary>
```bash
#!/usr/bin/env bash
@@ -242,8 +237,8 @@ chmod +x build-llamacpp-ov.sh
# ============================================
set -euo pipefail
OPENVINO_VERSION_MAJOR="2026.3.1"
OPENVINO_VERSION_FULL="2026.3.1.22476.56d9685302d"
OPENVINO_VERSION_MAJOR="2026.3"
OPENVINO_VERSION_FULL="2026.3.0.22451.bd8d6542e3c"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OPENVINO_INSTALL_DIR="/opt/intel/openvino_${OPENVINO_VERSION_MAJOR}"
@@ -318,9 +313,8 @@ fi
echo "============================================"
echo "Configuring with CMake..."
echo "============================================"
set +u
# shellcheck disable=SC1091
source "${OPENVINO_ROOT}/setupvars.sh"
set -u
cmake -B build/ReleaseOV -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
@@ -340,27 +334,27 @@ echo " ./build/ReleaseOV/bin/llama-cli -m model.gguf"
```
> [!NOTE]
> The script pins OpenVINO `2026.3.1` via the `OPENVINO_VERSION_MAJOR` / `OPENVINO_VERSION_FULL` variables at the top — edit them to track a different release.
> The script pins OpenVINO `2026.3` via the `OPENVINO_VERSION_MAJOR` / `OPENVINO_VERSION_FULL` variables at the top — edit them to track a different release.
</details>
#### Windows Build Script
#### Automated Windows Build Script
For Windows users, the following `.bat` script automates the prerequisite installs (Git, Ninja, CMake, Visual Studio 2022 Build Tools, vcpkg + OpenCL), the OpenVINO Runtime download/extract, and the Ninja-based llama.cpp build.
Save the following as `build-llamacpp-ov.bat` next to where you want the `llama.cpp` to land, then run it from either **Command Prompt** or **PowerShell**:
Save the following as `windows-llamacpp-ov-install.bat` next to where you want the `llama.cpp` to land, then run it from either **Command Prompt** or **PowerShell**:
```cmd
:: Command Prompt
build-llamacpp-ov.bat
windows-llamacpp-ov-install.bat
```
```powershell
# PowerShell
.\build-llamacpp-ov.bat
.\windows-llamacpp-ov-install.bat
```
<details>
<summary>Click to expand <code>build-llamacpp-ov.bat</code></summary>
<summary>Click to expand <code>windows-llamacpp-ov-install.bat</code></summary>
```bat
@echo off
@@ -370,8 +364,8 @@ REM ============================================
REM llama.cpp OpenVINO Build Script (Ninja)
REM ============================================
set "OPENVINO_VERSION_MAJOR=2026.3.1"
set "OPENVINO_VERSION_FULL=2026.3.1.22476.56d9685302d"
set "OPENVINO_VERSION_MAJOR=2026.3"
set "OPENVINO_VERSION_FULL=2026.3.0.22451.bd8d6542e3c"
set "SCRIPT_DIR=%~dp0"
set "VCPKG_DIR=C:\vcpkg"
@@ -459,6 +453,9 @@ if exist "%OPENVINO_INSTALL_DIR%\setupvars.bat" (
)
REM Move the single top-level folder contents into the versioned install dir.
REM NOTE: delayed expansion (!VAR!) is required because the surrounding else( ... )
REM block is parsed once up-front, so %OPENVINO_EXTRACTED% would expand to "" here
REM and xcopy would then treat "\*" as C:\* and fail with "Cannot perform a cyclic copy".
set "OPENVINO_EXTRACTED="
for /d %%i in ("%OPENVINO_EXTRACT_TMP%\*") do set "OPENVINO_EXTRACTED=%%i"
if not defined OPENVINO_EXTRACTED (
@@ -550,7 +547,7 @@ endlocal
```
> [!NOTE]
> The script pins OpenVINO `2026.3.1` via the `OPENVINO_VERSION_MAJOR` / `OPENVINO_VERSION_FULL` variables at the top — edit them to track a different release. From any new shell, source the matching `setupvars` script via the junction — `call "C:\Intel\openvino\setupvars.bat"` from `cmd`, or `& "C:\Intel\openvino\setupvars.ps1"` from PowerShell. If `winget` cannot register Visual Studio Build Tools on first run, install them once manually and re-run the script from an elevated **Developer Command Prompt for VS 2022**.
> The script pins OpenVINO `2026.3` via the `OPENVINO_VERSION_MAJOR` / `OPENVINO_VERSION_FULL` variables at the top — edit them to track a different release. From any new shell, source the matching `setupvars` script via the junction — `call "C:\Intel\openvino\setupvars.bat"` from `cmd`, or `& "C:\Intel\openvino\setupvars.ps1"` from PowerShell. If `winget` cannot register Visual Studio Build Tools on first run, install them once manually and re-run the script from an elevated **Developer Command Prompt for VS 2022**.
</details>
@@ -715,7 +712,6 @@ Boolean flags follow a uniform convention: set to a **positive integer** (e.g. `
| `GGML_OPENVINO_CACHE_DIR` | String | `not set` | Directory for OpenVINO model caching (recommended: `/tmp/ov_cache`). Enables model caching when set. **Not supported on NPU devices.** |
| `GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR` | String | `not set` | Directory for the frontend compiled-model cache. When set, OpenVINO compiled models are exported as blobs and imported on later runs to skip weight requantization, graph conversion, and compilation for matching single-graph models. |
| `GGML_OPENVINO_PREFILL_CHUNK_SIZE`| Integer | `256` | Token chunk size for **NPU** prefill (NPU-only; ignored on CPU/GPU). Must be a positive integer; otherwise the default is used. |
| `GGML_OPENVINO_NPU_COMPILE_CONFIG` | String | `not set` | NPU-only compiler mode parameters forwarded to OpenVINO as `NPU_COMPILATION_MODE_PARAMS`, for example `optimization-level=3`. |
| `GGML_OPENVINO_STATEFUL_EXECUTION`| Boolean | `0` | Enable stateful KV cache for better performance. Recommended on CPU, GPU. |
| `GGML_OPENVINO_DISABLE_CACHE` | Boolean | `0` | Disable the in-process compiled-model / decoder cache (cache is on by default). Set to `1` to disable. |
| `GGML_OPENVINO_DISABLE_KV_SLICE` | Boolean | `0` | Disable the KV-cache input-tensor slicing optimization (slicing is on by default on CPU/GPU). Set to `1` to disable. |
@@ -729,11 +725,9 @@ Boolean flags follow a uniform convention: set to a **positive integer** (e.g. `
| `GGML_OPENVINO_DEBUG_INPUT` | Boolean | `0` | Enable input debugging and print input tensor info. |
| `GGML_OPENVINO_DEBUG_OUTPUT` | Boolean | `0` | Enable output debugging and print output tensor info. |
| `GGML_OPENVINO_PRINT_CGRAPH_TENSOR_ADDRESS` | Boolean | `0` | Print tensor address map once. |
| `GGML_OPENVINO_LOG_UNSUPPORTED_OPS`| Boolean | `0` | Log warning messages with tensor details and rejection reasons for any ops not supported by the OpenVINO backend. Emits at `WARN` level (requires `--log-verbosity >= 2`, enabled by default). |
> [!NOTE]
> - `GGML_OPENVINO_STATEFUL_EXECUTION` is an **Experimental** feature to allow stateful execution for managing the KV cache internally inside the OpenVINO model, improving performance on CPUs and GPUs. Stateful execution is not effective on NPUs, and not all models currently support this feature. This feature is experimental and has been validated only with the llama-simple, llama-cli, llama-bench, and llama-run applications and is recommended to enable for the best performance. Other applications, such as llama-server and llama-perplexity, are not yet supported.
> - `GGML_OPENVINO_LOG_UNSUPPORTED_OPS` emits logs at `WARN` level (`GGML_LOG_WARN`), which requires application log verbosity `--log-verbosity >= 2` (or `-lv 2`).
>`GGML_OPENVINO_STATEFUL_EXECUTION` is an **Experimental** feature to allow stateful execution for managing the KV cache internally inside the OpenVINO model, improving performance on CPUs and GPUs. Stateful execution is not effective on NPUs, and not all models currently support this feature. This feature is experimental and has been validated only with the llama-simple, llama-cli, llama-bench, and llama-run applications and is recommended to enable for the best performance. Other applications, such as llama-server and llama-perplexity, are not yet supported.
### Example Usage
+2 -6
View File
@@ -795,9 +795,7 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| GGML_SYCL_ENABLE_FLASH_ATTN | 1 (default) or 0| Enable Flash-Attention. It can reduce memory usage. The performance impact depends on the LLM.|
| GGML_SYCL_ENABLE_OPT | 0 or 1 (default)| Enable optimize features for Intel GPUs. (Recommended to 0 for Intel devices older than Gen 10) |
| GGML_SYCL_ENABLE_GRAPH | 0 (default) or 1 | Enable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still on development, no better performance. |
| GGML_SYCL_ENABLE_HOST_PINNED_MEM | 0 or 1 (default) | Enable host pinned memory to speed up copy data from host to device. When disable it, host memory will common malloc() on CPU. Disable it when use `--load-model mlock`.|
| GGML_SYCL_HOST_PINNED_MEM_2G | 0 (default) or 1 | Limit the max memory allocation to be no more than 2GB when enable host pinned memory. USM allocations above 2 GiB take the relaxed/large-allocation path, which serializes H2D copies with compute and prevents copy/compute overlap. It will impact the startup time. Need more test. Depend on `GGML_SYCL_ENABLE_HOST_PINNED_MEM=1`.|
| GGML_SYCL_GET_MEM_API | 0 (default) or 1 | Set to get memory info (free, total) by Level Zero or SYCL API:<br>0 - Level Zero API: support more GPUs, only run on Level Zero running time. When there is an error, fallback to call SYCL API. Depend on GGML_SYCL_SUPPORT_LEVEL_ZERO_API.<br>1 - SYCL API: legacy, support more running time, it can't get the free size of some GPUs (like Arc770). In such case, return total size for free size.|
| GGML_SYCL_ENABLE_HOST_PINNED_MEM | 0 or 1 (default) | Enable host pinned memory to speed up copy data from host to device. When disable it, host memory will common malloc() on CPU.|
| GGML_SYCL_USE_LEVEL_ZERO_API | 1 (default) or 0 | Use Level Zero API for device memory allocation instead of SYCL. Reduces system RAM usage on Intel dGPUs by avoiding DMA-buf/TTM host memory staging. Requires GGML_SYCL_SUPPORT_LEVEL_ZERO_API=ON at build time. SYCL backend always runs on Level Zero running time even if it's set as OFF (The SYCL api will be usage for memory allocation).|
| GGML_SYCL_ENABLE_DNN | 0 or 1 (default)| Enable running computations through oneDNN and always use oneMKL. |
| GGML_SYCL_FA_ONEDNN | 1 (default) or 0 | Enable the oneDNN fused SDPA (flash-attention) path on supported GPUs. Set to 0 to always use the native SYCL flash-attention kernel. |
@@ -805,10 +803,8 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| GGML_SYCL_ENABLE_VMM | 0 or 1 (default) | Enable the virtual-memory device pool. |
| GGML_SYCL_ENABLE_MKL_FA | 1 (default) or 0 | Enable oneMKL GEMM flash attention for XMX-accelerated prompt processing with quantized KV cache. Automatically activates during prefill (prompt processing) when all conditions are met: (1) flash-attn enabled (`-fa` or `--flash-attn on`), (2) KV cache quantized (`--cache-type-k q8_0 --cache-type-v q8_0` or other `*_0/*_1` types), (3) batch size ≥ 1024 (`--batch-size 1024`), (4) prompt length ≥ 1024 tokens. Set to 0 to force the TILE kernel for A/B testing. Example minimum command: `llama-cli -m model.gguf -fa -ngl 99 --cache-type-k q8_0 --cache-type-v q8_0 --batch-size 1024 -p "your prompt"` |
| GGML_SYCL_MKL_FA_DEBUG | 0 (default) or 1 | Enable per-call diagnostic logging for MKL flash attention: GEMM/softmax timings, interleaved-head detection, and buffer memory usage. |
| GGML_SYCL_MEMTRACE | 0 (default), 1, 2 | Enable record and output memory allocation diagnostics. Requires `-lv 4`. <br>0 - Disable<br>1 - Basic memory info, including current and peak allocations, as well allocations from other sources, around 50 lines per model load.<br>2 - More verbose, logging around 900 specific allocations and deallocations. |
| GGML_SYCL_MEMTRACE_STEP | 64 (default) or positive integer | With GGML_SYCL_MEMTRACE=1, the minimum growth in memory usage to trigger another log record. |
| GGML_SYCL_MKL_FA_DIAG | 0 (default) or 1 | Enable output fingerprinting for MKL flash attention. Dumps the first 64 float output values for the first 6 FA calls with n_kv ≥ 1024, labeled with kernel type (MKL/TILE/VEC) for cross-kernel comparison. |
| GGML_SYCL_ENABLE_FUSION | 0 or 1 (default) | Enable fused-kernel dispatch in graph compute. Unsupported types and layouts fall back to the standalone op kernels. See `ggml_sycl_can_fuse()`. |
| GGML_SYCL_ENABLE_FUSION | 0 or 1 (default) | Enable fused-kernel dispatch in graph compute. |
| GGML_SYCL_ENABLE_ESIMD | 0 or 1 (default)| Enable ESIMD kernels when available. |
| ZES_ENABLE_SYSMAN | 0 (default) or 1 | Support to get free memory of GPU by sycl::aspect::ext_intel_free_memory.<br>Recommended to use when --split-mode = layer |
| UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS | 0 (default) or 1 | Allow SYCL/Unified Runtime Level Zero device allocations larger than 4 GiB. llama.cpp's direct Level Zero allocation path requests the relaxed maximum-size limit itself when GGML_SYCL_ENABLE_LEVEL_ZERO=1. |
@@ -8,7 +8,7 @@
"toolset": { "value": "host=x86_64", "strategy": "external" },
"cacheVariables": {
"ANDROID_ABI": "arm64-v8a",
"ANDROID_PLATFORM": "android-34",
"ANDROID_PLATFORM": "android-31",
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake",
"CMAKE_C_FLAGS": "-march=armv8.7a+fp16+dotprod+i8mm -fvectorize -ffp-model=fast -fno-finite-math-only -flto -D_GNU_SOURCE",
"CMAKE_CXX_FLAGS": "-march=armv8.7a+fp16+dotprod+i8mm -fvectorize -ffp-model=fast -fno-finite-math-only -flto -D_GNU_SOURCE",
+115 -103
View File
@@ -2,47 +2,39 @@
## Setup
The cross-compilation toolchain images are provided by the
[Qualcomm Snapdragon Toolchain registry](https://github.com/snapdragon-toolchain).
These Docker images include the Android NDK, OpenCL SDK, Hexagon SDK, CMake, and the necessary cross-compilers:
### Android
* **Android toolchain**: `ghcr.io/snapdragon-toolchain/arm64-android:v0.7`
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.7`
The easiest way to build llama.cpp for a Snapdragon-based Android device is using the toolchain Docker image (see github.com/snapdragon-toolchain).
This image includes Android NDK, OpenCL SDK, Hexagon SDK, CMake, etc.
The unified build utility (`scripts/snapdragon/build.py`) automatically pulls
and orchestrates these containers to perform target compilation.
You only need to ensure that Docker (or Docker Desktop on macOS/Windows) is running on your host machine.
Specific setup, build, and installation details for Linux and Windows on Snapdragon platforms are documented in:
* [Linux on Snapdragon guide](linux.md)
* [Windows on Snapdragon guide](windows.md)
This method works on Linux, macOS, and Windows. macOS and Windows users should install Docker Desktop.
```
~/src/llama.cpp$ docker run -it -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.7
[d]/> cd /workspace
```
Note: The rest of the **Android** build process assumes that you're running inside the toolchain container.
### Windows On Snapdragon
Native Windows 11 arm64 builds has the following tools dependencies:
- MS Visual Studio 2026 (Community Edition or Pro)
- MSVC arm64 standard and runtime libraries
- UCRT and Driver Kit
- LLVM core libraries and Clang compiler (winget)
- CMake, Git, Python (winget)
- Hexagon SDK Community Edition 6.6 or later (see windows.md)
- OpenCL SDK 2.3 or later (see windows.md)
Note: The rest of the **Windows** build process assumes that you're running natively in Powershell.
Adapt below build commands accordingly.
## How to Build
### Using build.py script (Recommended)
Let's build llama.cpp with CPU, OpenCL, and Hexagon backends via CMake presets:
The easiest way to build llama.cpp is by using the `scripts/snapdragon/build.py` script. It automatically copies the CMake presets,
launches the correct compilation Docker container, builds the libraries and tools,
installs them, and optionally pushes them to your ADB device.
Build and deploy for Android target (accepts `android` or `adb` alias):
```
$ ./scripts/snapdragon/build.py --target adb --push
```
Build and deploy for Linux target (accepts `linux` or `lnx` alias):
```
$ ./scripts/snapdragon/build.py --target linux:user@host --push
```
### Manual CMake Build
Alternatively, you can build llama.cpp manually by entering the cross-compilation Docker container and running the CMake commands:
```bash
# Start the cross-compilation container manually:
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.7
# Inside the container, build the project using presets:
[d]/workspace> cp docs/backend/snapdragon/CMakeUserPresets.json .
[d]/workspace> cmake --preset arm64-android-snapdragon-release -B build-snapdragon
@@ -76,19 +68,19 @@ Preset CMake variables:
To generate an installable "package" simply use cmake --install:
```
[d]/workspace> cmake --install build-snapdragon --prefix pkg-android/llama.cpp
[d]/workspace> cmake --install build-snapdragon --prefix pkg-snapdragon/llama.cpp
-- Install configuration: "Release"
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-cpu.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-opencl.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-hexagon.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-htp-v73.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-htp-v75.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-htp-v79.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml-htp-v81.so
-- Installing: /workspace/pkg-android/llama.cpp/lib/libggml.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-cpu.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-opencl.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-hexagon.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-htp-v73.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-htp-v75.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-htp-v79.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml-htp-v81.so
-- Installing: /workspace/pkg-snapdragon/llama.cpp/lib/libggml.so
...
-- Installing: /workspace/pkg-android/llama.cpp/bin/llama-bench
-- Installing: /workspace/pkg-android/llama.cpp/bin/llama-cli
-- Installing: /workspace/pkg-snapdragon/llama.cpp/bin/llama-bench
-- Installing: /workspace/pkg-snapdragon/llama.cpp/bin/llama-cli
...
```
@@ -99,14 +91,14 @@ To generate an installable "package" simply use cmake --install:
For this step, your device needs to be configured for on-device development.
Please see https://developer.android.com/studio/debug/dev-options for details.
Once ADB is enabled, use `adb push` to install `pkg-android` on the device.
Once ADB is enabled, use `adb push` to install `pkg-snapdragon` on the device.
**Note that the toolchain Docker image doesn't have ADB and doesn't set up the ADB bridge. Please use native ADB on the host.**
```
~/src/llama.cpp$ adb push pkg-android/llama.cpp /data/local/tmp/
pkg-android/llama.cpp/bin/: 67 files pushed, 0 skipped. 190.2 MB/s (919095042 bytes in 4.607s)
pkg-android/llama.cpp/include/: 19 files pushed, 0 skipped. 20.5 MB/s (255173 bytes in 0.012s)
pkg-android/llama.cpp/lib/: 16 files pushed, 0 skipped. 144.4 MB/s (43801382 bytes in 0.289s)
~/src/llama.cpp$ adb push pkg-snapdragon/llama.cpp /data/local/tmp/
pkg-snapdragon/llama.cpp/bin/: 67 files pushed, 0 skipped. 190.2 MB/s (919095042 bytes in 4.607s)
pkg-snapdragon/llama.cpp/include/: 19 files pushed, 0 skipped. 20.5 MB/s (255173 bytes in 0.012s)
pkg-snapdragon/llama.cpp/lib/: 16 files pushed, 0 skipped. 144.4 MB/s (43801382 bytes in 0.289s)
102 files pushed, 0 skipped. 186.9 MB/s (963151597 bytes in 4.914s)
```
@@ -123,44 +115,24 @@ Llama-3.2-1B-Instruct-Q4_0.gguf: 1 file pushed, 0 skipped. 38.3 MB/s (773025920
### Windows
All artifacts are already installed in the `pkg-wos` folder.
To run, you can use the `scripts/snapdragon/run.py` runner script (see details below).
All artifacts are already installed in the `pkg-snapdragon` folder.
To run, adapt below instructions to use Powershell scripts in `scripts/snapdragon/windows`.
## How to Run
The easiest way to run llama.cpp cli tools is using the provided `scripts/snapdragon/run.py` wrapper script. This script automatically
maps CLI options to environment variables, resolves executable paths, and runs the command locally, via ADB, or remotely via SSH on the
target device.
The easiest way to run llama.cpp cli tools is using provided wrapper scripts that properly set up all required environment variables.
llama.cpp supports three backends on Snapdragon-based devices: CPU, Adreno GPU (GPUOpenCL), and Hexagon NPU.
You can select which backend(s) to run the model on using the `--device` option of the tool (or `--devices` option in `run.py`).
llama.cpp supports three backends on Snapdragon-based devices: CPU, Adreno GPU (GPUOpenCL), and Hexagon NPU (HTP0-4).
You can select which backend to run the model on using the `D=` variable, which maps to the `--device` option.
Hexagon NPU behaves as a "GPU" device when it comes to `-ngl` and other offload-related options.
Here are some examples of running various llama.cpp tools.
Here are some examples of running various llama.cpp tools via ADB.
Generating a completion with Gemma on Android (relying on default `HTP0:0` device and default thread count `-t 6`):
Simple question for Llama-3.2-1B
```
~/src/llama.cpp$ ./scripts/snapdragon/run.py --target adb -- llama-completion -m models/gemma-2-2b-it-Q4_0.gguf -f prompts/sample_prompt_1024.txt --jinja -st
...
ggml-hex: Hexagon backend (experimental) : allocating new registry : ndev 1
ggml-hex: Hexagon Arch version v79
ggml-hex: allocating new session: HTP0:0
...
load_tensors: offloading output layer to GPU
load_tensors: offloaded 27/27 layers to GPU
load_tensors: CPU model buffer size = 300.00 MiB
load_tensors: HTP0:0 model buffer size = 1400.26 MiB
...
llama_perf_context_print: prompt eval time = 320.00 ms / 1024 tokens ( 0.31 ms per token, 3200.00 tokens per second)
llama_perf_context_print: eval time = 2100.00 ms / 100 runs ( 21.00 ms per token, 47.62 tokens per second)
```
Simple question for Llama-3.2-1B:
```
~/src/llama.cpp$ ./scripts/snapdragon/run.py --target android --devices HTP0 -- llama-cli -m Llama-3.2-1B-Instruct-Q4_0.gguf -p "what is the most popular cookie in the world?"
~/src/llama.cpp$ M=Llama-3.2-1B-Instruct-Q4_0.gguf D=HTP0 ./scripts/snapdragon/adb/run-completion.sh -p "what is the most popular cookie in the world?"
...
ggml-hex: Hexagon backend (experimental) : allocating new registry : ndev 1
ggml-hex: Hexagon Arch version v79
@@ -170,7 +142,8 @@ ggml-hex: new session: HTP0 : session-id 0 domain-id 3 uri file:///libggml-htp-v
load_tensors: offloading output layer to GPU
load_tensors: offloaded 17/17 layers to GPU
load_tensors: CPU model buffer size = 225.49 MiB
load_tensors: HTP0 model buffer size = 504.26 MiB
load_tensors: HTP0 model buffer size = 0.26 MiB
load_tensors: HTP0-REPACK model buffer size = 504.00 MiB
...
I hope this helps you understand the world's most popular cookies! [end of text]
...
@@ -183,25 +156,60 @@ llama_perf_context_print: graphs reused = 473
llama_memory_breakdown_print: | memory breakdown [MiB] | total free self model context compute unaccounted |
llama_memory_breakdown_print: | - HTP0 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - Host | 439 = 225 + 136 + 77 |
llama_memory_breakdown_print: | - HTP0-REPACK | 504 = 504 + 0 + 0 |
```
Op test for MUL_MAT:
Summary request for OLMoE-1B-7B. This is a large model that requires two HTP sessions/devices
```
~/src/llama.cpp$ ./scripts/snapdragon/run.py --target adb --hex-hostbuf 0 --devices HTP0:0 -- test-backend-ops -b HTP0:0 -o MUL_MAT
~/src/llama.cpp$ M=OLMoE-1B-7B-0125-Instruct-Q4_0.gguf NDEV=2 D=HTP0,HTP1 ./scripts/snapdragon/adb/run-completion.sh -f surfing.txt
...
Backend 2/3: HTP0:0
ggml-hex: Hexagon backend (experimental) : allocating new registry : ndev 1
ggml-hex: Hexagon Arch version v81
ggml-hex: allocating new session: HTP0
ggml-hex: allocating new session: HTP1
...
load_tensors: offloading output layer to GPU
load_tensors: offloaded 17/17 layers to GPU
load_tensors: CPU model buffer size = 143.86 MiB
load_tensors: HTP1 model buffer size = 0.23 MiB
load_tensors: HTP1-REPACK model buffer size = 1575.00 MiB
load_tensors: HTP0 model buffer size = 0.28 MiB
load_tensors: HTP0-REPACK model buffer size = 2025.00 MiB
...
llama_context: CPU output buffer size = 0.19 MiB
llama_kv_cache: HTP1 KV buffer size = 238.00 MiB
llama_kv_cache: HTP0 KV buffer size = 306.00 MiB
llama_kv_cache: size = 544.00 MiB ( 8192 cells, 16 layers, 1/1 seqs), K (q8_0): 272.00 MiB, V (q8_0): 272.00 MiB
llama_context: HTP0 compute buffer size = 15.00 MiB
llama_context: HTP1 compute buffer size = 15.00 MiB
llama_context: CPU compute buffer size = 24.56 MiB
...
llama_perf_context_print: prompt eval time = 1730.57 ms / 212 tokens ( 8.16 ms per token, 122.50 tokens per second)
llama_perf_context_print: eval time = 5624.75 ms / 257 runs ( 21.89 ms per token, 45.69 tokens per second)
llama_perf_context_print: total time = 7377.33 ms / 469 tokens
llama_perf_context_print: graphs reused = 255
llama_memory_breakdown_print: | memory breakdown [MiB] | total free self model context compute unaccounted |
llama_memory_breakdown_print: | - HTP0 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP1 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - Host | 742 = 144 + 544 + 54 |
llama_memory_breakdown_print: | - HTP1-REPACK | 1575 = 1575 + 0 + 0 |
llama_memory_breakdown_print: | - HTP0-REPACK | 2025 = 2025 + 0 + 0 |
```
Op test for MUL_MAT
```
~/src/llama.cpp$ HB=0 ./scripts/snapdragon/adb/run-tool.sh test-backend-ops -b HTP0 -o MUL_MAT
...
Backend 2/3: HTP0
Device description: Hexagon
Device memory: 2048 MB (2048 MB free)
MUL_MAT(type_a=q4_0,type_b=f32,m=16,n=1,k=256,bs=[1,1],nr=[1,1],per=[0,1,2,3],v=0,o=1): OK
MUL_MAT(type_a=q4_0,type_b=f32,m=16,n=2,k=256,bs=[1,1],nr=[1,1],per=[0,1,2,3],v=0,o=1): OK
MUL_MAT(type_a=q4_0,type_b=f32,m=16,n=3,k=256,bs=[1,1],nr=[1,1],per=[0,1,2,3],v=0,o=1): OK
```
Llama benchmark:
```
~/src/llama.cpp$ ./scripts/snapdragon/run.py --target adb --devices HTP0 -- llama-bench -p 128 -n 64 -m Llama-3.2-1B-Instruct-Q4_0.gguf
~/src/llama.cpp-hexagon$ M=Llama-3.2-1B-Instruct-Q4_0.gguf ./scripts/snapdragon/adb/run-bench.sh -p 128 -n 64
...
ggml-hex: Hexagon backend (experimental) : allocating new registry : ndev 1
ggml-hex: Hexagon Arch version v79
@@ -211,20 +219,15 @@ ggml-hex: new session: HTP0 : session-id 0 domain-id 3 uri file:///libggml-htp-v
| ---------------| ---------: | -----: | ---------- | --: | ------: | ------: | ---: | ----: | ------------: |
| llama 1B Q4_0 | 729.75 MiB | 1.24 B | HTP | 99 | 4 | 128 | 0 | pp128 | 169.42 ± 1.75 |
| llama 1B Q4_0 | 729.75 MiB | 1.24 B | HTP | 99 | 4 | 128 | 0 | tg64 | 51.54 ± 1.13 |
build: 6a8cf8914 (6733)
```
## Environment variables
- `GGML_HEXAGON_DEVICES` (default: not set, defaults to HTP0 session)
Controls which NPU devices and sessions to allocate. Can be configured as:
- A single integer `N`: Allocates `N` sessions named `HTP0`, `HTP1`, ..., `HTP<N-1>` (behaves identically to `GGML_HEXAGON_NDEV=N`).
- A comma-separated list of device names in `HTP<physical_idx>:<virtual_idx>` format (or legacy `HTP<idx>` format). For example, `HTP0:0,HTP0:1` creates two virtual
sessions on the first physical NPU (useful for memory limits). `HTP0:0,HTP1:0` allocates one session on each of the two physical NPUs
on a dual-NPU device.
- `GGML_HEXAGON_NDEV` (deprecated)
Replaced by `GGML_HEXAGON_DEVICES`. Controls the number of virtual sessions to allocate on physical NPU `0`.
Allocates sessions named `HTP0`, `HTP1`, etc.
- `GGML_HEXAGON_NDEV=1`
Controls the number of devices/sessions to allocate. The default is 1.
Most quantized models under 4B fit into a single session; an 8B model needs two, and a 20B model needs four.
- `GGML_HEXAGON_NHVX=0`
Controls the number of HVX hardware threads to use. The default is all (actual number varies depending on the hardware version).
@@ -252,17 +255,26 @@ ggml-hex: new session: HTP0 : session-id 0 domain-id 3 uri file:///libggml-htp-v
- `2` Extended profile with per-op `usecs`, `cycles` and default PMU counter data
- `0x1,...,0x8` Extended profile with per-op `usecs`, `cycles` and custom PMU counter data
The logging output can be either saved into a file for post-processing or it can be piped directly into the post-processing tool
to generate the report.
The logging output can be either saved into a file for post-processing or it can be piped directly into the post-processing tool to generate the report.
Examples:
`GGML_HEXAGON_PROFILE=1 ./scripts/snapdragon/run.py --target adb -- llama-cli ... |& ./scripts/snapdragon/ggml-hexagon-profile.py -`
`GGML_HEXAGON_PROFILE=1 llama-completion ... |& ./scripts/snapdragon/ggml-hexagon-profile.py -`
- `GGML_HEXAGON_OPSTAGE=0x0`
Allows enabling specific stages of the Op processing pipeline:
- `0x1` Enable Op Queue (i.e., queuing Ops into NPU)
- `0x2` Enable Op Compute (MUL_MAT, etc.)
Examples:
`GGML_HEXAGON_OPSTAGE=0x1 llama-completion ...` - Ops are enqueued to the NPU but dma & compute are disabled
`GGML_HEXAGON_OPSTAGE=0x3 llama-completion ...` - Full queuing and processing of Ops (default)
- `GGML_HEXAGON_OPFILTER=regex`
Allows filtering (disabling) Ops that match the regex pattern:
Examples:
`GGML_HEXAGON_OPFILTER="FLASH_ATTN_EXT" ./scripts/snapdragon/run.py --target adb -- llama-cli ...` - Disable Flash Attention on Hexagon (falls back to CPU or GPU)
`GGML_HEXAGON_OPFILTER="ADD\|SUB" ./scripts/snapdragon/run.py --target adb -- llama-cli ...` - Disable ADD and SUB on Hexagon (fall back to CPU or GPU)
`GGML_HEXAGON_OPFILTER="FLASH_ATTN_EXT" llama-completion ...` - Disable Flash Attention on Hexagon (falls back to CPU or GPU)
`GGML_HEXAGON_OPFILTER="ADD\|SUB" llama-completion ...` - Disable ADD and SUB on Hexagon (fall back to CPU or GPU)
+40 -31
View File
@@ -39,21 +39,22 @@ the repacking.
## Large model handling
Hexagon NPU sessions (aka Process Domains (PD) in the Hexagon SDK) are limited to a maximum memory mapping window of around 3.5GB.
In llama.cpp/GGML, each Hexagon session is mapped to a single GGML backend device (e.g., `HTP0:0`, `HTP0:1`, etc. when using
`GGML_HEXAGON_DEVICES`, or `HTP0`, `HTP1` in legacy mode).
Hexagon NPU session (aka Process Domain (PD) in the Hexagon docs) is limited to a memory mapping of around 3.5GB.
In llama.cpp/GGML the Hexagon session is mapped to a single GGML backend device (HTP0, HTP1, etc).
To support running models larger than 3.5GB on a single device, the Hexagon backend dynamically maps and unmaps execution buffers
during the graph execution cycle to stay within the Process Domain window. This enables large models to run successfully on a single
NPU device.
In order to map models larger than 3.5GB we need to allocate multiple devices and split the model.
For this we're taking advantage of the llama.cpp/GGML multi-GPU layer-splitting support.
Each Hexagon device behaves like a GPU from the offload and model splitting perspective.
Alternatively, users can choose to use standard llama.cpp/GGML layer-splitting mode to partition and split the model across
multiple Hexagon devices or virtual sessions (which behave like multiple GPUs from the offload and splitting perspective).
Here is an example of running GPT-OSS-20B model on a Snapdragon device using 4 virtual sessions on a single NPU (physical index 0).
Here is an example of running GPT-OSS-20B model on a newer Snapdragon device with 16GB of DDR.
```
~/src/llama.cpp$ ./scripts/snapdragon/run.py --target adb --devices HTP0:0,HTP0:1,HTP0:2,HTP0:3 -- llama-cli --load-mode none -m /data/local/tmp/gguf/gpt-oss-20b-Q4_0.gguf -t 4 --ctx-size 8192 --batch-size 128 -ctk q8_0 -ctv q8_0 -fa on -ngl 99 -no-cnv -f surfing.txt
M=gpt-oss-20b-Q4_0.gguf NDEV=4 D=HTP0,HTP1,HTP2,HTP3 P=surfing.txt scripts/snapdragon/adb/run-completion.sh -f surfing.txt -n 32
...
LD_LIBRARY_PATH=/data/local/tmp/llama.cpp/lib
ADSP_LIBRARY_PATH=/data/local/tmp/llama.cpp/lib
GGML_HEXAGON_NDEV=4 ./bin/llama-cli --load-mode none -m /data/local/tmp/llama.cpp/../gguf/gpt-oss-20b-Q4_0.gguf
-t 4 --ctx-size 8192 --batch-size 128 -ctk q8_0 -ctv q8_0 -fa on -ngl 99 --device HTP0,HTP1,HTP2,HTP3 -no-cnv -f surfing.txt
...
llama_model_loader: - type f32: 289 tensors
llama_model_loader: - type q4_0: 96 tensors
@@ -62,29 +63,33 @@ llama_model_loader: - type mxfp4: 72 tensors
...
load_tensors: offloaded 25/25 layers to GPU
load_tensors: CPU model buffer size = 1182.09 MiB
load_tensors: HTP0:1 model buffer size = 2512.58 MiB
load_tensors: HTP0:3 model buffer size = 2093.83 MiB
load_tensors: HTP0:0 model buffer size = 2931.34 MiB
load_tensors: HTP0:2 model buffer size = 2512.58 MiB
load_tensors: HTP1 model buffer size = 6.64 MiB
load_tensors: HTP1-REPACK model buffer size = 2505.94 MiB
load_tensors: HTP3 model buffer size = 5.55 MiB
load_tensors: HTP3-REPACK model buffer size = 2088.28 MiB
load_tensors: HTP0 model buffer size = 7.75 MiB
load_tensors: HTP0-REPACK model buffer size = 2923.59 MiB
load_tensors: HTP2 model buffer size = 6.64 MiB
load_tensors: HTP2-REPACK model buffer size = 2505.94 MiB
...
llama_context: n_ctx_per_seq (8192) < n_ctx_train (131072) -- the full capacity of the model will not be utilized
llama_context: CPU output buffer size = 0.77 MiB
llama_kv_cache_iswa: creating non-SWA KV cache, size = 8192 cells
llama_kv_cache: HTP0:1 KV buffer size = 25.50 MiB
llama_kv_cache: HTP0:3 KV buffer size = 25.50 MiB
llama_kv_cache: HTP0:0 KV buffer size = 25.50 MiB
llama_kv_cache: HTP0:2 KV buffer size = 25.50 MiB
llama_kv_cache: HTP1 KV buffer size = 25.50 MiB
llama_kv_cache: HTP3 KV buffer size = 25.50 MiB
llama_kv_cache: HTP0 KV buffer size = 25.50 MiB
llama_kv_cache: HTP2 KV buffer size = 25.50 MiB
llama_kv_cache: size = 102.00 MiB ( 8192 cells, 12 layers, 1/1 seqs), K (q8_0): 51.00 MiB, V (q8_0): 51.00 MiB
llama_kv_cache_iswa: creating SWA KV cache, size = 256 cells
llama_kv_cache: HTP0:1 KV buffer size = 0.80 MiB
llama_kv_cache: HTP0:3 KV buffer size = 0.53 MiB
llama_kv_cache: HTP0:0 KV buffer size = 1.06 MiB
llama_kv_cache: HTP0:2 KV buffer size = 0.80 MiB
llama_kv_cache: HTP1 KV buffer size = 0.80 MiB
llama_kv_cache: HTP3 KV buffer size = 0.53 MiB
llama_kv_cache: HTP0 KV buffer size = 1.06 MiB
llama_kv_cache: HTP2 KV buffer size = 0.80 MiB
llama_kv_cache: size = 3.19 MiB ( 256 cells, 12 layers, 1/1 seqs), K (q8_0): 1.59 MiB, V (q8_0): 1.59 MiB
llama_context: HTP0:0 compute buffer size = 16.06 MiB
llama_context: HTP0:1 compute buffer size = 16.06 MiB
llama_context: HTP0:2 compute buffer size = 16.06 MiB
llama_context: HTP0:3 compute buffer size = 16.06 MiB
llama_context: HTP0 compute buffer size = 16.06 MiB
llama_context: HTP1 compute buffer size = 16.06 MiB
llama_context: HTP2 compute buffer size = 16.06 MiB
llama_context: HTP3 compute buffer size = 16.06 MiB
llama_context: CPU compute buffer size = 98.19 MiB
...
llama_perf_context_print: prompt eval time = 3843.67 ms / 197 tokens ( 19.51 ms per token, 51.25 tokens per second)
@@ -92,9 +97,13 @@ llama_perf_context_print: eval time = 1686.13 ms / 31 runs ( 54.3
llama_perf_context_print: total time = 6266.30 ms / 228 tokens
llama_perf_context_print: graphs reused = 30
llama_memory_breakdown_print: | memory breakdown [MiB] | total free self model context compute unaccounted |
llama_memory_breakdown_print: | - HTP0:0 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP0:1 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP0:2 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP0:3 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP0 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP1 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP2 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - HTP3 (Hexagon) | 2048 = 2048 + ( 0 = 0 + 0 + 0) + 0 |
llama_memory_breakdown_print: | - Host | 1476 = 1208 + 105 + 162 |
llama_memory_breakdown_print: | - HTP1-REPACK | 2505 = 2505 + 0 + 0 |
llama_memory_breakdown_print: | - HTP3-REPACK | 2088 = 2088 + 0 + 0 |
llama_memory_breakdown_print: | - HTP0-REPACK | 2923 = 2923 + 0 + 0 |
llama_memory_breakdown_print: | - HTP2-REPACK | 2505 = 2505 + 0 + 0 |
```

Some files were not shown because too many files have changed in this diff Show More