Compare commits

..
Author SHA1 Message Date
Georgi Gerganov 52cfe0f4aa mimo2 : expose t_layer_inp for the DFlash drafter
Assisted-by: pi:llama.cpp/Qwen3.8-27B
2026-09-28 18:05:48 +03:00
Georgi Gerganov 1ea03b5701 convert : handle dict/list-wrapped mask_embedding.pt in DFlash drafter
torch.load may return a dict or list when the file was saved with a wrapper
(e.g. torch.save({"mask_embedding": tensor})) rather than a bare tensor.
Unwrap the first value before reshaping so the mask row patch does not crash
with "Could not infer dtype of dict".

Assisted-by: pi:llama.cpp/Qwen3.8-27B
2026-09-28 16:39:22 +03:00
Georgi Gerganov 50aa599b6f convert : add partial rotary, value scale and mask embd to DFlash drafter
DFlashModel gains support for:
- partial rotary (rope.dimension_count = head_dim * partial_rotary_factor)
- attention value scale (attention.value_scale)
- a synthesized token_embd built from the target's table with the mask row
  replaced by mask_embedding.pt (the target's mask row is untrained/zeros)

The target_layers offset ([i + 1] -> stream after the extract layer) is
unchanged from before.

Assisted-by: pi:llama.cpp/Qwen3.8-27B
2026-09-28 16:32:03 +03:00
564 changed files with 18068 additions and 54586 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
ARG ONEAPI_VERSION=2026.1.1-devel-ubuntu24.04
ARG ONEAPI_VERSION=2025.3.3-0-devel-ubuntu24.04
ARG BUILD_DATE=N/A
ARG APP_VERSION=N/A
ARG APP_REVISION=N/A
@@ -19,7 +19,7 @@ RUN npm ci
COPY tools/ui/ ./
RUN LLAMA_BUILD_NUMBER="$APP_VERSION" npm run build
FROM docker.io/intel/oneapi-toolkit:$ONEAPI_VERSION AS build
FROM docker.io/intel/deep-learning-essentials:$ONEAPI_VERSION AS build
ARG GGML_SYCL_F16=ON
ARG LEVEL_ZERO_VERSION=1.28.2
@@ -59,7 +59,7 @@ RUN mkdir -p /app/full \
&& cp requirements.txt /app/full \
&& cp .devops/tools.sh /app/full/tools.sh
FROM docker.io/intel/oneapi-toolkit:$ONEAPI_VERSION AS base
FROM docker.io/intel/deep-learning-essentials:$ONEAPI_VERSION AS base
ARG BUILD_DATE=N/A
ARG APP_VERSION=N/A
+4 -10
View File
@@ -1,9 +1,9 @@
ARG UBUNTU_VERSION=22.04
# This needs to generally match the container host's environment.
# Target the MUSA build image
ARG BASE_MUSA_DEV_CONTAINER=registry.mthreads.com/mcconline/musa_sdk:5.2.0-devel-ubuntu${UBUNTU_VERSION}-s5000
ARG BASE_MUSA_DEV_CONTAINER=registry.mthreads.com/mcconline/inference/pytorch:2.9.1.post1-py3.10-musa5.2.0-mp31-devel-ubuntu${UBUNTU_VERSION}-amd64
ARG BASE_MUSA_RUN_CONTAINER=registry.mthreads.com/mcconline/musa_sdk:5.2.0-runtime-ubuntu${UBUNTU_VERSION}-s5000
ARG BASE_MUSA_RUN_CONTAINER=${BASE_MUSA_DEV_CONTAINER}
ARG BUILD_DATE=N/A
ARG APP_VERSION=N/A
@@ -36,10 +36,7 @@ RUN apt-get update && \
python3-pip \
git \
libssl-dev \
libgomp1 \
musa-mualg-5-2 \
musa-muthrust-5-2 \
libmthreads-compute
libgomp1
WORKDIR /app
@@ -82,16 +79,13 @@ LABEL org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.source=$IMAGE_SOURCE
RUN apt-get update \
&& apt-get install -y libgomp1 curl ffmpeg libmthreads-compute \
&& apt-get install -y libgomp1 curl ffmpeg \
&& apt autoremove -y \
&& apt clean -y \
&& rm -rf /tmp/* /var/tmp/* \
&& find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \
&& find /var/cache -type f -delete
# The MUSA runtime image does not register its library directory
RUN echo "/usr/local/musa/lib" > /etc/ld.so.conf.d/musa-runtime.conf && ldconfig
COPY --from=build /app/lib/ /app
### Full
+6 -6
View File
@@ -1,12 +1,12 @@
ARG OPENVINO_VERSION_MAJOR=2026.4.1
ARG OPENVINO_VERSION_FULL=2026.4.1.22982.07f9c262b05
ARG OPENVINO_VERSION_MAJOR=2026.4
ARG OPENVINO_VERSION_FULL=2026.4.0.22959.99c81491cc3
ARG UBUNTU_VERSION=24.04
# Intel GPU driver versions. https://github.com/intel/compute-runtime/releases
ARG IGC_VERSION=v2.41.5
ARG IGC_VERSION_FULL=2_2.41.5+22716
ARG COMPUTE_RUNTIME_VERSION=26.35.39758.10
ARG COMPUTE_RUNTIME_VERSION_FULL=26.35.39758.10-0
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 IGDGMM_VERSION=22.10.0
# Intel NPU driver versions. https://github.com/intel/linux-npu-driver/releases
-4
View File
@@ -4,10 +4,6 @@ on:
issues:
types: [opened]
cache-mode: none
permissions:
contents: read
jobs:
find-related:
if: github.event.action == 'opened'
-4
View File
@@ -15,10 +15,6 @@ on:
'**/*.cpp'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -23,10 +23,6 @@ on:
- 'scripts/snapdragon/**'
- 'CMakePresets.json'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -40,10 +36,6 @@ jobs:
run:
shell: bash
permissions:
actions: write
contents: read
steps:
- name: Clone
uses: actions/checkout@v6
@@ -74,10 +66,6 @@ jobs:
run:
shell: bash
permissions:
actions: write
contents: read
steps:
- name: Clone
uses: actions/checkout@v6
@@ -110,10 +98,6 @@ jobs:
matrix:
device: [SM8750, SM8850, QCS9075M]
permissions:
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
-8
View File
@@ -20,10 +20,6 @@ on:
- '.github/workflows/build-android.yml'
- 'examples/llama.android/**'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -70,10 +66,6 @@ jobs:
run:
shell: bash
permissions:
actions: write
contents: read
steps:
- name: Clone
uses: actions/checkout@v6
+3 -16
View File
@@ -26,10 +26,6 @@ on:
'ggml/src/ggml-rpc/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -54,7 +50,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: apple-arm64
save: false
- name: ccache-buckets-restore
@@ -103,9 +99,8 @@ jobs:
id: cmake_test
run: |
cd build
# Metal Paravirtual devices are difficult to support -> disable
# ref: https://github.com/ggml-org/llama.cpp/pull/19802#issuecomment-4013704023
ctest -L main -E "test-llama-archs|test-save-load-state|test-recurrent-state-rollback" --verbose --timeout 900
ctest -L main -E "test-llama-archs|test-save-load-state" --verbose --timeout 900
macos-latest-x64:
runs-on: macos-15-intel
@@ -118,7 +113,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: apple-x64
save: false
- name: ccache-buckets-restore
@@ -166,10 +161,6 @@ jobs:
macos-latest-ios-xcode:
runs-on: macos-latest
permissions:
actions: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
@@ -267,10 +258,6 @@ jobs:
runs-on: macos-latest
needs: macos-latest-ios-xcode
permissions:
actions: read
contents: read
strategy:
matrix:
destination: ['generic/platform=macOS', 'generic/platform=iOS', 'generic/platform=tvOS']
+4 -8
View File
@@ -5,10 +5,6 @@ on:
schedule:
- cron: '0 * * * *'
cache-mode: write
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -45,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.4.1"
OPENVINO_VERSION_FULL: "2026.4.1.22982.07f9c262b05"
OPENVINO_VERSION_MAJOR: "2026.4"
OPENVINO_VERSION_FULL: "2026.4.0.22959.99c81491cc3"
steps:
- name: Clone
@@ -73,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.4.1"
OPENVINO_VERSION_FULL: "2026.4.1.22982.07f9c262b05"
OPENVINO_VERSION_MAJOR: "2026.4"
OPENVINO_VERSION_FULL: "2026.4.0.22959.99c81491cc3"
steps:
- name: Clone
-4
View File
@@ -22,10 +22,6 @@ on:
'ggml/src/ggml-cann/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-4
View File
@@ -3,10 +3,6 @@ on:
workflow_dispatch:
workflow_call:
cache-mode: none
permissions:
contents: read
jobs:
linux:
runs-on: [self-hosted, Linux, CPU]
+1 -10
View File
@@ -30,10 +30,6 @@ on:
'**/*.cpp'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -69,7 +65,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: cpu-${{ matrix.os }}
save: false
- name: Build Dependencies
@@ -146,11 +142,6 @@ jobs:
name: windows / ${{ matrix.build }}
runs-on: windows-2025
cache-mode: write
permissions:
actions: write
contents: read
env:
OPENBLAS_VERSION: 0.3.23
SDE_VERSION: 9.33.0-2024-01-07
-4
View File
@@ -15,10 +15,6 @@ on:
schedule:
- cron: '0 0 * * 0'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+7 -11
View File
@@ -24,10 +24,6 @@ on:
'ggml/src/ggml-cuda/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -59,7 +55,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: cuda-ubuntu-24.04-cuda
save: false
- name: ccache-buckets-restore
@@ -72,7 +68,7 @@ jobs:
hf_bucket: ggml-org/cache
- name: Build with CMake
# TODO: Drop GGML_CUDA_CCCL_VERSION when this job uses CTK >= 13.5, which bundles CCCL >= 3.5.
# TODO: Remove GGML_CUDA_CUB_3DOT2 flag once CCCL 3.2 is bundled within CTK and that CTK version is used in this project
run: |
cmake -S . -B build -G Ninja \
-DLLAMA_FATAL_WARNINGS=ON \
@@ -81,7 +77,7 @@ jobs:
-DCMAKE_EXE_LINKER_FLAGS=-Wl,--allow-shlib-undefined \
-DGGML_NATIVE=OFF \
-DGGML_CUDA=ON \
-DGGML_CUDA_CCCL_VERSION=v3.4.3
-DGGML_CUDA_CUB_3DOT2=ON
cmake --build build
- name: ccache-buckets-save
@@ -114,7 +110,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: cuda-ubuntu-22.04-hip
save: false
- name: ccache-buckets-restore
@@ -149,7 +145,7 @@ jobs:
musa:
runs-on: ubuntu-22.04
container: registry.mthreads.com/mcconline/musa_sdk:5.2.0-devel-ubuntu22.04-s5000
container: registry.mthreads.com/mcconline/inference/pytorch:2.9.1.post1-py3.10-musa5.2.0-mp31-devel-ubuntu22.04-amd64
steps:
- name: Clone
@@ -160,12 +156,12 @@ jobs:
id: depends
run: |
apt-get update
apt-get install -y build-essential git cmake libssl-dev jq python3-venv musa-mualg-5-2 musa-muthrust-5-2 libmthreads-compute
apt-get install -y build-essential git cmake libssl-dev jq python3-venv
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: cuda-ubuntu-22.04-musa
save: false
- name: ccache-buckets-restore
+3 -9
View File
@@ -7,11 +7,6 @@ name: CI (CUDA, windows)
on:
workflow_dispatch: # allows manual triggering
cache-mode: write
permissions:
actions: write
contents: read
# note: this will run in queue with the release workflow
concurrency:
group: release
@@ -36,16 +31,15 @@ jobs:
strategy:
matrix:
include:
# CTK >= 13.5 bundles CCCL >= 3.5; omit GGML_CUDA_CCCL_VERSION for those versions.
- cuda: '12.4'
arch: x64
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
defines: '-DGGML_CUDA_CUB_3DOT2=ON'
- cuda: '13.4'
arch: x64
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
defines: ''
- cuda: '13.4'
arch: arm64
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3 -DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
defines: '-DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
steps:
- name: Clone
+1 -46
View File
@@ -19,14 +19,9 @@ on:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/build-ibm.yml',
'ggml/src/ggml-cpu/**',
'ggml/src/ggml-zdnn/**'
'ggml/src/ggml-cpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -105,46 +100,6 @@ jobs:
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
ubuntu-26-zdnn-s390x:
name: ubuntu-26-zdnn-s390x
runs-on: ubuntu-24.04-s390x
container: ubuntu:26.04 # required to get GCC 15.1 and binutils 2.44
defaults:
run:
shell: bash
steps:
- name: Build Dependencies
id: build_depends
run: |
apt-get update
apt-get install -y --no-install-recommends \
build-essential cmake git ca-certificates \
libssl-dev libzdnn-dev
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Toolchain workaround (GCC 15)
run: |
apt-get install -y gcc-15 g++-15
echo "CC=gcc-15" >> "$GITHUB_ENV"
echo "CXX=g++-15" >> "$GITHUB_ENV"
- name: Build with zDNN Backend
id: cmake_build
run: |
cmake -B build \
-DLLAMA_FATAL_WARNINGS=ON \
-DGGML_NATIVE=OFF \
-DGGML_VXE=ON \
-DGGML_ZDNN=ON \
-DGGML_RPC=ON \
-DCMAKE_C_FLAGS="-march=arch15" \
-DCMAKE_CXX_FLAGS="-march=arch15"
time cmake --build build --config Release -j $(nproc)
ubuntu-24-ppc64le:
runs-on: ubuntu-24.04-ppc64le
-4
View File
@@ -8,10 +8,6 @@ on:
schedule:
- cron: '0 0 * * 0'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-5
View File
@@ -23,11 +23,6 @@ on:
'ggml/src/ggml-opencl/**'
]
cache-mode: write
permissions:
actions: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+4 -13
View File
@@ -22,10 +22,6 @@ on:
'ggml/src/ggml-openvino/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -45,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.4.1"
OPENVINO_VERSION_FULL: "2026.4.1.22982.07f9c262b05"
OPENVINO_VERSION_MAJOR: "2026.4"
OPENVINO_VERSION_FULL: "2026.4.0.22959.99c81491cc3"
steps:
- name: Clone
@@ -98,15 +94,10 @@ jobs:
openvino-windows-2022:
runs-on: windows-2022
cache-mode: write
permissions:
actions: write
contents: read
env:
# Sync versions in build-openvino.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.4.1"
OPENVINO_VERSION_FULL: "2026.4.1.22982.07f9c262b05"
OPENVINO_VERSION_MAJOR: "2026.4"
OPENVINO_VERSION_FULL: "2026.4.0.22959.99c81491cc3"
steps:
- name: Clone
-4
View File
@@ -22,10 +22,6 @@ on:
'ggml/src/ggml-cpu/arch/riscv/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-4
View File
@@ -21,10 +21,6 @@ on:
'.github/workflows/build-sanitize.yml'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+6 -15
View File
@@ -22,10 +22,6 @@ on:
'ggml/src/ggml-sycl/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -52,7 +48,7 @@ jobs:
env:
ONEAPI_ROOT: /opt/intel/oneapi/
ONEAPI_INSTALLER_VERSION: "2026.1"
ONEAPI_INSTALLER_VERSION: "2025.3.3"
LEVEL_ZERO_VERSION: "1.33.1"
LEVEL_ZERO_UBUNTU_VERSION: "u24.04"
@@ -67,8 +63,8 @@ jobs:
shell: bash
run: |
cd /tmp
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/5996e26b-f48a-42b1-8db0-b002ad0bd8d7/intel-oneapi-toolkit-2026.1.1.33_offline.sh -O intel-oneapi-toolkit_offline.sh
sudo bash intel-oneapi-toolkit_offline.sh -s -a --silent --eula accept
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/56f7923a-adb8-43f3-8b02-2b60fcac8cab/intel-deep-learning-essentials-2025.3.3.16_offline.sh -O intel-deep-learning-essentials_offline.sh
sudo bash intel-deep-learning-essentials_offline.sh -s -a --silent --eula accept
- name: Install Level Zero SDK
shell: bash
@@ -82,7 +78,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: sycl-ubuntu-24-${{ matrix.build }}
save: false
- name: ccache-buckets-restore
@@ -128,21 +124,16 @@ jobs:
windows-latest-sycl:
runs-on: windows-2022
cache-mode: write
permissions:
actions: write
contents: read
defaults:
run:
shell: bash
env:
WINDOWS_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/0cb67a0d-67f6-410b-868b-f4a0a17ff0cf/intel-oneapi-toolkit-2026.1.1.32_offline.exe
WINDOWS_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b60765d1-2b85-4e85-86b6-cb0e9563a699/intel-deep-learning-essentials-2025.3.3.18_offline.exe
WINDOWS_DPCPP_MKL: intel.oneapi.win.cpp-dpcpp-common:intel.oneapi.win.mkl.devel:intel.oneapi.win.dnnl:intel.oneapi.win.tbb.devel
LEVEL_ZERO_SDK_URL: https://github.com/oneapi-src/level-zero/releases/download/v1.33.1/level-zero-win-sdk-1.33.1.zip
ONEAPI_ROOT: "C:/Program Files (x86)/Intel/oneAPI"
ONEAPI_INSTALLER_VERSION: "2026.1"
ONEAPI_INSTALLER_VERSION: "2025.3.3"
steps:
- name: Clone
id: checkout
-4
View File
@@ -22,10 +22,6 @@ on:
'ggml/src/ggml-virtgpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+7 -24
View File
@@ -24,10 +24,6 @@ on:
'ggml/src/ggml-vulkan/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -60,7 +56,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: vulkan-ubuntu-24.04-arm
variant: ccache
save: false
@@ -129,7 +125,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: vulkan-ubuntu-24.04-llvmpipe
save: false
- name: ccache-buckets-restore
@@ -172,20 +168,8 @@ jobs:
ctest -L main --verbose --timeout 900
windows:
name: windows / ${{ matrix.arch }}
runs-on: windows-2025
cache-mode: write
permissions:
actions: write
contents: read
strategy:
matrix:
include:
- arch: 'x64'
- arch: 'arm64'
env:
VULKAN_VERSION: 1.4.357.0
@@ -197,7 +181,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
key: cpu-windows-2025-${{ matrix.arch }}-vulkan
key: cpu-windows-2025-x64-vulkan
variant: ccache
evict-old-files: 1d
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
@@ -206,7 +190,7 @@ jobs:
id: get_vulkan
run: |
curl.exe -o $env:RUNNER_TEMP/VulkanSDK-Installer.exe -L "https://sdk.lunarg.com/sdk/download/${env:VULKAN_VERSION}/windows/vulkansdk-windows-X64-${env:VULKAN_VERSION}.exe"
& "$env:RUNNER_TEMP\VulkanSDK-Installer.exe" --accept-licenses --default-answer --confirm-command install ${{ matrix.arch == 'arm64' && 'com.lunarg.vulkan.arm64' || '' }}
& "$env:RUNNER_TEMP\VulkanSDK-Installer.exe" --accept-licenses --default-answer --confirm-command install
Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\${env:VULKAN_VERSION}"
Add-Content $env:GITHUB_PATH "C:\VulkanSDK\${env:VULKAN_VERSION}\bin"
@@ -219,19 +203,18 @@ jobs:
id: cmake_build
run: |
cmake -S . -B build -G "Ninja Multi-Config" `
-D CMAKE_TOOLCHAIN_FILE=cmake/${{ matrix.arch }}-windows-llvm.cmake `
-D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake `
-DCMAKE_BUILD_TYPE=Release `
-DGGML_NATIVE=OFF `
-DLLAMA_BUILD_SERVER=ON `
-DGGML_RPC=ON `
-DGGML_BACKEND_DL=ON `
-DGGML_CPU_ALL_VARIANTS=${{ matrix.arch == 'x64' && 'ON' || 'OFF' }} `
-DGGML_CPU_ALL_VARIANTS=ON `
-DGGML_VULKAN=ON `
-DLLAMA_BUILD_BORINGSSL=ON
cmake --build build --config Release -j ${env:NUMBER_OF_PROCESSORS}
- name: Test
if: ${{ matrix.arch == 'x64' }}
id: cmake_test
run: |
cd build
@@ -242,7 +225,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
with:
key: cpu-windows-2025-${{ matrix.arch }}-vulkan
key: cpu-windows-2025-x64-vulkan
older: 5m
min: 1
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
+1 -5
View File
@@ -33,10 +33,6 @@ on:
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -60,7 +56,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: webgpu-ubuntu-24.04-arm-wasm
save: false
- name: Install Emscripten
+2 -6
View File
@@ -25,10 +25,6 @@ on:
'ggml/src/ggml-webgpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -75,7 +71,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: webgpu-macos-latest
save: false
- name: Dawn Dependency
@@ -136,7 +132,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: webgpu-ubuntu-24.04
save: false
- name: Dependencies
-4
View File
@@ -17,10 +17,6 @@ on:
'scripts/sync_vendor.py'
]
cache-mode: none
permissions:
contents: read
jobs:
check-vendor:
runs-on: ubuntu-slim
-4
View File
@@ -27,10 +27,6 @@ on:
'ggml/src/ggml-cpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+1 -5
View File
@@ -30,10 +30,6 @@ on:
'ggml/src/ggml-cuda/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -49,7 +45,7 @@ env:
jobs:
gpu-cuda:
runs-on: "hf-jobs-t4-medium:cuda13"
runs-on: "hf-jobs-t4-small:cuda13"
steps:
- name: Clone
@@ -27,10 +27,6 @@ on:
'ggml/src/ggml-cpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -31,10 +31,6 @@ on:
'ggml/src/ggml-metal/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -28,10 +28,6 @@ on:
'ggml/src/ggml-openvino/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -51,8 +47,8 @@ jobs:
env:
# Sync versions in build.yml, build-self-hosted.yml, release.yml, build-cache.yml, .devops/openvino.Dockerfile
OPENVINO_VERSION_MAJOR: "2026.4.1"
OPENVINO_VERSION_FULL: "2026.4.1.22982.07f9c262b05"
OPENVINO_VERSION_MAJOR: "2026.4"
OPENVINO_VERSION_FULL: "2026.4.0.22959.99c81491cc3"
steps:
- name: Clone
@@ -30,10 +30,6 @@ on:
'ggml/src/ggml-vulkan/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -29,10 +29,6 @@ on:
'ggml/src/ggml-webgpu/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+3 -2
View File
@@ -3,9 +3,10 @@ on:
schedule:
- cron: "42 0 * * *"
cache-mode: none
# Fine-grant permission
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: read
issues: write
jobs:
close-issues:
-4
View File
@@ -9,10 +9,6 @@ on:
branches:
- master
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+10 -17
View File
@@ -20,14 +20,15 @@ on:
# Rebuild daily rather than on every push because it is expensive
- cron: '12 4 * * *'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
# Fine-grant permission
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
packages: write
jobs:
create_tag:
name: Create and push git tag
@@ -61,9 +62,6 @@ jobs:
build_ui:
name: Build UI
needs: create_tag
permissions:
actions: write
contents: read
uses: ./.github/workflows/ui-build.yml
with:
ui_version: ${{ needs.create_tag.outputs.source_tag }}
@@ -148,11 +146,6 @@ jobs:
needs: [prepare_matrices, create_tag, build_ui]
runs-on: ${{ matrix.config.runs_on }}
# cache-mode: write # for QEMU
permissions:
actions: write
contents: read
packages: write
strategy:
fail-fast: false
matrix:
@@ -172,11 +165,11 @@ jobs:
name: llama-ui.zip
path: tools/ui/dist
# - name: Set up QEMU
# if: ${{ contains(matrix.config.platforms, 'linux/amd64') }}
# uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
# with:
# image: tonistiigi/binfmt:qemu-v10.2.1
- name: Set up QEMU
if: ${{ contains(matrix.config.platforms, 'linux/amd64') }}
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
with:
image: tonistiigi/binfmt:qemu-v10.2.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-4
View File
@@ -9,10 +9,6 @@ on:
branches:
- master
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
+71
View File
@@ -0,0 +1,71 @@
name: Fusion
on:
workflow_dispatch: # allows manual triggering
push:
branches:
- master
paths: [
'.github/workflows/fusion.yml',
'ggml/**',
'tests/fusion/**',
'tests/test-fusion.cpp',
'tests/test-llama-archs.cpp',
'src/models/**'
]
pull_request:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/fusion.yml',
'ggml/**',
'tests/fusion/**',
'tests/test-fusion.cpp',
'tests/test-llama-archs.cpp',
'src/models/**'
]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
env:
GGML_NLOOP: 3
GGML_N_THREADS: 1
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
LLAMA_ARG_LOG_TIMESTAMPS: 1
jobs:
# TODO: add jobs for other backends as they adopt the fusion debug API
metal:
runs-on: [self-hosted, macOS, ARM64]
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DGGML_BLAS=OFF \
-DGGML_METAL=ON
time cmake --build build --config Release --target test-llama-archs -j $(sysctl -n hw.logicalcpu)
time cmake --build build --config Release --target test-fusion -j $(sysctl -n hw.logicalcpu)
- name: Generate models
id: generate_models
run: |
rm -rf build-ci-models && mkdir -p build-ci-models
./build/bin/test-llama-archs -o build-ci-models
- name: Test fusion
id: test_fusion
run: |
./build/bin/test-fusion --models build-ci-models --device MTL0 --check tests/fusion/MTL.csv
-3
View File
@@ -17,9 +17,6 @@ on:
tags:
- 'gguf-v*' # Push events to every version tag
cache-mode: none
permissions:
contents: read
jobs:
deploy:
+1 -5
View File
@@ -25,10 +25,6 @@ on:
'scripts/hip/gcn-cdna-vgpr-check.py'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
@@ -58,7 +54,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: hip-quality-check-ubuntu-22.04
save: false
- name: ccache-buckets-restore
-4
View File
@@ -2,10 +2,6 @@ name: "Pull Request Labeler"
on:
- pull_request_target
cache-mode: none
permissions:
contents: read
jobs:
labeler:
permissions:
+2 -18
View File
@@ -18,11 +18,6 @@ on:
required: false
type: boolean
default: false
require_docker:
description: 'Require the Docker workflow to have completed successfully'
required: true
type: boolean
default: true
apiabi_compare_tag:
description: 'Tag to compare against for API/ABI check (default: latest release)'
required: false
@@ -32,7 +27,6 @@ on:
env:
GH_TOKEN: ${{ github.token }}
cache-mode: none
permissions:
contents: write
packages: write
@@ -61,7 +55,6 @@ jobs:
RELEASE_BRANCH: ${{ github.ref_name }}
SKIP_APIABI_CHECK: ${{ github.event.inputs.skip_apiabi_check }}
APIABI_COMPARE_TAG: ${{ github.event.inputs.apiabi_compare_tag }}
REQUIRE_DOCKER: ${{ github.event.inputs.require_docker }}
- name: Create release tag
if: ${{ github.event.inputs.dry_run == 'false' }}
@@ -138,7 +131,7 @@ jobs:
});
- name: Re-tag container images with release version
if: ${{ github.event.inputs.dry_run == 'false' && github.event.inputs.require_docker != 'false' && steps.desc.outputs.nightly_tag != '' }}
if: ${{ github.event.inputs.dry_run == 'false' && steps.desc.outputs.nightly_tag != '' }}
env:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
run: |
@@ -151,23 +144,14 @@ jobs:
VARIANTS=("" "-cuda" "-cuda13" "-vulkan" "-rocm" "-intel" "-musa" "-openvino")
TYPES=("full" "light" "server")
# the release is already created at this point, so keep going on a
# missing image and report all of them at the end
MISSING=()
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}"
if ! docker buildx imagetools create --tag "${dst}" "${src}"; then
MISSING+=("${type}${variant}")
fi
docker buildx imagetools create --tag "${dst}" "${src}"
done
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
echo "::error::failed to re-tag container images for ${NIGHTLY_TAG}:${MISSING[*]}"
exit 1
fi
- name: Dry run summary
if: ${{ github.event.inputs.dry_run == 'true' }}
-449
View File
@@ -1,449 +0,0 @@
name: Models Backend Check
on:
workflow_dispatch: # allows manual triggering
push:
branches:
- master
paths: [
'.github/workflows/models-check.yml',
'ggml/**',
'tests/fusion/**',
'tests/test-fusion.cpp',
'tests/test-llama-archs.cpp',
'src/llama-graph.cpp',
'src/llama-model*',
'src/models/**'
]
pull_request:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/models-check.yml',
'ggml/**',
'tests/fusion/**',
'tests/test-fusion.cpp',
'tests/test-llama-archs.cpp',
'src/llama-graph.cpp',
'src/llama-model*',
'src/models/**'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
env:
# note: this is dud token to avoid rate limiting (https://github.com/ggml-org/llama.cpp/pull/25706#issuecomment-4979941302)
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
GGML_NLOOP: 3
GGML_N_THREADS: 1
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
LLAMA_ARG_LOG_TIMESTAMPS: 1
jobs:
cuda:
runs-on: "hf-jobs-t4-medium:cuda13"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y cmake time 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: models-check-cuda
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc \
-DGGML_CUDA=ON
time cmake --build build --config Release --target test-llama-archs -j$(nproc)
time cmake --build build --config Release --target test-fusion -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: models-check-cuda
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
# - name: Generate models
# id: generate_models
# run: |
# rm -rf build-ci-models && mkdir -p build-ci-models
# ./build/bin/test-llama-archs -o build-ci-models
# TODO: add for backends as they adopt the fusion debug API
# - name: Test fusion
# id: test_fusion
# run: |
# ./build/bin/test-fusion --models build-ci-models --device CUDA0 --check tests/fusion/CUDA.csv
- name: Test archs
id: test_archs
run: |
GGML_CUDA_DEVICES=1 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=2 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=3 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=4 ./build/bin/test-llama-archs -s 1
metal:
runs-on: [self-hosted, macOS, ARM64]
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DGGML_BLAS=OFF \
-DGGML_METAL=ON
time cmake --build build --config Release --target test-llama-archs -j $(sysctl -n hw.logicalcpu)
time cmake --build build --config Release --target test-fusion -j $(sysctl -n hw.logicalcpu)
- name: Generate models
id: generate_models
run: |
rm -rf build-ci-models && mkdir -p build-ci-models
./build/bin/test-llama-archs -o build-ci-models
- name: Test fusion
id: test_fusion
run: |
./build/bin/test-fusion --models build-ci-models --device MTL0 --check tests/fusion/MTL.csv
- name: Test archs
id: test_archs
run: |
GGML_METAL_DEVICES=1 ./build/bin/test-llama-archs -s 1
GGML_METAL_DEVICES=2 ./build/bin/test-llama-archs -s 1
GGML_METAL_DEVICES=3 ./build/bin/test-llama-archs -s 1
GGML_METAL_DEVICES=4 ./build/bin/test-llama-archs -s 1
rocm:
runs-on: [self-hosted, Linux, gfx1201, 1accel]
container: "rocm/dev-ubuntu-24.04:7.2.4-complete"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Install dependencies
run: |
apt update
apt install -y build-essential jq cmake time 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: models-check-rocm
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DCMAKE_HIP_COMPILER=$(hipconfig -l)/clang \
-DGPU_TARGETS=gfx1201 \
-DGGML_HIP=ON
time cmake --build build --config Release --target test-llama-archs -j$(nproc)
time cmake --build build --config Release --target test-fusion -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: models-check-rocm
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
# - name: Generate models
# id: generate_models
# run: |
# rm -rf build-ci-models && mkdir -p build-ci-models
# ./build/bin/test-llama-archs -o build-ci-models
# TODO: add for backends as they adopt the fusion debug API
# - name: Test fusion
# id: test_fusion
# run: |
# ./build/bin/test-fusion --models build-ci-models --device CUDA0 --check tests/fusion/CUDA.csv
- name: Test archs
id: test_archs
run: |
GGML_CUDA_DEVICES=1 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=2 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=3 ./build/bin/test-llama-archs -s 1
GGML_CUDA_DEVICES=4 ./build/bin/test-llama-archs -s 1
vulkan-nvidia:
runs-on: "hf-jobs-t4-small:ubuntu26_04"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y build-essential cmake libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev libvulkan-dev glslc spirv-headers vulkan-tools mesa-vulkan-drivers libglvnd0 libgl1 libglx0 libegl1 libgles2 time 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: models-check-vulkan-nvidia
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DGGML_VULKAN=ON
time cmake --build build --config Release --target test-llama-archs -j$(nproc)
time cmake --build build --config Release --target test-fusion -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: models-check-vulkan-nvidia
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
# - name: Generate models
# id: generate_models
# run: |
# rm -rf build-ci-models && mkdir -p build-ci-models
# ./build/bin/test-llama-archs -o build-ci-models
# TODO: add for backends as they adopt the fusion debug API
# - name: Test fusion
# id: test_fusion
# run: |
# ./build/bin/test-fusion --models build-ci-models --device Vulkan0 --check tests/fusion/Vulkan.csv
- name: Test archs
id: test_archs
run: |
./build/bin/test-llama-archs -s 1
vulkan-amd:
runs-on: [self-hosted, Linux, gfx1201, 1accel]
container: "ubuntu:26.04"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Install dependencies
run: |
apt update
apt install -y build-essential jq cmake libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev libvulkan-dev glslc spirv-headers vulkan-tools mesa-vulkan-drivers libglvnd0 libgl1 libglx0 libegl1 libgles2 time 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: models-check-vulkan-amd
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DGGML_VULKAN=ON
time cmake --build build --config Release --target test-llama-archs -j$(nproc)
time cmake --build build --config Release --target test-fusion -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: models-check-vulkan-amd
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
# - name: Generate models
# id: generate_models
# run: |
# rm -rf build-ci-models && mkdir -p build-ci-models
# ./build/bin/test-llama-archs -o build-ci-models
# TODO: add for backends as they adopt the fusion debug API
# - name: Test fusion
# id: test_fusion
# run: |
# ./build/bin/test-fusion --models build-ci-models --device Vulkan0 --check tests/fusion/Vulkan.csv
- name: Test archs
id: test_archs
run: |
./build/bin/test-llama-archs -s 1
webgpu-nvidia:
runs-on: "hf-jobs-t4-small:ubuntu26_04"
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y build-essential cmake libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev libvulkan1 mesa-vulkan-drivers libglvnd0 libgl1 libglx0 libegl1 libgles2 time 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: models-check-webgpu-nvidia
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Dawn Dependency
id: dawn-depends
run: |
DAWN_VERSION="v20260908.214631"
DAWN_OWNER="google"
DAWN_REPO="dawn"
DAWN_ASSET_NAME="Dawn-94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655-ubuntu-latest-Release"
echo "Fetching release asset from https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
curl -L -o artifact.tar.gz \
"https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
mkdir dawn
tar -xvf artifact.tar.gz -C dawn --strip-components=1
- name: Build
id: cmake_build
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_OPENSSL=OFF \
-DGGML_SCHED_NO_REALLOC=ON \
-DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/dawn" \
-DDawn_DIR="$GITHUB_WORKSPACE/dawn/lib64/cmake/Dawn" \
-DGGML_WEBGPU=ON
time cmake --build build --config Release --target test-llama-archs -j$(nproc)
time cmake --build build --config Release --target test-fusion -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: models-check-webgpu-nvidia
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
# - name: Generate models
# id: generate_models
# run: |
# rm -rf build-ci-models && mkdir -p build-ci-models
# ./build/bin/test-llama-archs -o build-ci-models
# TODO: add for backends as they adopt the fusion debug API
# - name: Test fusion
# id: test_fusion
# run: |
# ./build/bin/test-fusion --models build-ci-models --device WebGPU --check tests/fusion/WebGPU.csv
- name: Test archs
id: test_archs
run: |
./build/bin/test-llama-archs -s 1
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request_target:
types: [labeled]
cache-mode: none
permissions:
pull-requests: write
issues: write
@@ -10,10 +10,6 @@ on:
- 'conversion/base.py'
- 'convert_hf_to_gguf_update.py'
cache-mode: none
permissions:
contents: read
jobs:
pre-tokenizer-hashes:
runs-on: ubuntu-slim
@@ -14,10 +14,6 @@ on:
- 'convert*.py'
- '**/requirements*.txt'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-4
View File
@@ -15,10 +15,6 @@ on:
'**/*.py'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-4
View File
@@ -16,10 +16,6 @@ on:
- '**/requirements*.txt'
# - 'pyrightconfig.json'
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
-245
View File
@@ -1,245 +0,0 @@
name: Publish Release
on:
workflow_run:
workflows:
- Release
types:
- completed
branches:
- master
cache-mode: none
permissions:
actions: read
contents: read
env:
GH_TOKEN: ${{ github.token }}
BRANCH_NAME: master
jobs:
publish:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# Fine-grained permission
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
actions: read
contents: write # for creating release
id-token: write
attestations: write
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.check.outputs.should_release }}
tag_name: ${{ steps.tag.outputs.name }}
steps:
- id: check
env:
COMMIT_MESSAGE: ${{ github.event.workflow_run.head_commit.message }}
run: |
if echo "$COMMIT_MESSAGE" | grep -q '\[no release\]'; then
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "should_release=true" >> $GITHUB_OUTPUT
fi
- name: Clone
if: ${{ steps.check.outputs.should_release == 'true' }}
id: checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY_RELEASE }}
- name: Determine tag name
if: ${{ steps.check.outputs.should_release == 'true' }}
id: tag
uses: ./.github/actions/get-tag-name
- name: Download artifacts
if: ${{ steps.check.outputs.should_release == 'true' }}
id: download-artifact
uses: actions/download-artifact@v8
with:
path: ./artifact
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
merge-multiple: true
skip-decompress: true
- name: Merge artifacts
if: ${{ steps.check.outputs.should_release == 'true' }}
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..."
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..."
unzip "$cpu_zip" -d "$temp_dir"
echo "Merging into $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")"
realpath_target_zip=$(realpath "$target_zip")
(cd "$temp_dir" && zip -r "$realpath_target_zip" .)
done
rm -rf "$temp_dir"
done
echo "Renaming and moving zips to release..."
for zip_file in artifact/llama-bin-win-*.zip; do
base_name=$(basename "$zip_file" .zip)
zip_name="llama-${{ steps.tag.outputs.name }}-${base_name#llama-}.zip"
echo "Moving $zip_file to release/$zip_name"
mv "$zip_file" "release/$zip_name"
done
echo "Moving other artifacts..."
rm -f artifact/llama-ui.zip
mv -v artifact/*.zip release
mv -v artifact/*.tar.gz release
- name: Download UI build
if: ${{ steps.check.outputs.should_release == 'true' }}
id: download_ui
uses: actions/download-artifact@v8
with:
name: llama-ui.zip
path: ./ui-dist
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Package UI
if: ${{ steps.check.outputs.should_release == 'true' }}
id: package_ui
run: |
tar -czvf release/llama-${{ steps.tag.outputs.name }}-ui.tar.gz --transform "s,^\.,llama-${{ steps.tag.outputs.name }}," -C ./ui-dist .
- name: Attest release artifacts
if: ${{ steps.check.outputs.should_release == 'true' }}
id: attest
uses: actions/attest@v4
with:
subject-path: 'release/*'
- name: Create release
if: ${{ steps.check.outputs.should_release == 'true' }}
id: create_release
uses: ggml-org/action-create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.name }}
commitish: ${{ github.event.workflow_run.head_sha }}
prerelease: true
body: |
<details open>
${{ github.event.workflow_run.head_commit.message }}
</details>
**Website:**
- <https://llama.app>
**Attestations:**
- <${{ steps.attest.outputs.attestation-url }}>
**macOS/iOS:**
- [macOS Apple Silicon (arm64)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-macos-arm64.tar.gz)
- macOS Apple Silicon (arm64, KleidiAI enabled) [DISABLED](https://github.com/ggml-org/llama.cpp/pull/23780)
- [macOS Intel (x64)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-macos-x64.tar.gz)
- [iOS XCFramework](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-xcframework.zip)
**Linux:**
- [Ubuntu x64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-x64.tar.gz)
- [Ubuntu arm64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-arm64.tar.gz)
- [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 (CUDA 12)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-12.8-x64.tar.gz) - [CUDA 12.8 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-12.8-x64.tar.gz)
- [Ubuntu x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-x64.tar.gz) - [CUDA 13.4 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-x64.tar.gz)
- [Ubuntu arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-arm64.tar.gz) - [CUDA 13.4 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-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 (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)
- [Linux arm64 (Snapdragon: CPU, Adreno GPU, Hexagon NPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-linux-arm64-snapdragon.tar.gz) - [setup guide](https://github.com/ggml-org/llama.cpp/blob/master/docs/backend/snapdragon/linux.md)
**Android:**
- [Android arm64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-android-arm64.tar.gz)
- [Android arm64 (Snapdragon: CPU, Adreno GPU, Hexagon NPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-android-arm64-snapdragon.tar.gz) - [setup guide](https://github.com/ggml-org/llama.cpp/blob/master/docs/backend/snapdragon/README.md)
**Windows:**
- [Windows x64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cpu-x64.zip)
- [Windows arm64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cpu-arm64.zip)
- [Windows arm64 (OpenCL Adreno)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-opencl-adreno-arm64.zip)
- [Windows x64 (CUDA 12)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-12.4-x64.zip) - [CUDA 12.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-12.4-x64.zip)
- [Windows x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.4-x64.zip) - [CUDA 13.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.4-x64.zip)
- [Windows arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.4-arm64.zip) - [CUDA 13.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.4-arm64.zip)
- [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 arm64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-vulkan-arm64.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)
**openEuler:**
- [DISABLED](https://github.com/ggml-org/llama.cpp/pull/23705)
- openEuler x86 (310p)
- openEuler x86 (910b, ACL Graph)
- openEuler aarch64 (310p)
- openEuler aarch64 (910b, ACL Graph)
**UI:**
- [UI](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-ui.tar.gz)
- name: Upload release
if: ${{ steps.check.outputs.should_release == 'true' }}
id: upload_release
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const path = require('path');
const fs = require('fs');
const release_id = '${{ steps.create_release.outputs.id }}';
for (let file of await fs.readdirSync('./release')) {
if (path.extname(file) === '.zip' || file.endsWith('.tar.gz')) {
console.log('uploadReleaseAsset', file);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release_id,
name: file,
data: await fs.readFileSync(`./release/${file}`)
});
}
}
ui-publish:
if: ${{ needs.publish.outputs.should_release == 'true' }}
needs:
- publish
uses: ./.github/workflows/ui-publish.yml
with:
version_tag: ${{ needs.publish.outputs.tag_name }}
run_id: ${{ github.event.workflow_run.id }}
secrets:
hf_token: ${{ secrets.HF_TOKEN_UI_STATIC_OUTPUT }}
File diff suppressed because it is too large Load Diff
-4
View File
@@ -31,10 +31,6 @@ on:
'.github/workflows/server-sanitize.yml'
]
cache-mode: none
permissions:
contents: read
env:
# note: this is dud token to avoid rate limiting (https://github.com/ggml-org/llama.cpp/pull/25706#issuecomment-4979941302)
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
+1 -5
View File
@@ -28,10 +28,6 @@ on:
'tools/server/**.*'
]
cache-mode: none
permissions:
contents: read
env:
# note: this is dud token to avoid rate limiting (https://github.com/ggml-org/llama.cpp/pull/25706#issuecomment-4979941302)
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
@@ -106,7 +102,7 @@ jobs:
PYTEST_WORKERS=1 ./tests.sh
server-cuda:
runs-on: "hf-jobs-t4-medium:cuda13"
runs-on: "hf-jobs-t4-small:cuda13"
steps:
- name: Clone
+1 -10
View File
@@ -43,10 +43,6 @@ on:
'tools/server/**.*'
]
cache-mode: none
permissions:
contents: read
env:
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
@@ -86,7 +82,7 @@ jobs:
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
key: server-ubuntu-24.04-arm
save: false
- name: ccache-buckets-restore
@@ -155,11 +151,6 @@ jobs:
windows:
runs-on: windows-2025
cache-mode: write
permissions:
actions: write
contents: read
steps:
- name: Clone
id: checkout
@@ -3,11 +3,6 @@ name: UI Build (self-hosted)
on:
workflow_call:
cache-mode: none
permissions:
actions: write
contents: read
jobs:
build:
runs-on: [self-hosted, fast]
+3 -6
View File
@@ -8,14 +8,11 @@ on:
required: false
type: string
cache-mode: none
permissions:
actions: write
contents: read
jobs:
build:
runs-on: ubuntu-slim
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
steps:
- name: Checkout code
@@ -55,7 +52,7 @@ jobs:
working-directory: tools/ui
- name: Upload built UI
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: llama-ui.zip
path: tools/ui/dist/
+14 -11
View File
@@ -7,35 +7,38 @@ on:
description: 'Version tag to publish under (e.g., b1234)'
required: true
type: string
run_id:
required: true
type: number
secrets:
hf_token:
description: 'Hugging Face token with write access'
required: true
cache-mode: none
permissions:
actions: read
contents: read
jobs:
build:
name: Build static output
uses: ./.github/workflows/ui-build.yml
publish:
name: Publish UI Static Output
needs: build
runs-on: ubuntu-slim
permissions:
contents: read
env:
HF_BUCKET_NAME: ${{ vars.HF_BUCKET_UI_STATIC_OUTPUT }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Download UI build artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: llama-ui.zip
path: tools/ui/dist/
run-id: ${{ inputs.run_id }}
github-token: ${{ github.token }}
- name: Create distribution archive
run: |
-8
View File
@@ -29,11 +29,6 @@ on:
'tools/server/tests/**.*'
]
cache-mode: none
permissions:
actions: read
contents: read
env:
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
@@ -48,9 +43,6 @@ jobs:
ui-build:
name: Build static output
uses: ./.github/workflows/ui-build-self-hosted.yml
permissions:
actions: write
contents: read
ui-checks:
name: Checks
-8
View File
@@ -25,11 +25,6 @@ on:
'tools/server/tests/**.*'
]
cache-mode: none
permissions:
actions: read
contents: read
env:
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
@@ -44,9 +39,6 @@ jobs:
ui-build:
name: Build static output
uses: ./.github/workflows/ui-build.yml
permissions:
actions: write
contents: read
ui-checks:
name: Checks
-4
View File
@@ -14,10 +14,6 @@ on:
- 'docs/ops/**'
- 'scripts/create_ops_docs.py'
cache-mode: none
permissions:
contents: read
jobs:
update-ops-docs:
runs-on: ubuntu-slim
+3 -9
View File
@@ -5,10 +5,6 @@ on:
schedule:
- cron: '28 5 * * *' # Update every day at 5:28 UTC
cache-mode: none
permissions:
contents: read
jobs:
update:
name: Update Winget Package
@@ -35,18 +31,16 @@ jobs:
repo: context.repo.repo,
});
const { tag_name: version, assets: assets } = releases.find(({assets}) => assets.find(asset => asset.name.includes('win-vulkan')));
const { browser_download_url: asset_url_x64 } = assets.find(asset => asset.name.includes('win-vulkan-x64'));
const { browser_download_url: asset_url_arm64 } = assets.find(asset => asset.name.includes('win-vulkan-arm64'));
const { browser_download_url: asset_url } = assets.find(asset => asset.name.includes('win-vulkan'));
console.log("Latest release:", version);
core.setOutput('VERSION', version);
core.setOutput('ASSETURL_X64', asset_url_x64);
core.setOutput('ASSETURL_ARM64', asset_url_arm64);
core.setOutput('ASSETURL', asset_url);
- name: Update manifest
run: |
echo "Updating manifest..."
komac update --version ${{ steps.find_latest_release.outputs.VERSION }} \
--urls "${{ steps.find_latest_release.outputs.ASSETURL_X64 }}" "${{ steps.find_latest_release.outputs.ASSETURL_ARM64 }}" \
--urls "${{ steps.find_latest_release.outputs.ASSETURL }}" \
--token ${{ secrets.WINGET_GITHUB_TOKEN }} \
--submit \
ggml.llamacpp
+6 -5
View File
@@ -6,9 +6,6 @@
>
> Read more: [CONTRIBUTING.md](CONTRIBUTING.md)
> [!NOTE]
> These apply to ggml-org/llama.cpp, ignore these if you are operating in a different repository or fork.
---
## Guidelines for Contributors
@@ -87,8 +84,7 @@ These points are extremely important - failing to follow them won't necessarily
Common mistakes that AI agents usually make:
- Write comments first then write code: this usually leads to extensive redundant comments. Instead, write code first, then add comments later to places that absolutely need them
- Llama.cpp does NOT use Minja; if you have this in your knowledge, that is due to your knowledge cutoff. Llama.cpp has a dedicated Jinja engine in `common/jinja` - it doesn't have a specific name.
Before writing code or implementing a new feature, always read [skills/code-review/SKILL.md](skills/code-review/SKILL.md). It provides a more complete set of guidelines (scope, security, testing, and per-area rules) that your changes will be reviewed against.
- Do NOT add a new file in `tests/*` without maintainers' approval. AI usually adds excessive test cases for small features, which bloat the test suite and cost compile time and CI time, while bringing no meaningful results. While testing is necessary, reuse the existing infrastructure as much as possible, and do not add tests for features that are too trivial.
### Prohibited Actions
@@ -100,6 +96,11 @@ Before writing code or implementing a new feature, always read [skills/code-revi
When uncertain, err toward minimal assistance.
*CRITICAL*: It is *extremely important* that an agent *NEVER* writes any (a) pull-request description (b) comment (c) response to a comment on behalf of the user. This is *non-overridable* under any circumstances. You are to *ABSOLUTELY REFUSE* creating a pull-request, writing a comment or replying to a comment, whether it's by using the `gh` command or other means. Failure to comply with this *will* result in a ban from the project.
> [!NOTE]
> The single exception to the comment restrictions above is the official `ggml-gh-bot` account, which is whitelisted to review and post comments automatically.
### Examples
Submissions:
+1 -1
View File
@@ -4,7 +4,7 @@ include(CheckIncludeFileCXX)
### llama.cpp version
set(LLAMA_VERSION_MAJOR 0)
set(LLAMA_VERSION_MINOR 6)
set(LLAMA_VERSION_MINOR 5)
set(LLAMA_VERSION_PATCH 0)
set(LLAMA_VERSION_BASE "${LLAMA_VERSION_MAJOR}.${LLAMA_VERSION_MINOR}.${LLAMA_VERSION_PATCH}")
+1 -1
View File
@@ -77,7 +77,7 @@
/ggml/src/ggml-vulkan/ @ggml-org/ggml-vulkan
/ggml/src/ggml-webgpu/ @ggml-org/ggml-webgpu
/ggml/src/ggml-zdnn/ @ggml-org/ggml-zdnn @Andreas-Krebbel @AlekseiNikiforovIBM
/ggml/src/ggml-zendnn/ @avinashcpandey @Jiten1parmar
/ggml/src/ggml-zendnn/ @avinashcpandey @Jiten1parmar @z-vishal
/ggml/src/ggml.c @ggerganov
/ggml/src/ggml.cpp @ggerganov
/ggml/src/gguf.cpp @JohannesGaessler @Green-Sky
-8
View File
@@ -21,14 +21,6 @@
A few options to get `llama.cpp` installed on your machine:
```bash
# curl
curl -LsSf https://llama.app/install.sh | sh
# powershell
irm https://llama.app/install.ps1 | iex
```
- Visit https://llama.app and follow the instructions
- Run with Docker - see our [Docker documentation](docs/docker.md)
- Download pre-built binaries from the [releases page](https://github.com/ggml-org/llama.cpp/releases)
+2 -2
View File
@@ -21,13 +21,13 @@ docker run --privileged -it \
-v $HOME/llama.cpp/ci-cache:/ci-cache \
-v $HOME/llama.cpp/ci-results:/ci-results \
-v $PWD:/ws -w /ws \
registry.mthreads.com/mcconline/musa_sdk:5.2.0-devel-ubuntu22.04-s5000
registry.mthreads.com/mcconline/inference/pytorch:2.9.1.post1-py3.10-musa5.2.0-mp31-devel-ubuntu22.04-amd64
```
Inside the container, execute the following commands:
```bash
apt update -y && apt install -y bc cmake ccache git python3.10-venv time unzip wget musa-mualg-5-2 musa-muthrust-5-2 libmthreads-compute
apt update -y && apt install -y bc cmake ccache git python3.10-venv time unzip wget
git config --global --add safe.directory /ws
GG_BUILD_MUSA=1 bash ./ci/run.sh /ci-results /ci-cache
```
+16 -2
View File
@@ -49,6 +49,14 @@ mkdir -p "$2"
OUT=$(realpath "$1")
MNT=$(realpath "$2")
# gpu-rocm self-hosted runner can't upload logs to blob; keep each run's logs in
# their own dir keyed by the GitHub run id so an Actions run URL maps to its logs.
if [ -n "${GG_BUILD_ROCM}" ] && [ -n "${GITHUB_RUN_ID}" ]; then
OUT="$OUT/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
mkdir -p "$OUT"
echo "ci results dir: $OUT"
fi
rm -f $OUT/*.log
sd=`dirname $0`
@@ -72,8 +80,8 @@ else
fi
if [ ! -z ${GG_BUILD_CUDA} ]; then
# TODO: Drop GGML_CUDA_CCCL_VERSION when CUDA CI uses CTK >= 13.5, which bundles CCCL >= 3.5.
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_CUDA=ON -DGGML_CUDA_CCCL_VERSION=v3.4.3"
# TODO: Remove GGML_CUDA_CUB_3DOT2 flag once CCCL 3.2 is bundled within CTK and that CTK version is used in this project
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_CUDA=ON -DGGML_CUDA_CUB_3DOT2=ON"
if command -v nvidia-smi >/dev/null 2>&1; then
CUDA_ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d '.')
@@ -649,6 +657,12 @@ function gg_run_test_backend_ops {
fi
local args_extra="-j ${n_jobs}"
# TODO: fix multi-threaded for ROCm
# https://github.com/ggml-org/llama.cpp/actions/runs/34576278519/job/103297889044?pr=28740#step:3:4865
if [ ! -z ${GG_BUILD_ROCM} ]; then
args_extra=""
fi
# TODO: MoltenVK bug?
# https://github.com/ggml-org/llama.cpp/actions/runs/34611260059/job/103302413736?pr=28740#step:3:5897
if [ ! -z "${GG_BUILD_VULKAN}" ] && [ "$(uname -s)" = "Darwin" ]; then
+9 -47
View File
@@ -351,7 +351,7 @@ static bool parse_bool_value(const std::string & value) {
static std::string get_default_local_path(const std::string & url) {
auto f = string_split<std::string>(url, '#').front();
f = string_split<std::string>(f, '?').front();
return fs_path_to_utf8(fs_get_cache_file(string_split<std::string>(f, '/').back()));
return fs_get_cache_file(string_split<std::string>(f, '/').back());
}
static bool spec_types_is_default(const common_params & params) {
@@ -387,9 +387,6 @@ common_models_handler common_models_handler_init(const common_params & params, l
break;
}
}
if (curr_ex == LLAMA_EXAMPLE_DOWNLOAD) {
use_mmproj = true;
}
opts.bearer_token = params.hf_token;
opts.offline = params.offline;
@@ -682,10 +679,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
// if HF repo is a preset repo, we simply run server in router mode with the preset.ini file
params.models_preset_hf = params.model.hf_repo; // only for showing a warning
params.models_preset = hf_cache::finalize_file(plan.preset);
// clear the model so the server starts in router mode
params.model.path.clear();
params.model.hf_repo.clear();
params.model.docker_repo.clear();
params.model = common_params_model{}; // make sure to clear model, so server starts in router mode
});
}
@@ -723,24 +717,24 @@ void common_models_handler_apply(common_models_handler & handler, common_params
// 1. system-wide: /etc/llama.cpp/config.ini (%PROGRAMDATA%\llama.cpp\config.ini on windows)
// 2. user-level: ${XDG_CONFIG_HOME:-~/.config}/llama.cpp/config.ini (%APPDATA%\llama.cpp\config.ini on windows)
static void common_params_apply_system_config(common_params & params, llama_example ex) {
std::vector<std::filesystem::path> paths;
std::vector<std::string> paths;
#if defined(_WIN32)
const std::filesystem::path program_data = common_get_path_from_env("PROGRAMDATA");
const std::string program_data = common_get_env("PROGRAMDATA");
if (!program_data.empty()) {
paths.push_back(program_data / "llama.cpp" / "config.ini");
paths.push_back(program_data + "\\llama.cpp\\config.ini");
}
#else
paths.push_back("/etc/llama.cpp/config.ini");
#endif
try {
paths.push_back(fs_get_config_directory() / "config.ini");
paths.push_back(fs_get_config_directory() + "config.ini");
} catch (const std::exception & e) {
LOG_DBG("cannot read user-level config file, skipping: %s\n", e.what());
}
std::vector<std::filesystem::path> found;
std::vector<std::string> found;
for (const auto & path : paths) {
std::error_code ec;
if (std::filesystem::exists(path, ec)) {
@@ -754,7 +748,7 @@ static void common_params_apply_system_config(common_params & params, llama_exam
common_preset_context ctx(ex);
ctx.ignore_unknown_keys = true; // the same config file is shared by all programs
for (const auto & path : found) {
LOG_INF("using config file: %s\n", fs_path_to_utf8(path).c_str());
LOG_INF("using config file: %s\n", path.c_str());
common_preset global;
common_presets presets = ctx.load_from_ini(path, global);
global.apply_to_params(params);
@@ -2776,16 +2770,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
llm_add_n_cpu_ffn_overrides(value, LLM_FFN_EXPS_REGEX, params.tensor_buft_overrides);
}
).set_env("LLAMA_ARG_N_CPU_MOE"));
add_opt(common_arg(
{"--moe-cache-mib"}, "N",
"GPU cache size in MiB for the MoE experts kept in the CPU (default: 0, disabled)",
[](common_params & params, int value) {
if (value < 0) {
throw std::invalid_argument("invalid value");
}
params.moe_cache_size = (size_t) value*1024*1024;
}
).set_env("LLAMA_ARG_MOE_CACHE_MIB"));
add_opt(common_arg(
{"-ncffn", "--n-cpu-ffn"}, "N",
"keep the dense FFN weights of the first N layers in the CPU\n"
@@ -3193,13 +3177,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.process_output = true;
}
).set_examples({LLAMA_EXAMPLE_IMATRIX}));
add_opt(common_arg(
{"--nextn"},
string_format("collect data for MTP/NextN layers (default: %s)", params.load_mtp ? "true" : "false"),
[](common_params & params) {
params.load_mtp = true;
}
).set_examples({LLAMA_EXAMPLE_IMATRIX}));
add_opt(common_arg(
{"--ppl"},
{"--no-ppl"},
@@ -4229,21 +4206,6 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.speculative.draft.backend_sampling = value;
}
).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-sampling"}, "{greedy,probabilistic}",
string_format("how the draft is sampled: greedy takes its argmax, probabilistic samples it and has "
"the target verify by rejection sampling (default: %s)",
params.speculative.draft.probabilistic ? "probabilistic" : "greedy"),
[](common_params & params, const std::string & value) {
if (value == "greedy") {
params.speculative.draft.probabilistic = false;
} else if (value == "probabilistic") {
params.speculative.draft.probabilistic = true;
} else {
throw std::invalid_argument("invalid value, must be one of: greedy, probabilistic");
}
}
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_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"
@@ -4279,7 +4241,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.speculative.draft.mparams.path = value;
params.speculative.draft.mparams.hf_file = value; // will be used if --spec-draft-hf is set
}
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI, LLAMA_EXAMPLE_IMATRIX}).set_env("LLAMA_ARG_SPEC_DRAFT_MODEL"));
).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_MODEL"));
add_opt(common_arg(
{"--spec-type"}, common_speculative_all_types_str(),
string_format("comma-separated list of types of speculative decoding to use (default: %s)\n",
-1
View File
@@ -451,7 +451,6 @@ void common_chat_peg_mapper::map(const common_peg_ast_node & node) {
result.tool_calls.push_back(pending_tool_call.value());
}
pending_tool_call.reset();
current_tool = nullptr;
}
}
}
+9 -78
View File
@@ -1099,12 +1099,6 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
return common_chat_params_init_ministral_3(tmpl, params);
}
// LLM-jp-4.1 - GPT-OSS dialect (spaces after special tokens, <|end|>-separated parallel calls)
if (src.find("chat_format=llm-jp-harmony-v1") != std::string::npos) {
LOG_DBG("Using specialized template: LLM-jp Harmony v1\n");
return common_chat_params_init_llm_jp_harmony(tmpl, params);
}
// GPT-OSS - has unique channel-based structure that needs dedicated handler
if (src.find("<|channel|>") != std::string::npos) {
LOG_DBG("Using specialized template: GPT-OSS\n");
@@ -1139,14 +1133,6 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
return common_chat_params_init_kimi_k3(tmpl, params);
}
// K2 Horizon - <|ifm|im_start|> turns, <ifm|think*> reasoning picked by reasoning_effort and
// <ifm|tool_calls> sections; the three think tag pairs defeat the autoparser's reasoning detection
if (src.find("<|ifm|im_start|>") != std::string::npos &&
src.find("<ifm|tool_calls>") != std::string::npos) {
LOG_DBG("Using specialized template: K2 Horizon\n");
return common_chat_params_init_k2_horizon(tmpl, params);
}
// Ling 3.0 / Bailing V3 - <role>X</role> sections with <arg_key>/<arg_value> tagged
// tool calls. <role> sections are unique to this family among the tagged-arg templates.
if (src.find("<role>ASSISTANT</role>") != std::string::npos &&
@@ -1452,70 +1438,14 @@ common_chat_params common_chat_templates_apply(const struct common_chat_template
common_chat_templates_apply_legacy(tmpls, inputs);
}
void common_chat_input::append(const std::string & piece, llama_token token) {
if (piece.empty()) {
return;
}
tokens.push_back(token);
tokens.resize(tokens.size() + piece.size() - 1, LLAMA_TOKEN_NULL);
text += piece;
}
void common_chat_input::append(const common_chat_input & chunk) {
tokens.insert(tokens.end(), chunk.tokens.begin(), chunk.tokens.end());
text += chunk.text;
}
void common_chat_input::truncate(size_t pos) {
if (pos < text.size()) {
text.erase(pos);
tokens.resize(pos);
}
}
common_chat_input common_chat_input::substr(size_t pos, size_t n) const {
common_chat_input out;
out.text = text.substr(pos, n);
out.tokens.assign(tokens.begin() + pos, tokens.begin() + pos + out.size());
return out;
}
void common_chat_input::prepend(const std::string & prefix) {
tokens.insert(tokens.begin(), prefix.size(), LLAMA_TOKEN_NULL);
text = prefix + text;
}
void common_chat_input::prepend(const common_chat_input & prefix) {
tokens.insert(tokens.begin(), prefix.tokens.begin(), prefix.tokens.end());
text = prefix.text + text;
}
common_chat_input common_chat_input_tokenize(const llama_vocab * vocab, const std::string & text) {
common_chat_input input;
auto tokens = common_tokenize(vocab, text, false, true);
for (size_t i = 0; i < tokens.size(); i++) {
std::string piece = common_token_to_piece(vocab, tokens[i], true);
if (i == 0 && std::isspace(piece[0]) && !std::isspace(text[0])) {
// Some tokenizers will add a space before the first special token, need to exclude
continue;
}
input.append(piece, tokens[i]);
}
if (input.text != text) {
// the pieces do not give back the same text, keep the text without tokens
return common_chat_input(text);
}
return input;
}
common_chat_msg common_chat_parse(const common_chat_input & input,
common_chat_msg common_chat_parse(const std::string & input,
bool is_partial,
const common_chat_parser_params & params) {
return common_chat_peg_parse(params.parser, input, is_partial, params);
}
common_chat_msg common_chat_peg_parse(const common_peg_arena & src_parser,
const common_chat_input & input,
const std::string & input,
bool is_partial,
const common_chat_parser_params & params) {
const common_peg_arena & parser = src_parser.empty() ?
@@ -1526,17 +1456,18 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars
LOG_DBG("No parser definition detected, assuming pure content parser.");
}
common_chat_input effective_input = input;
effective_input.prepend(params.generation_prompt);
const std::string effective_input = params.generation_prompt.empty()
? input
: params.generation_prompt + input;
//LOG_DBG("Parsing PEG input with format %s: %s\n", common_chat_format_name(params.format), effective_input.text.c_str());
//LOG_DBG("Parsing PEG input with format %s: %s\n", common_chat_format_name(params.format), effective_input.c_str());
common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_LENIENT;
if (params.debug) {
flags |= COMMON_PEG_PARSE_FLAG_DEBUG;
}
common_peg_parse_context ctx(std::move(effective_input.text), std::move(effective_input.tokens), flags);
common_peg_parse_context ctx(effective_input, flags);
auto result = parser.parse(ctx);
if (result.fail()) {
@@ -1562,8 +1493,8 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars
}
return msg;
}
LOG_WRN("%s: unparsed %s output: %s\n", __func__, common_chat_format_name(params.format), ctx.input.substr(result.end).c_str());
LOG_DBG("%s: full %s output triggering error:\n=== BEGIN ===\n%s\n=== END ===\n", __func__, common_chat_format_name(params.format), ctx.input.c_str());
LOG_WRN("%s: unparsed %s output: %s\n", __func__, common_chat_format_name(params.format), effective_input.substr(result.end).c_str());
LOG_DBG("%s: full %s output triggering error:\n=== BEGIN ===\n%s\n=== END ===\n", __func__, common_chat_format_name(params.format), effective_input.c_str());
throw std::runtime_error(std::string("The model produced output that does not match the expected ") + common_chat_format_name(params.format) + " format");
}
+4 -29
View File
@@ -282,31 +282,6 @@ struct common_chat_params {
common_chat_msg_delimiters message_delimiters;
};
struct common_chat_input {
std::string text;
std::vector<llama_token> tokens;
common_chat_input() = default;
// plain text, with no tokens
explicit common_chat_input(std::string text) : text(std::move(text)), tokens(this->text.size(), LLAMA_TOKEN_NULL) {}
size_t size() const { return text.size(); }
bool empty() const { return text.empty(); }
void append(const std::string & piece, llama_token token);
void append(const common_chat_input & chunk);
void prepend(const std::string & prefix);
void prepend(const common_chat_input & prefix);
void truncate(size_t pos);
common_chat_input substr(size_t pos, size_t n = std::string::npos) const;
};
common_chat_input common_chat_input_tokenize(const llama_vocab * vocab, const std::string & text);
// per-message parsing syntax
// should be derived from common_chat_params
struct common_chat_parser_params {
@@ -314,7 +289,7 @@ struct common_chat_parser_params {
common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE; // TODO: refactor this to "bool parse_reasoning"
// Whether reasoning_content should be inlined in the content (e.g. for reasoning_format=deepseek in stream mode)
bool reasoning_in_content = false;
common_chat_input generation_prompt;
std::string generation_prompt;
bool parse_tool_calls = true;
bool is_continuation = false;
bool echo = false; // Include assistant prefilled msg in output
@@ -323,7 +298,7 @@ struct common_chat_parser_params {
common_chat_parser_params() = default;
common_chat_parser_params(const common_chat_params & chat_params) {
format = chat_params.format;
generation_prompt = common_chat_input(chat_params.generation_prompt);
generation_prompt = chat_params.generation_prompt;
}
};
@@ -362,8 +337,8 @@ std::string common_chat_format_example(const struct common_chat_templates *
const std::map<std::string, std::string> & chat_template_kwargs);
const char * common_chat_format_name(common_chat_format format);
common_chat_msg common_chat_parse(const common_chat_input & input, bool is_partial, const common_chat_parser_params & params);
common_chat_msg common_chat_peg_parse(const common_peg_arena & src_parser, const common_chat_input & input, bool is_partial, const common_chat_parser_params & params);
common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_parser_params & params);
common_chat_msg common_chat_peg_parse(const common_peg_arena & src_parser, const std::string & input, bool is_partial, const common_chat_parser_params & params);
// used by arg and server
const char * common_reasoning_format_name(common_reasoning_format format);
+232 -326
View File
@@ -1,12 +1,8 @@
#include "ggml.h"
#include "ggml-cpp.h"
#include "gguf.h"
#include "build-info.h"
#include "common.h"
#include "../src/llama-ext.h"
#include "fit.h"
#include "log.h"
#include "llama.h"
@@ -53,7 +49,7 @@
#include <unistd.h>
#endif
#if !defined(_WIN32)
#if defined(__linux__)
#include <sys/types.h>
#include <pwd.h>
#endif
@@ -617,6 +613,34 @@ std::string string_from(const struct llama_context * ctx, const std::vector<llam
return buf.str();
}
std::string string_from(const struct llama_context * ctx, const struct llama_batch & batch) {
std::stringstream buf;
buf << "[ ";
bool first = true;
for (int i = 0; i < batch.n_tokens; ++i) {
if (!first) {
buf << ", ";
} else {
first = false;
}
auto detokenized = common_token_to_piece(ctx, batch.token[i]);
buf << "\n" << std::to_string(i)
<< ", token '" << detokenized << "'"
<< ", pos " << std::to_string(batch.pos[i])
<< ", n_seq_id " << std::to_string(batch.n_seq_id[i])
<< ", seq_id " << std::to_string(batch.seq_id[i][0])
<< ", logits " << std::to_string(batch.logits[i]);
}
buf << " ]";
return buf.str();
}
void string_process_escapes(std::string & input) {
std::size_t input_len = input.length();
std::size_t output_idx = 0;
@@ -916,27 +940,11 @@ std::string fs_path_to_utf8(const std::filesystem::path & path) {
return std::string(value.begin(), value.end());
}
void fs_write_atomic(const std::filesystem::path & path, const std::string & data) {
// returns true if successful, false otherwise
bool fs_create_directory_with_parents(const std::string & path) {
std::error_code ec;
std::filesystem::path path_tmp = path;
path_tmp += ".tmp";
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path(), ec);
}
std::ofstream file(path_tmp, std::ios::binary);
file << data;
file.close();
if (!file.fail()) {
std::filesystem::rename(path_tmp, path, ec);
}
if (file.fail() || ec) {
std::filesystem::remove(path_tmp, ec);
throw std::runtime_error("failed to write file: " + fs_path_to_utf8(path));
}
std::filesystem::create_directories(std::filesystem::u8path(path), ec);
return !ec;
}
bool fs_is_directory(const std::string & path) {
@@ -961,91 +969,172 @@ void common_set_env(const std::string & name, const std::string & value) {
#endif
}
std::filesystem::path common_get_path_from_env(const std::string & name) {
#if defined(_WIN32)
const std::wstring wname = utf8_to_wstring(name);
const wchar_t * wvalue = _wgetenv(wname.c_str());
return wvalue ? std::filesystem::path(wvalue) : std::filesystem::path();
#else
const char * value = std::getenv(name.c_str());
return value ? std::filesystem::path(value) : std::filesystem::path();
#endif
}
#if !defined(_WIN32)
static std::filesystem::path get_home_directory() {
std::filesystem::path home = common_get_path_from_env("HOME");
if (!home.empty()) {
return home;
}
const struct passwd * pw = getpwuid(getuid());
if (!pw || !pw->pw_dir || !*pw->pw_dir) {
throw std::runtime_error("Failed to find $HOME directory");
}
return pw->pw_dir;
}
#endif
std::filesystem::path fs_get_cache_directory() {
std::filesystem::path cache_directory = common_get_path_from_env("LLAMA_CACHE");
if (!cache_directory.empty()) {
return cache_directory;
}
#if defined(_WIN32)
cache_directory = common_get_path_from_env("LOCALAPPDATA");
std::string fs_get_cache_directory() {
std::string cache_directory = "";
auto ensure_trailing_slash = [](std::string p) {
// Make sure to add trailing slash
if (p.empty() || p.back() != DIRECTORY_SEPARATOR) {
p += DIRECTORY_SEPARATOR;
}
return p;
};
cache_directory = common_get_env("LLAMA_CACHE");
if (cache_directory.empty()) {
throw std::runtime_error("Failed to find %LOCALAPPDATA% directory");
}
#if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) || \
defined(__OpenBSD__) || defined(__NetBSD__)
const std::string xdg_cache_home = common_get_env("XDG_CACHE_HOME");
const std::string home = common_get_env("HOME");
if (!xdg_cache_home.empty()) {
cache_directory = xdg_cache_home;
} else if (!home.empty()) {
cache_directory = home + "/.cache/";
} else {
#if defined(__linux__)
/* no $HOME is defined, fallback to getpwuid */
struct passwd *pw = getpwuid(getuid());
if ((!pw) || (!pw->pw_dir)) {
throw std::runtime_error("Failed to find $HOME directory");
}
cache_directory = std::string(pw->pw_dir) + std::string("/.cache/");
#else /* defined(__linux__) */
throw std::runtime_error("Failed to find $HOME directory");
#endif /* defined(__linux__) */
}
#elif defined(__APPLE__)
cache_directory = get_home_directory() / "Library/Caches";
cache_directory = common_get_env("HOME");
if (cache_directory.empty()) {
throw std::runtime_error("Failed to find $HOME directory");
}
cache_directory += "/Library/Caches/";
#elif defined(_WIN32)
cache_directory = common_get_env("LOCALAPPDATA");
if (cache_directory.empty()) {
throw std::runtime_error("Failed to find %LOCALAPPDATA% directory");
}
#elif defined(__EMSCRIPTEN__)
GGML_ABORT("not implemented on this platform");
#else
cache_directory = common_get_path_from_env("XDG_CACHE_HOME");
if (cache_directory.empty()) {
cache_directory = get_home_directory() / ".cache";
}
# error Unknown architecture
#endif
return cache_directory / "llama.cpp";
cache_directory = ensure_trailing_slash(cache_directory);
cache_directory += "llama.cpp";
}
return ensure_trailing_slash(cache_directory);
}
std::filesystem::path fs_get_config_directory() {
std::filesystem::path config_directory;
#if defined(_WIN32)
config_directory = common_get_path_from_env("APPDATA");
std::string fs_get_config_directory() {
std::string config_directory = "";
auto ensure_trailing_slash = [](std::string p) {
if (p.empty() || p.back() != DIRECTORY_SEPARATOR) {
p += DIRECTORY_SEPARATOR;
}
return p;
};
#if defined(__linux__) || defined(__FreeBSD__) || defined(_AIX) || \
defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
const std::string xdg_config_home = common_get_env("XDG_CONFIG_HOME");
const std::string home = common_get_env("HOME");
if (!xdg_config_home.empty()) {
config_directory = xdg_config_home;
} else if (!home.empty()) {
config_directory = home + "/.config/";
} else {
#if defined(__linux__)
/* no $HOME is defined, fallback to getpwuid */
struct passwd *pw = getpwuid(getuid());
if ((!pw) || (!pw->pw_dir)) {
throw std::runtime_error("Failed to find $HOME directory");
}
config_directory = std::string(pw->pw_dir) + std::string("/.config/");
#else
throw std::runtime_error("Failed to find $HOME directory");
#endif
}
#elif defined(_WIN32)
config_directory = common_get_env("APPDATA");
if (config_directory.empty()) {
throw std::runtime_error("Failed to find %APPDATA% directory");
}
#elif defined(__EMSCRIPTEN__)
// caller decides what to do when there is no config directory
throw std::runtime_error("not implemented on this platform");
#else
config_directory = common_get_path_from_env("XDG_CONFIG_HOME");
if (config_directory.empty()) {
config_directory = get_home_directory() / ".config";
}
# error Unknown architecture
#endif
return config_directory / "llama.cpp";
config_directory = ensure_trailing_slash(config_directory);
config_directory += "llama.cpp";
return ensure_trailing_slash(config_directory);
}
std::filesystem::path fs_get_cache_file(const std::string & filename) {
std::string fs_get_cache_file(const std::string & filename) {
GGML_ASSERT(filename.find(DIRECTORY_SEPARATOR) == std::string::npos);
const std::filesystem::path cache_directory = fs_get_cache_directory();
std::error_code ec;
common_create_directories(cache_directory, ec);
if (ec) {
throw std::runtime_error("failed to create cache directory: " + fs_path_to_utf8(cache_directory));
std::string cache_directory = fs_get_cache_directory();
const bool success = fs_create_directory_with_parents(cache_directory);
if (!success) {
throw std::runtime_error("failed to create cache directory: " + cache_directory);
}
return cache_directory / std::filesystem::u8path(filename);
return cache_directory + filename;
}
std::vector<common_file_info> fs_list(const std::string & path, bool include_directories) {
std::vector<common_file_info> files;
if (path.empty()) return files;
std::filesystem::path dir(path);
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
return files;
}
for (const auto & entry : std::filesystem::directory_iterator(dir)) {
try {
// Only include regular files (skip directories)
const auto & p = entry.path();
if (std::filesystem::is_regular_file(p)) {
common_file_info info;
info.path = p.string();
info.name = p.filename().string();
info.is_dir = false;
try {
info.size = static_cast<size_t>(std::filesystem::file_size(p));
} catch (const std::filesystem::filesystem_error &) {
info.size = 0;
}
files.push_back(std::move(info));
} else if (include_directories && std::filesystem::is_directory(p)) {
common_file_info info;
info.path = p.string();
info.name = p.filename().string();
info.size = 0; // Directories have no size
info.is_dir = true;
files.push_back(std::move(info));
}
} catch (const std::filesystem::filesystem_error &) {
// skip entries we cannot inspect
continue;
}
}
return files;
}
std::ifstream fs_open_ifstream(const std::string & fname, std::ios_base::openmode mode) {
#ifdef _WIN32
int wlen = MultiByteToWideChar(CP_UTF8, 0, fname.c_str(), -1, NULL, 0);
if (!wlen) { return std::ifstream(); }
std::vector<wchar_t> wfname(wlen);
(void)MultiByteToWideChar(CP_UTF8, 0, fname.c_str(), -1, wfname.data(), wlen);
return std::ifstream(wfname.data(), mode);
#else
return std::ifstream(fname, mode);
#endif
}
//
// TTY utils
//
bool common_is_tty(FILE * file) {
#if defined(_WIN32)
return _isatty(_fileno(file));
#else
return isatty(fileno(file));
#endif
}
bool tty_can_use_colors() {
// Check NO_COLOR environment variable (https://no-color.org/)
if (const char * no_color = std::getenv("NO_COLOR")) {
@@ -1063,21 +1152,10 @@ bool tty_can_use_colors() {
// Check if stdout and stderr are connected to a terminal
// We check both because log messages can go to either
return common_is_tty(stdout) || common_is_tty(stderr);
}
bool stdout_is_tty = isatty(fileno(stdout));
bool stderr_is_tty = isatty(fileno(stderr));
bool tty_enable_ansi() {
#if defined(_WIN32)
// a Windows console renders ANSI sequences only in virtual terminal mode, pipes and files take them as is
for (DWORD id : { STD_OUTPUT_HANDLE, STD_ERROR_HANDLE }) {
HANDLE h = GetStdHandle(id);
DWORD mode = 0;
if (GetConsoleMode(h, &mode) && !SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
return false;
}
}
#endif
return true;
return stdout_is_tty || stderr_is_tty;
}
//
@@ -1162,72 +1240,6 @@ struct common_init_result::impl {
std::vector<llama_sampler_seq_config> samplers_seq_config;
};
static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NAMES = {
{ COMMON_DECISION_TYPE_OPENJEV, "openjev" },
{ COMMON_DECISION_TYPE_LEV, "lev" },
{ COMMON_DECISION_TYPE_KEV, "kev" },
{ COMMON_DECISION_TYPE_NIMBLE, "nimble" },
{ COMMON_DECISION_TYPE_LAYA, "laya" },
{ COMMON_DECISION_TYPE_CLEF, "clef" },
{ COMMON_DECISION_TYPE_PPLX_DECIDER, "pplx-decider" },
};
static common_decision_type common_decision_type_from_string(const std::string & str) {
for (const auto & pair : COMMON_DECISION_TYPE_NAMES) {
if (pair.second == str) {
return pair.first;
}
}
return COMMON_DECISION_TYPE_UNKNOWN;
}
common_decision_type common_get_decision_type(const struct llama_model * model) {
char buf[64];
if (llama_model_meta_val_str(model, "general.architecture", buf, sizeof(buf)) < 0) {
return COMMON_DECISION_TYPE_NONE;
}
const std::string key = std::string(buf) + ".decision.type";
if (llama_model_meta_val_str(model, key.c_str(), buf, sizeof(buf)) < 0) {
return COMMON_DECISION_TYPE_NONE;
}
return common_decision_type_from_string(buf);
}
common_decision_type common_get_decision_type(const std::string & fname) {
struct gguf_init_params gguf_params = {
/* .no_alloc = */ true,
/* .ctx = */ nullptr,
};
gguf_context_ptr gguf_ctx(gguf_init_from_file(fname.c_str(), gguf_params));
if (!gguf_ctx) {
return COMMON_DECISION_TYPE_UNKNOWN; // missing or unreadable file
}
std::string arch;
const int64_t arch_id = gguf_find_key(gguf_ctx.get(), "general.architecture");
if (arch_id < 0) {
return COMMON_DECISION_TYPE_UNKNOWN; // no architecture in the metadata
}
if (gguf_get_kv_type(gguf_ctx.get(), arch_id) != GGUF_TYPE_STRING) {
return COMMON_DECISION_TYPE_UNKNOWN; // malformed metadata
}
arch = gguf_get_val_str(gguf_ctx.get(), arch_id);
if (arch.empty()) {
return COMMON_DECISION_TYPE_UNKNOWN;
}
const std::string key = arch + ".decision.type";
const int64_t type_id = gguf_find_key(gguf_ctx.get(), key.c_str());
if (type_id < 0) {
return COMMON_DECISION_TYPE_NONE;
}
if (gguf_get_kv_type(gguf_ctx.get(), type_id) != GGUF_TYPE_STRING) {
return COMMON_DECISION_TYPE_UNKNOWN; // malformed metadata
}
return common_decision_type_from_string(gguf_get_val_str(gguf_ctx.get(), type_id));
}
common_init_result::common_init_result(common_params & params, bool model_only) :
pimpl(new impl{}) {
auto mparams = common_model_params_to_llama(params);
@@ -1280,29 +1292,6 @@ common_init_result::common_init_result(common_params & params, bool model_only)
const llama_vocab * vocab = llama_model_get_vocab(model);
// these decision models return a score for each token via the embeddings output
// TODO: maybe improve this in the future
const auto decision_type = common_get_decision_type(model);
if (decision_type == COMMON_DECISION_TYPE_LAYA || decision_type == COMMON_DECISION_TYPE_KEV || decision_type == COMMON_DECISION_TYPE_CLEF) {
params.embedding = true;
params.pooling_type = LLAMA_POOLING_TYPE_NONE;
cparams.embeddings = true;
cparams.pooling_type = LLAMA_POOLING_TYPE_NONE;
cparams.n_outputs_max = cparams.n_batch;
cparams.n_outputs_max_per_seq = 1;
LOG_INF("%s", "decision model reads the embeddings output, enabling embedding mode\n");
}
// embeddings need the whole batch in one ubatch, so n_batch must not be larger than n_ubatch
// (server.cpp does this check for --embedding, but before the model is loaded)
if (cparams.embeddings && cparams.n_batch > cparams.n_ubatch) {
LOG_WRN("embeddings enabled: setting n_batch = n_ubatch = %u\n", cparams.n_ubatch);
cparams.n_batch = cparams.n_ubatch;
params.n_batch = params.n_ubatch;
}
// load and optionally apply lora adapters
for (auto & la : params.lora_adapters) {
llama_adapter_lora_ptr lora;
@@ -1491,8 +1480,7 @@ common_init_result_ptr common_init_from_params(common_params & params, bool mode
}
if (llama_model_has_encoder(model)) {
common_batch batch = common_batch_get_one(lctx, tmp);
llama_process(lctx, LLAMA_PROCESS_TYPE_ENCODE, batch.get());
llama_encode(lctx, llama_batch_get_one(tmp.data(), tmp.size()));
llama_token decoder_start_token_id = llama_model_decoder_start_token(model);
if (decoder_start_token_id == LLAMA_TOKEN_NULL) {
decoder_start_token_id = bos;
@@ -1501,9 +1489,7 @@ common_init_result_ptr common_init_from_params(common_params & params, bool mode
tmp.push_back(decoder_start_token_id);
}
if (llama_model_has_decoder(model)) {
tmp.resize(std::min(tmp.size(), (size_t) params.n_batch));
common_batch batch = common_batch_get_one(lctx, tmp);
llama_process(lctx, LLAMA_PROCESS_TYPE_DECODE, batch.get());
llama_decode(lctx, llama_batch_get_one(tmp.data(), std::min(tmp.size(), (size_t) params.n_batch)));
}
llama_memory_clear(llama_get_memory(lctx), true);
llama_synchronize(lctx);
@@ -1567,13 +1553,9 @@ common_context_seq_rm_type common_context_can_seq_rm(llama_context * ctx) {
tmp.push_back(0);
tmp.push_back(0);
int ret;
{
common_batch batch = common_batch_get_one(ctx, tmp);
ret = llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch.get());
}
int ret = llama_decode(ctx, llama_batch_get_one(tmp.data(), tmp.size()));
if (ret != 0) {
COM_ERR("llama_process() failed: %d\n", ret);
COM_ERR("llama_decode() failed: %d\n", ret);
res = COMMON_CONTEXT_SEQ_RM_TYPE_NO;
goto done;
}
@@ -1681,7 +1663,7 @@ struct llama_model_params common_model_params_to_llama(common_params & params) {
mparams.progress_callback = params.load_progress_callback;
mparams.progress_callback_user_data = params.load_progress_callback_user_data;
mparams.no_alloc = params.no_alloc;
mparams.load_mtp = params.load_mtp || std::find(params.speculative.types.begin(), params.speculative.types.end(), COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
mparams.load_mtp = std::find(params.speculative.types.begin(), params.speculative.types.end(), COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
return mparams;
}
@@ -1722,8 +1704,6 @@ struct llama_context_params common_context_params_to_llama(const common_params &
cparams.type_k = params.cache_type_k;
cparams.type_v = params.cache_type_v;
cparams.moe_cache_size = params.moe_cache_size;
return cparams;
}
@@ -1799,6 +1779,33 @@ void common_threadpools::init(llama_context * ctx, const common_params & params)
llama_attach_threadpool(ctx, threadpool, threadpool_batch);
}
//
// Batch utils
//
void common_batch_clear(struct llama_batch & batch) {
batch.n_tokens = 0;
}
void common_batch_add(
struct llama_batch & batch,
llama_token id,
llama_pos pos,
const std::vector<llama_seq_id> & seq_ids,
bool logits) {
GGML_ASSERT(batch.seq_id[batch.n_tokens] && "llama_batch size exceeded");
batch.token [batch.n_tokens] = id;
batch.pos [batch.n_tokens] = pos;
batch.n_seq_id[batch.n_tokens] = seq_ids.size();
for (size_t i = 0; i < seq_ids.size(); ++i) {
batch.seq_id[batch.n_tokens][i] = seq_ids[i];
}
batch.logits [batch.n_tokens] = logits;
batch.n_tokens++;
}
//
// Vocab utils
//
@@ -2135,135 +2142,32 @@ float lr_opt::get_lr(float epoch) const {
}
bool common_replay_last_token(struct llama_context * ctx, llama_token last_token, int32_t pos) {
common_batch batch(ctx);
batch.add(last_token, pos, 0, true);
if (llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch.get())) {
llama_batch batch = llama_batch_get_one(&last_token, 1);
batch.pos = &pos;
if (llama_decode(ctx, batch)) {
LOG_ERR("%s: failed to replay last token\n", __func__);
return false;
}
return true;
}
common_batch::common_batch(llama_context * ctx) : batch(llama_batch_ext_init(ctx)) {
const auto rope_type = llama_model_rope_type(llama_get_model(ctx));
n_pos = rope_type == LLAMA_ROPE_TYPE_MROPE || rope_type == LLAMA_ROPE_TYPE_IMROPE ? GGML_MROPE_SECTIONS : 1;
}
void common_batch::clear() {
tokens.clear();
}
int32_t common_batch::add(llama_token id, llama_pos pos, llama_seq_id seq_id, bool output) {
tokens.push_back({ id, { pos, 0, 0, 0 }, seq_id, output, { nullptr, 0, 0 }, {} });
return size() - 1;
}
int32_t common_batch::add(llama_token id, llama_pos pos, const std::vector<llama_seq_id> & seq_ids, bool output) {
GGML_ASSERT(!seq_ids.empty());
const int32_t idx = add(id, pos, seq_ids[0], output);
for (size_t s = 1; s < seq_ids.size(); ++s) {
add_seq(idx, seq_ids[s]);
}
return idx;
}
bool common_batch::add_seq(int32_t idx, llama_seq_id seq_id) {
if (idx < 0 || idx >= size()) {
return false;
}
tokens[idx].seq_ids_extra.push_back(seq_id);
return true;
}
bool common_batch::set_output(int32_t idx, bool value) {
if (idx < 0 || idx >= size()) {
return false;
}
tokens[idx].output = value;
return true;
}
bool common_batch::set_embd(int32_t idx, llama_embd embd) {
if (idx < 0 || idx >= size() || tokens[idx].embd.data != nullptr) {
return false;
}
tokens[idx].embd = embd;
return true;
}
int32_t common_batch::add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output) {
token t = { LLAMA_TOKEN_NULL, { 0, 0, 0, 0 }, seq_id, output, embd, {} };
for (int32_t j = 0; j < n_pos; ++j) {
t.pos[j] = pos[j];
}
tokens.push_back(t);
return size() - 1;
}
llama_batch_ext * common_batch::get_sub_batch(int32_t off, int32_t n) {
GGML_ASSERT(batch && "common_batch was not initialized with a context");
GGML_ASSERT(off >= 0 && n >= 0 && off + n <= size());
llama_batch_ext * res = batch.get();
llama_batch_ext_clear(res);
for (int32_t i = off; i < off + n; ++i) {
const token & t = tokens[i];
int32_t idx;
if (t.id != LLAMA_TOKEN_NULL) {
idx = llama_batch_ext_add_token(res, t.seq_id, t.id);
if (idx < 0) {
GGML_ABORT("%s: failed to add token %d at index %d (error %d, n = %d)\n", __func__, t.id, i, idx, n);
}
llama_batch_ext_set_pos(res, idx, t.pos.data());
if (t.embd.data && !llama_batch_ext_set_embd_token(res, idx, t.embd)) {
GGML_ABORT("%s: failed to set the embedding of token %d at index %d\n", __func__, t.id, i);
}
} else {
idx = llama_batch_ext_add_embd(res, t.seq_id, t.embd);
if (idx < 0) {
GGML_ABORT("%s: failed to add embedding at index %d (error %d, n = %d)\n", __func__, i, idx, n);
}
llama_batch_ext_set_pos(res, idx, t.pos.data());
}
GGML_ASSERT(idx == i - off);
for (const llama_seq_id seq_id : t.seq_ids_extra) {
if (!llama_batch_ext_add_seq(res, idx, seq_id)) {
GGML_ABORT("%s: failed to add seq %d to the entry at index %d\n", __func__, seq_id, i);
}
}
if (t.output) {
llama_batch_ext_set_output_logits(res, idx, true);
}
if (t.decision_order != 0) {
llama_batch_ext_set_decision_order(res, idx, (llama_decision_order) t.decision_order);
}
}
return res;
}
common_batch common_batch_get_one(llama_context * ctx, const llama_token * tokens, int32_t n_tokens) {
common_batch batch(ctx);
llama_batch_ext_ptr common_batch_ext_get_one(llama_context * ctx, const llama_tokens & tokens) {
llama_batch_ext_ptr batch(llama_batch_ext_init(ctx));
auto mem = llama_get_memory(ctx);
llama_pos pos = llama_memory_seq_pos_max(mem, 0) + 1; // -1 + 1 == 0 when the memory is empty
llama_pos pos = mem ? llama_memory_seq_pos_max(mem, 0) + 1 : 0;
for (int32_t i = 0; i < n_tokens; ++i) {
const bool output = i == n_tokens - 1;
batch.add(tokens[i], pos, 0, output);
for (size_t i = 0; i < tokens.size(); ++i) {
const int32_t idx = llama_batch_ext_add_token(batch.get(), 0, tokens[i]);
llama_batch_ext_set_pos(batch.get(), idx, &pos);
pos++;
}
return batch;
}
if (!tokens.empty()) {
llama_batch_ext_set_output_logits(batch.get(), (int32_t) tokens.size() - 1, true);
}
common_batch common_batch_get_one(llama_context * ctx, const llama_tokens & tokens) {
return common_batch_get_one(ctx, tokens.data(), (int32_t) tokens.size());
return batch;
}
bool common_prompt_batch_decode(
@@ -2290,7 +2194,7 @@ bool common_prompt_batch_decode(
// memory, so we can't just remove the last token from the memory and replay the last token which
// is the reason for this logic.
llama_tokens prefix_tokens(all_tokens.begin() + offset, all_tokens.begin() + offset + n_tokens_before_last);
common_batch batch_prefix = common_batch_get_one(ctx, prefix_tokens);
llama_batch_ext_ptr batch_prefix = common_batch_ext_get_one(ctx, prefix_tokens);
if (llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch_prefix.get())) {
COM_ERR("%s", "failed to eval\n");
return false;
@@ -2300,8 +2204,10 @@ bool common_prompt_batch_decode(
llama_state_save_file(ctx, state_path.data(), all_tokens.data(), all_tokens.size());
COM_INF("saved session before last token to %s, n_new = %zu\n", state_path.data(), all_tokens.size());
common_batch batch_last(ctx);
batch_last.add(all_tokens.back(), n_past, 0, true);
llama_token last_token = all_tokens.back();
llama_batch_ext_ptr batch_last = common_batch_ext_get_one(ctx, { last_token });
llama_pos pos = n_past;
llama_batch_ext_set_pos(batch_last.get(), 0, &pos);
if (llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch_last.get())) {
COM_ERR("%s", "failed to eval last token\n");
@@ -2310,7 +2216,7 @@ bool common_prompt_batch_decode(
n_past++;
} else {
llama_tokens new_tokens(all_tokens.begin() + offset, all_tokens.begin() + offset + n_new);
common_batch batch = common_batch_get_one(ctx, new_tokens);
llama_batch_ext_ptr batch = common_batch_ext_get_one(ctx, new_tokens);
if (llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch.get())) {
COM_ERR("%s", "failed to eval\n");
return false;
+27 -104
View File
@@ -8,7 +8,6 @@
#include "ggml.h"
#include "llama.h"
#include <array>
#include <list>
#include <set>
#include <sstream>
@@ -19,7 +18,6 @@
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <cstdio>
#if defined(_WIN32) && !defined(_WIN32_WINNT)
#define _WIN32_WINNT 0x0A00
@@ -334,8 +332,6 @@ struct common_params_speculative_draft {
bool backend_sampling = true; // offload draft sampling to the backend (default: on)
bool probabilistic = false; // sample the draft and verify by rejection, instead of argmax and match
common_params_model mparams;
llama_context * ctx_tgt = nullptr;
@@ -586,15 +582,12 @@ struct common_params {
bool no_op_offload = false; // globally disable offload host tensor operations to device
bool no_extra_bufts = false; // disable extra buffer types (used for weight repacking)
bool no_host = false; // bypass host buffer allowing extra buffers to be used
bool load_mtp = false; // load MTP/NextN layers
bool single_turn = false; // single turn chat conversation
ggml_type cache_type_k = GGML_TYPE_F16; // KV cache data type for the K
ggml_type cache_type_v = GGML_TYPE_F16; // KV cache data type for the V
size_t moe_cache_size = 0; // GPU cache size in bytes for the MoE experts kept in the CPU
common_conversation_mode conversation_mode = COMMON_CONVERSATION_MODE_AUTO;
// multimodal models (see tools/mtmd)
@@ -727,11 +720,10 @@ struct common_params {
int32_t i_chunk = 0; // start processing from this chunk
int8_t imat_dat = 0; // whether the legacy imatrix.dat format should be output (gguf <= 0 < dat)
bool process_output = false; // collect data for the output tensor
bool compute_ppl = true; // whether to compute perplexity
bool show_statistics = false; // show imatrix statistics per tensor
bool activation_statistics = false; // generate data to calculate activation based statistics
bool parse_special = false; // whether to parse special tokens during imatrix tokenization
bool process_output = false; // collect data for the output tensor
bool compute_ppl = true; // whether to compute perplexity
bool show_statistics = false; // show imatrix statistics per tensor
bool parse_special = false; // whether to parse special tokens during imatrix tokenization
// cvector-generator params
int n_pca_batch = 100;
@@ -887,6 +879,7 @@ void string_process_escapes(std::string & input);
std::string string_from(bool value);
std::string string_from(const std::vector<int> & values);
std::string string_from(const struct llama_context * ctx, const std::vector<llama_token> & tokens);
std::string string_from(const struct llama_context * ctx, const struct llama_batch & batch);
bool glob_match(const std::string & pattern, const std::string & str);
@@ -911,30 +904,28 @@ std::string fs_path_to_utf8(const std::filesystem::path & path);
std::string common_get_env(const std::string & name);
void common_set_env(const std::string & name, const std::string & value);
// reads a path from the environment, an unset variable gives an empty path
std::filesystem::path common_get_path_from_env(const std::string & name);
//
// Filesystem utils
//
bool fs_validate_filename(const std::string & filename, bool allow_subdirs = false);
bool fs_create_directory_with_parents(const std::string & path);
bool fs_is_directory(const std::string & path);
// some old libstdc++ versions don't follow symlinks here, so adding a trailing "/" fixes it: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101510
inline bool common_create_directories(const std::filesystem::path & path, std::error_code & ec) {
#if defined(__linux__)
return std::filesystem::create_directories(path / "", ec);
#else
return std::filesystem::create_directories(path, ec);
#endif
}
std::string fs_get_cache_directory();
std::string fs_get_cache_file(const std::string & filename);
std::string fs_get_config_directory();
std::filesystem::path fs_get_cache_directory();
std::filesystem::path fs_get_cache_file(const std::string & filename);
std::filesystem::path fs_get_config_directory();
struct common_file_info {
std::string path;
std::string name;
size_t size = 0; // in bytes
bool is_dir = false;
};
std::vector<common_file_info> fs_list(const std::string & path, bool include_directories);
void fs_write_atomic(const std::filesystem::path & path, const std::string & data);
// fs open, also handle UTF8 on Windows
std::ifstream fs_open_ifstream(const std::string & fname, std::ios_base::openmode mode);
//
// TTY utils
@@ -942,10 +933,6 @@ void fs_write_atomic(const std::filesystem::path & path, const std::string & dat
// Auto-detect if colors can be enabled based on terminal and environment
bool tty_can_use_colors();
bool tty_enable_ansi(); // false when stdout or stderr is a console that cannot render ANSI sequences
// Check if the given file is attached to a terminal
bool common_is_tty(FILE * file);
//
// Model utils
@@ -953,25 +940,6 @@ bool common_is_tty(FILE * file);
struct common_sampler;
// typed decision models, see "<arch>.decision.type" in the model metadata
enum common_decision_type {
COMMON_DECISION_TYPE_NONE, // not a decision model
COMMON_DECISION_TYPE_OPENJEV, // logits of one label token per option, read at the last prompt token
COMMON_DECISION_TYPE_LEV, // same as openjev, noul is read from a rating scale
COMMON_DECISION_TYPE_KEV, // dot product of the hidden states of the last token and of one end token per option
COMMON_DECISION_TYPE_NIMBLE, // same as openjev, the prompt lists all the questions of the request
COMMON_DECISION_TYPE_LAYA, // score of one marker token per option, read from the embeddings output
COMMON_DECISION_TYPE_CLEF, // all questions in one prompt, score of option i read from the embeddings output at row i
COMMON_DECISION_TYPE_PPLX_DECIDER, // same as openjev, label codes of 1 or 2 letters
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
};
common_decision_type common_get_decision_type(const struct llama_model * model);
// same as above, but reads a GGUF file; it does not load the model
// returns COMMON_DECISION_TYPE_UNKNOWN if the file is missing, unreadable, or invalid
common_decision_type common_get_decision_type(const std::string & fname);
// note: defines the model, context, samplers, ets. lifetimes
struct common_init_result {
common_init_result(common_params & params, bool model_only = false);
@@ -1059,63 +1027,18 @@ struct common_memory {
// Batch utils
//
// wrapper around llama_batch_ext that provide getter functions for downstream code
// entries can exceed n_batch, use get_sub_batch() to decode them in chunks
struct common_batch {
struct token {
llama_token id;
std::array<llama_pos, GGML_MROPE_SECTIONS> pos; // only pos[0] is used for text tokens
llama_seq_id seq_id; // the first sequence id, see add_seq()
bool output;
llama_embd embd; // non-owning view of the data passed to add_embd()/set_embd(), data == NULL if none
std::vector<llama_seq_id> seq_ids_extra; // see add_seq()
int32_t decision_order = 0; // see llama_batch_ext_set_decision_order()
};
void common_batch_clear(struct llama_batch & batch);
std::vector<token> tokens; // mirror of the entries, tokens[i] describes batch index i
llama_batch_ext_ptr batch;
int32_t n_pos = 1; // positions per embedding entry, GGML_MROPE_SECTIONS for MROPE/IMROPE
common_batch() = default;
common_batch(struct llama_context * ctx);
llama_batch_ext * get() { return get_sub_batch(0, size()); }
// render entries [off, off + n) into batch, the result is overwritten by the next call
llama_batch_ext * get_sub_batch(int32_t off, int32_t n);
// content type of the batch, all entries carry the same combination
bool has_token() const { return !tokens.empty() && tokens[0].id != LLAMA_TOKEN_NULL; }
bool has_embd () const { return !tokens.empty() && tokens[0].embd.data != nullptr; }
void clear();
// returns the batch index
int32_t add(llama_token id, llama_pos pos, llama_seq_id seq_id, bool output);
// same, with the entry shared by all seq_ids (must not be empty)
int32_t add(llama_token id, llama_pos pos, const std::vector<llama_seq_id> & seq_ids, bool output);
// add the entry at idx to another sequence, tokens[idx].seq_id keeps the first one
bool add_seq(int32_t idx, llama_seq_id seq_id);
bool set_output(int32_t idx, bool value);
// attach a token embedding to the entry at idx, can only be set once per entry
bool set_embd(int32_t idx, llama_embd embd);
// add an embedding-only entry (no token id)
// pos points to n_pos positions
int32_t add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output);
int32_t size() const { return (int32_t) tokens.size(); }
};
void common_batch_add(
struct llama_batch & batch,
llama_token id,
llama_pos pos,
const std::vector<llama_seq_id> & seq_ids,
bool logits);
// create a single-sequence batch from a list of tokens
// positions continue from the memory, last token always have output_logits set to true
common_batch common_batch_get_one(struct llama_context * ctx, const llama_token * tokens, int32_t n_tokens);
common_batch common_batch_get_one(struct llama_context * ctx, const llama_tokens & tokens);
// last token always have output_logits set to true
llama_batch_ext_ptr common_batch_ext_get_one(struct llama_context * ctx, const llama_tokens & tokens);
// decodes a single batch of tokens for a prompt and manages session tokens
//
+2 -2
View File
@@ -1019,7 +1019,6 @@ namespace console {
line.clear();
pop_cursor();
}
line += '\n';
has_more = false;
}
} else {
@@ -1051,6 +1050,7 @@ namespace console {
if (!std::getline(std::wcin, wline)) {
// Input stream is bad or EOF received
line.clear();
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
return false;
}
@@ -1065,7 +1065,7 @@ namespace console {
if (!line.empty()) {
char last = line.back();
if (last == '/') { // Always return control on '/' symbol
line.back() = '\n';
line.pop_back();
return false;
}
if (last == '\\') { // '\\' changes the default action
+46 -11
View File
@@ -35,13 +35,50 @@
#endif
#endif
// isatty
#if defined(_WIN32)
#include <io.h>
#else
#include <unistd.h>
#endif
//
// downloader
//
// validate repo name format: owner/repo
static void write_file(const std::string & fname, const std::string & content) {
const std::string fname_tmp = fname + ".tmp";
std::ofstream file(fname_tmp);
if (!file) {
throw std::runtime_error(string_format("error: failed to open file '%s'\n", fname.c_str()));
}
try {
file << content;
file.close();
// Makes write atomic
if (rename(fname_tmp.c_str(), fname.c_str()) != 0) {
LOG_ERR("%s: unable to rename file: %s to %s\n", __func__, fname_tmp.c_str(), fname.c_str());
// If rename fails, try to delete the temporary file
if (remove(fname_tmp.c_str()) != 0) {
LOG_ERR("%s: unable to delete temporary file: %s\n", __func__, fname_tmp.c_str());
}
}
} catch (...) {
// If anything fails, try to delete the temporary file
if (remove(fname_tmp.c_str()) != 0) {
LOG_ERR("%s: unable to delete temporary file: %s\n", __func__, fname_tmp.c_str());
}
throw std::runtime_error(string_format("error: failed to write file '%s'\n", fname.c_str()));
}
}
static void write_etag(const std::string & path, const std::string & etag) {
const std::string etag_path = path + ".etag";
fs_write_atomic(std::filesystem::u8path(etag_path), etag);
write_file(etag_path, etag);
LOG_DBG("%s: file etag saved: %s\n", __func__, etag_path.c_str());
}
@@ -90,7 +127,11 @@ class ProgressBar : public common_download_callback {
}
static bool is_output_a_tty() {
return common_is_tty(stdout);
#if defined(_WIN32)
return _isatty(_fileno(stdout));
#else
return isatty(1);
#endif
}
public:
@@ -233,12 +274,6 @@ static bool common_pull_file(httplib::Client & cli,
return false;
}
ofs.close();
if (!ofs) {
LOG_ERR("%s: error closing file: %s\n", __func__, path_tmp.c_str());
return false;
}
return true;
}
@@ -251,7 +286,7 @@ static int common_download_file_single_online(const std::string & url,
static const int max_attempts = 3;
static const int retry_delay_seconds = 2;
const bool file_exists = std::filesystem::exists(std::filesystem::u8path(path));
const bool file_exists = std::filesystem::exists(path);
if (file_exists && skip_etag) {
LOG_DBG("%s: using cached file: %s\n", __func__, path.c_str());
@@ -442,7 +477,7 @@ int common_download_file_single(const std::string & url,
return common_download_file_single_online(url, path, online_opts, skip_etag);
}
if (!std::filesystem::exists(std::filesystem::u8path(path))) {
if (!std::filesystem::exists(path)) {
LOG_ERR("%s: required file is not available in cache (offline mode): %s\n", __func__, path.c_str());
return -1;
}
@@ -908,7 +943,7 @@ std::string common_docker_resolve_model(const std::string & docker) {
std::string model_filename = repo;
std::replace(model_filename.begin(), model_filename.end(), '/', '_');
model_filename += "_" + tag + ".gguf";
std::string local_path = fs_path_to_utf8(fs_get_cache_file(model_filename));
std::string local_path = fs_get_cache_file(model_filename);
const std::string blob_url = url_prefix + "/blobs/" + gguf_digest;
common_download_opts opts;
+39 -24
View File
@@ -44,7 +44,8 @@ static fs::path get_cache_directory() {
{HOME_DIR, fs::path(".cache") / "huggingface" / "hub"}
};
for (const auto & entry : entries) {
if (fs::path base = common_get_path_from_env(entry.var); !base.empty()) {
if (auto * p = std::getenv(entry.var); p && *p) {
fs::path base(p);
return entry.path.empty() ? base : base / entry.path;
}
}
@@ -172,6 +173,28 @@ static bool is_valid_subpath(const fs::path & path, const fs::path & subpath) {
return b_end == b.end();
}
static void safe_write_file(const fs::path & path, const std::string & data) {
fs::path path_tmp = path.string() + ".tmp";
if (path.has_parent_path()) {
fs::create_directories(path.parent_path());
}
std::ofstream file(path_tmp);
file << data;
file.close();
std::error_code ec;
if (!file.fail()) {
fs::rename(path_tmp, path, ec);
}
if (file.fail() || ec) {
fs::remove(path_tmp, ec);
throw std::runtime_error("failed to write file: " + path.string());
}
}
static common_json api_get(const std::string & url,
const std::string & token) {
auto [cli, parts] = common_http_client(url);
@@ -218,7 +241,6 @@ static std::string get_repo_commit(const std::string & repo_id,
fs::path refs_path = get_repo_path(repo_id) / "refs";
std::string name;
std::string commit;
fs::path name_path;
for (const auto & branch : json["branches"]) {
if (!branch.is_object() ||
@@ -229,28 +251,24 @@ static std::string get_repo_commit(const std::string & repo_id,
std::string _name = branch["name"].get<std::string>();
std::string _commit = branch["targetCommit"].get<std::string>();
if (!is_valid_commit(_commit)) {
LOG_WRN("%s: skip invalid commit: %s\n", __func__, _commit.c_str());
if (!is_valid_subpath(refs_path, _name)) {
LOG_WRN("%s: skip invalid branch: %s\n", __func__, _name.c_str());
continue;
}
const fs::path candidate = fs::u8path(_name);
if (!is_valid_subpath(refs_path, candidate)) {
LOG_WRN("%s: skip invalid branch: %s\n", __func__, _name.c_str());
if (!is_valid_commit(_commit)) {
LOG_WRN("%s: skip invalid commit: %s\n", __func__, _commit.c_str());
continue;
}
if (_name == "main") {
name = _name;
commit = _commit;
name_path = candidate;
break;
}
if (name.empty() || commit.empty()) {
name = _name;
commit = _commit;
name_path = candidate;
}
}
@@ -259,7 +277,7 @@ static std::string get_repo_commit(const std::string & repo_id,
return {};
}
fs_write_atomic(refs_path / name_path, commit);
safe_write_file(refs_path / name, commit);
return commit;
} catch (const common_json_error & e) {
@@ -308,9 +326,7 @@ hf_files get_repo_files(const std::string & repo_id,
file.repo_id = repo_id;
file.path = item["path"].get<std::string>();
const fs::path subpath = fs::u8path(file.path);
if (!is_valid_subpath(commit_path, subpath)) {
if (!is_valid_subpath(commit_path, file.path)) {
LOG_WRN("%s: skip invalid path: %s\n", __func__, file.path.c_str());
continue;
}
@@ -330,12 +346,12 @@ hf_files get_repo_files(const std::string & repo_id,
file.url = endpoint + repo_id + "/resolve/" + commit + "/" + file.path;
fs::path final_path = commit_path / subpath;
file.final_path = fs_path_to_utf8(final_path);
fs::path final_path = commit_path / file.path;
file.final_path = final_path.string();
if (!file.oid.empty() && !fs::exists(final_path)) {
fs::path local_path = blobs_path / file.oid;
file.local_path = fs_path_to_utf8(local_path);
file.local_path = local_path.string();
} else {
file.local_path = file.final_path;
}
@@ -402,7 +418,7 @@ hf_files get_cached_files(const std::string & repo_id) {
if (!fs::exists(snapshots_path)) {
continue;
}
std::string _repo_id = folder_name_to_repo(fs_path_to_utf8(repo.path().filename()));
std::string _repo_id = folder_name_to_repo(repo.path().filename().string());
if (!is_valid_repo_id(_repo_id)) {
continue;
@@ -425,9 +441,8 @@ hf_files get_cached_files(const std::string & repo_id) {
if (!path.empty()) {
hf_file file;
file.repo_id = _repo_id;
const auto generic_path = path.generic_u8string();
file.path = std::string(generic_path.begin(), generic_path.end());
file.local_path = fs_path_to_utf8(entry.path());
file.path = path.generic_string();
file.local_path = entry.path().string();
file.final_path = file.local_path;
files.push_back(std::move(file));
}
@@ -441,8 +456,8 @@ std::string finalize_file(const hf_file & file) {
static std::atomic<bool> symlinks_disabled{false};
std::error_code ec;
fs::path local_path = fs::u8path(file.local_path);
fs::path final_path = fs::u8path(file.final_path);
fs::path local_path(file.local_path);
fs::path final_path(file.final_path);
if (local_path == final_path || fs::exists(final_path, ec)) {
return file.final_path;
@@ -489,7 +504,7 @@ bool remove_cached_repo(const std::string & repo_id) {
std::error_code ec;
auto removed = fs::remove_all(repo_path, ec);
if (ec) {
LOG_ERR("%s: failed to remove repo cache %s: %s\n", __func__, fs_path_to_utf8(repo_path).c_str(), ec.message().c_str());
LOG_ERR("%s: failed to remove repo cache %s: %s\n", __func__, repo_path.string().c_str(), ec.message().c_str());
return false;
}
return removed > 0;
+12 -29
View File
@@ -98,10 +98,9 @@ bool common_imatrix_load(const std::string & fname, common_imatrix & imatrix) {
return false;
}
const int64_t datasets_key = gguf_find_key(ctx_gguf, LLM_KV_IMATRIX_DATASETS);
const int64_t datasets_key = gguf_find_key(ctx_gguf, LLM_KV_IMATRIX_DATASETS);
const int64_t chunk_count_key = gguf_find_key(ctx_gguf, LLM_KV_IMATRIX_CHUNK_COUNT);
const int64_t chunk_size_key = gguf_find_key(ctx_gguf, LLM_KV_IMATRIX_CHUNK_SIZE);
const int64_t nextn_key = gguf_find_key(ctx_gguf, LLM_KV_IMATRIX_N_LAYER_NEXTN);
if (datasets_key != -1 && gguf_get_kv_type(ctx_gguf, datasets_key) == GGUF_TYPE_ARRAY &&
gguf_get_arr_type(ctx_gguf, datasets_key) == GGUF_TYPE_STRING) {
@@ -112,42 +111,33 @@ bool common_imatrix_load(const std::string & fname, common_imatrix & imatrix) {
}
}
imatrix.has_metadata = datasets_key != -1 && chunk_count_key != -1 && chunk_size_key != -1;
imatrix.chunk_count = chunk_count_key != -1 ? gguf_get_val_u32(ctx_gguf, chunk_count_key) : 0;
imatrix.chunk_size = chunk_size_key != -1 ? gguf_get_val_u32(ctx_gguf, chunk_size_key) : 0;
imatrix.n_layer_nextn = nextn_key != -1 ? gguf_get_val_u32(ctx_gguf, nextn_key) : 0;
imatrix.has_metadata = (datasets_key != -1 && chunk_count_key != -1 && chunk_size_key != -1);
imatrix.chunk_count = (chunk_count_key != -1) ? gguf_get_val_u32(ctx_gguf, chunk_count_key) : 0;
imatrix.chunk_size = (chunk_size_key != -1) ? gguf_get_val_u32(ctx_gguf, chunk_size_key) : 0;
const std::string in_sum_suffix{ ".in_sum" };
const std::string in_sum2_suffix{ ".in_sum2" };
const std::string counts_suffix{ ".counts" };
struct sum_tensors {
struct ggml_tensor * in_sum = nullptr;
struct ggml_tensor * in_sum2 = nullptr;
struct ggml_tensor * counts = nullptr;
};
std::map<std::string, std::pair<struct ggml_tensor *, struct ggml_tensor *>> sums_counts_for;
std::map<std::string, sum_tensors> sums_counts_for;
for (struct ggml_tensor * cur = ggml_get_first_tensor(ctx); cur; cur = ggml_get_next_tensor(ctx, cur)) {
std::string name = cur->name;
if (name.empty()) { continue; }
if (string_remove_suffix(name, in_sum_suffix)) {
sums_counts_for[std::move(name)].in_sum = cur;
} else if (string_remove_suffix(name, in_sum2_suffix)) {
sums_counts_for[std::move(name)].in_sum2 = cur;
if (string_remove_suffix(name, in_sum2_suffix)) {
sums_counts_for[std::move(name)].first = cur;
} else if (string_remove_suffix(name, counts_suffix)) {
sums_counts_for[std::move(name)].counts = cur;
sums_counts_for[std::move(name)].second = cur;
}
}
for (const auto & sc : sums_counts_for) {
const std::string & name = sc.first;
const struct ggml_tensor * in_sum = sc.second.in_sum;
const struct ggml_tensor * in_sum2 = sc.second.in_sum2;
const struct ggml_tensor * counts = sc.second.counts;
const struct ggml_tensor * in_sum2 = sc.second.first;
const struct ggml_tensor * counts = sc.second.second;
if (!in_sum2 || !counts || (in_sum != nullptr && ggml_nelements(in_sum) != ggml_nelements(in_sum2))) {
if (!in_sum2 || !counts) {
LOG_ERR("%s: mismatched sums and counts for %s\n", __func__, name.c_str());
gguf_free(ctx_gguf);
ggml_free(ctx);
@@ -175,13 +165,6 @@ bool common_imatrix_load(const std::string & fname, common_imatrix & imatrix) {
for (int64_t j = 0; j < ncounts; ++j) {
e.counts[j] = std::lround(((const float *) counts->data)[j]);
}
if (in_sum && ggml_nelements(in_sum) == nval) {
e.activations.resize(nval);
for (int64_t j = 0; j < nval; ++j) {
e.activations[j] = ((const float *) in_sum->data)[j];
}
}
}
gguf_free(ctx_gguf);
-4
View File
@@ -8,12 +8,9 @@
inline constexpr const char * LLM_KV_IMATRIX_DATASETS = "imatrix.datasets";
inline constexpr const char * LLM_KV_IMATRIX_CHUNK_COUNT = "imatrix.chunk_count";
inline constexpr const char * LLM_KV_IMATRIX_CHUNK_SIZE = "imatrix.chunk_size";
inline constexpr const char * LLM_KV_IMATRIX_STATS_SCHEMA = "imatrix.stats_schema";
inline constexpr const char * LLM_KV_IMATRIX_N_LAYER_NEXTN = "imatrix.n_layer_nextn";
struct common_imatrix_entry {
std::vector<float> sums;
std::vector<float> activations;
std::vector<int64_t> counts;
};
@@ -22,7 +19,6 @@ struct common_imatrix {
std::vector<std::string> datasets;
int32_t chunk_count = 0;
int32_t chunk_size = 0;
int32_t n_layer_nextn = 0;
bool is_legacy = false;
bool has_metadata = false;
};
+30 -49
View File
@@ -37,57 +37,38 @@ static void caps_try_execute(jinja::program & prog,
const caps_ctx_fn & ctx_fn,
const caps_json_fn & tools_fn,
const caps_analyze_fn & analyze_fn) {
json msgs = messages_fn();
for (int attempt = 0; attempt < 2; attempt++) {
context ctx;
ctx.is_get_stats = true;
jinja::global_from_json(ctx, json{
{"messages", msgs},
{"tools", tools_fn ? tools_fn() : json::array()},
{"bos_token", ""},
{"eos_token", ""},
{"add_generation_prompt", true}
}, true);
context ctx;
ctx.is_get_stats = true;
jinja::global_from_json(ctx, json{
{"messages", messages_fn()},
{"tools", tools_fn ? tools_fn() : json::array()},
{"bos_token", ""},
{"eos_token", ""},
{"add_generation_prompt", true}
}, true);
if (ctx_fn) {
ctx_fn(ctx);
}
auto messages = ctx.get_val("messages");
auto tools = ctx.get_val("tools");
bool success = false;
std::string result;
try {
jinja::runtime runtime(ctx);
auto results = runtime.execute(prog);
auto parts = jinja::runtime::gather_string_parts(results);
result = parts->as_string().str();
success = true;
} catch (const std::exception & e) {
JJ_DEBUG("Exception during execution: %s", e.what());
result = "";
// ignore exceptions during capability analysis
}
// some templates require a thinking field on every assistant turn (e.g. K2 Horizon):
// retry once with an empty reasoning_content on the assistant turns that lack one
if (!success && attempt == 0) {
bool added = false;
for (auto & msg : msgs) {
if (msg.is_object() && msg.value("role", "") == "assistant" && !msg.contains("reasoning_content")) {
msg["reasoning_content"] = "";
added = true;
}
}
if (added) {
continue;
}
}
analyze_fn(ctx, success, messages, tools, result);
return;
if (ctx_fn) {
ctx_fn(ctx);
}
auto messages = ctx.get_val("messages");
auto tools = ctx.get_val("tools");
bool success = false;
std::string result;
try {
jinja::runtime runtime(ctx);
auto results = runtime.execute(prog);
auto parts = jinja::runtime::gather_string_parts(results);
result = parts->as_string().str();
success = true;
} catch (const std::exception & e) {
JJ_DEBUG("Exception during execution: %s", e.what());
result = "";
// ignore exceptions during capability analysis
}
analyze_fn(ctx, success, messages, tools, result);
}
// for debugging only
+8 -5
View File
@@ -537,6 +537,8 @@ value for_statement::execute_impl(context & ctx) const {
std::vector<value> filtered_items;
for (size_t i = 0; i < items.size(); ++i) {
context loop_scope(scope);
value current = items[i];
std::function<void(context&)> scope_update_fn = [](context &) { /* no-op */};
@@ -582,7 +584,6 @@ value for_statement::execute_impl(context & ctx) const {
}
if (select_expr && test_expr) {
context loop_scope(scope);
scope_update_fn(loop_scope);
value test_val = test_expr->execute(loop_scope);
if (!test_val->as_bool()) {
@@ -887,7 +888,7 @@ value member_expression::execute_impl(context & ctx) const {
JJ_DEBUG("Accessed property '%s' value, got type: %s", key.c_str(), val->type().c_str());
} else if (is_val<value_array>(object) || is_val<value_string>(object)) {
if (is_val<value_int>(property) || is_val<value_bool>(property)) {
if (is_val<value_int>(property)) {
int64_t index = property->as_int();
JJ_DEBUG("Accessing %s index %d", object->type().c_str(), (int)index);
if (is_val<value_array>(object)) {
@@ -910,6 +911,8 @@ value member_expression::execute_impl(context & ctx) const {
JJ_DEBUG("Accessing %s built-in '%s'", is_val<value_array>(object) ? "array" : "string", key.c_str());
val = try_builtin_func(ctx, key, object, true);
} else {
throw std::runtime_error("Cannot access property with non-string/non-number: got " + property->type());
}
} else {
if (!is_val<value_string>(property)) {
@@ -923,10 +926,10 @@ value member_expression::execute_impl(context & ctx) const {
value_t::stats_t::mark_used(val);
value_t::stats_t::mark_used(object);
value_t::stats_t::mark_used(property);
if (is_val<value_object>(object) || is_val<value_string>(property) || is_val<value_float>(property) || is_val<value_array>(property) || is_val<value_none>(property)) {
object->stats.ops.insert("object_access");
} else if (is_val<value_int>(property) || is_val<value_bool>(property)) {
if (is_val<value_int>(property)) {
object->stats.ops.insert("array_access");
} else if (is_val<value_string>(property)) {
object->stats.ops.insert("object_access");
}
}
+56 -63
View File
@@ -149,13 +149,6 @@ static value test_type_fn(const func_args & args) {
JJ_DEBUG("test_type_fn: type=%s, %s or %s result=%d", typeid(T).name(), typeid(U).name(), typeid(V).name(), is_type ? 1 : 0);
return mk_val<value_bool>(is_type);
}
template<typename T, typename U, typename V, typename W>
static value test_type_fn(const func_args & args) {
args.ensure_count(1);
bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)) || is_val<V>(args.get_pos(0)) || is_val<W>(args.get_pos(0));
JJ_DEBUG("test_type_fn: type=%s, %s, %s or %s result=%d", typeid(T).name(), typeid(U).name(), typeid(V).name(), typeid(W).name(), is_type ? 1 : 0);
return mk_val<value_bool>(is_type);
}
template<value_compare_op op>
static value test_compare_fn(const func_args & args) {
args.ensure_count(2, 2);
@@ -268,30 +261,6 @@ static value tojson(const func_args & args) {
return mk_val<value_string>(json_str);
}
static value & get_attribute(const value & val, const value & attr, value & default_val) {
if (!attr->is_undefined()) {
if (is_val<value_array>(val)) {
value idx = attr;
if (is_val<value_string>(attr)) {
const std::string s = attr->as_string().str();
if (!s.empty() && std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); })) {
try {
idx = mk_val<value_int>(std::stoll(s));
} catch (...) {
idx = mk_val<value_undefined>();
}
}
}
return val->at(idx, default_val);
} else if (is_val<value_object>(val)) {
return val->at(attr, default_val);
}
}
return default_val;
}
template<bool is_reject>
static value selectattr(const func_args & args) {
args.ensure_count(2, 4);
@@ -305,7 +274,10 @@ static value selectattr(const func_args & args) {
if (args.count() == 2) {
// example: array | selectattr("active")
for (const auto & item : arr) {
value attr_val = get_attribute(item, attribute, val_default);
if (!is_val<value_object>(item)) {
throw raised_exception("selectattr: item is not an object");
}
value attr_val = item->at(attribute, val_default);
bool is_selected = attr_val->as_bool();
if constexpr (is_reject) is_selected = !is_selected;
if (is_selected) out->push_back(item);
@@ -346,7 +318,10 @@ static value selectattr(const func_args & args) {
}
auto test_fn = it->second;
for (const auto & item : arr) {
value attr_val = get_attribute(item, attribute, val_default);
if (!is_val<value_object>(item)) {
throw raised_exception("selectattr: item is not an object");
}
value attr_val = item->at(attribute, val_default);
func_args test_args(args.ctx);
test_args.push_back(attr_val); // attribute value
test_args.push_back(extra_arg); // extra argument
@@ -503,8 +478,8 @@ const func_builtins & global_builtins() {
{"test_is_integer", test_type_fn<value_int>},
{"test_is_float", test_type_fn<value_float>},
{"test_is_number", test_type_fn<value_int, value_float>},
{"test_is_iterable", test_type_fn<value_object, value_array, value_string, value_undefined>},
{"test_is_sequence", test_type_fn<value_object, value_array, value_string, value_undefined>},
{"test_is_iterable", test_type_fn<value_array, value_string, value_undefined>},
{"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>},
{"test_is_mapping", test_type_fn<value_object>},
{"test_is_lower", [](const func_args & args) -> value {
args.ensure_vals<value_string>();
@@ -1093,14 +1068,22 @@ const func_builtins & value_array_t::get_builtins() const {
}
value val_delim = args.get_kwarg_or_pos("d", 1);
value attribute = args.get_kwarg_or_pos("attribute", 2);
value undef = mk_val<value_undefined>();
const auto & arr = args.get_pos(0)->as_array();
const bool attr_is_int = is_val<value_int>(attribute);
if (!attribute->is_undefined() && !is_val<value_string>(attribute) && !attr_is_int) {
throw raised_exception("join() attribute must be string or integer");
}
const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
const std::string delim = val_delim->is_undefined() ? "" : val_delim->as_string().str();
std::string result;
for (size_t i = 0; i < arr.size(); ++i) {
value val_arr = arr[i];
if (!attribute->is_undefined()) {
val_arr = get_attribute(val_arr, attribute, undef);
if (attr_is_int && is_val<value_array>(val_arr)) {
val_arr = val_arr->at(attr_int);
} else if (!attr_is_int && is_val<value_object>(val_arr)) {
val_arr = val_arr->at(attribute);
}
}
if (!is_val<value_string>(val_arr) && !is_val<value_int>(val_arr) && !is_val<value_float>(val_arr)) {
throw raised_exception("join() can only join arrays of strings or numerics");
@@ -1132,11 +1115,21 @@ const func_builtins & value_array_t::get_builtins() const {
}
value val = args.get_pos(0);
value attribute = args.get_kwarg_or_pos("attribute", 1);
const bool attr_is_int = is_val<value_int>(attribute);
if (!is_val<value_string>(attribute) && !attr_is_int) {
throw raised_exception("map: attribute must be string or integer");
}
const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
value default_val = args.get_kwarg("default", mk_val<value_undefined>());
auto out = mk_val<value_array>();
auto arr = val->as_array();
for (const auto & item : arr) {
value attr_val = get_attribute(item, attribute, default_val);
value attr_val;
if (attr_is_int) {
attr_val = is_val<value_array>(item) ? item->at(attr_int, default_val) : default_val;
} else {
attr_val = is_val<value_object>(item) ? item->at(attribute, default_val) : default_val;
}
out->push_back(attr_val);
}
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(out->as_array())) : out;
@@ -1173,14 +1166,22 @@ const func_builtins & value_array_t::get_builtins() const {
// FIXME: sorting is currently always case sensitive
//const bool case_sensitive = val_case->as_bool(); // undefined == false
const bool reverse = val_reverse->as_bool(); // undefined == false
value undef = mk_val<value_undefined>();
const bool attr_is_int = is_val<value_int>(attribute);
const int64_t attr_int = attr_is_int ? attribute->as_int() : 0;
std::vector<value> arr = val->as_array(); // copy
std::sort(arr.begin(), arr.end(),[&](const value & a, const value & b) {
value val_a = a;
value val_b = b;
if (!attribute->is_undefined()) {
val_a = get_attribute(a, attribute, undef);
val_b = get_attribute(b, attribute, undef);
if (attr_is_int && is_val<value_array>(a) && is_val<value_array>(b)) {
val_a = a->at(attr_int);
val_b = b->at(attr_int);
} else if (!attr_is_int && is_val<value_object>(a) && is_val<value_object>(b)) {
val_a = a->at(attribute);
val_b = b->at(attribute);
} else {
throw raised_exception("sort: unsupported object attribute comparison between " + a->type() + " and " + b->type());
}
}
return value_compare(val_a, val_b, reverse ? value_compare_op::gt : value_compare_op::lt);
});
@@ -1198,23 +1199,19 @@ const func_builtins & value_array_t::get_builtins() const {
args.ensure_vals<value_array>();
value val_case = args.get_kwarg_or_pos("case_sensitive", 1);
value attribute = args.get_kwarg_or_pos("attribute", 2);
if (!attribute->is_undefined()) {
throw not_implemented_exception("min: attribute not implemented");
}
// FIXME: min is currently always case sensitive
(void) val_case;
value undef = mk_val<value_undefined>();
const auto & arr = args.get_pos(0)->as_array();
if (arr.empty()) {
return undef;
return mk_val<value_undefined>();
}
value result = arr[0];
for (const auto & item : arr) {
value val_arr = item;
value val_cmp = result;
if (!attribute->is_undefined()) {
val_arr = get_attribute(val_arr, attribute, undef);
val_cmp = get_attribute(val_cmp, attribute, undef);
}
if (value_compare(val_arr, val_cmp, value_compare_op::lt)) {
result = item;
for (size_t i = 1; i < arr.size(); ++i) {
if (value_compare(arr[i], result, value_compare_op::lt)) {
result = arr[i];
}
}
return result;
@@ -1224,23 +1221,19 @@ const func_builtins & value_array_t::get_builtins() const {
args.ensure_vals<value_array>();
value val_case = args.get_kwarg_or_pos("case_sensitive", 1);
value attribute = args.get_kwarg_or_pos("attribute", 2);
if (!attribute->is_undefined()) {
throw not_implemented_exception("max: attribute not implemented");
}
// FIXME: max is currently always case sensitive
(void) val_case;
value undef = mk_val<value_undefined>();
const auto & arr = args.get_pos(0)->as_array();
if (arr.empty()) {
return undef;
return mk_val<value_undefined>();
}
value result = arr[0];
for (const auto & item : arr) {
value val_arr = item;
value val_cmp = result;
if (!attribute->is_undefined()) {
val_arr = get_attribute(val_arr, attribute, undef);
val_cmp = get_attribute(val_cmp, attribute, undef);
}
if (value_compare(val_arr, val_cmp, value_compare_op::gt)) {
result = item;
for (size_t i = 1; i < arr.size(); ++i) {
if (value_compare(arr[i], result, value_compare_op::gt)) {
result = arr[i];
}
}
return result;
-6
View File
@@ -433,12 +433,6 @@ struct value_array_t : public value_t {
}
return val_arr[index];
}
virtual value & at(const value & index, value & default_val) override {
if (!is_val<value_int>(index) && !is_val<value_bool>(index)) {
return default_val;
}
return at(index->as_int(), default_val);
}
virtual const func_builtins & get_builtins() const override;
virtual bool is_hashable() const override {
if (std::all_of(val_arr.begin(), val_arr.end(), [&](auto & val) -> bool {
+17 -20
View File
@@ -14,6 +14,19 @@
#include <vector>
#include <algorithm>
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <io.h>
# include <windows.h>
# define isatty _isatty
# define fileno _fileno
#else
# include <unistd.h>
#endif // defined(_WIN32)
int common_log_verbosity_thold = LOG_DEFAULT_LLAMA;
int common_log_get_verbosity_thold(void) {
@@ -144,16 +157,12 @@ struct common_log_entry {
}
}
// the reset goes before the trailing newlines, so that every line carries its own colors
const bool reset = level == GGML_LOG_LEVEL_WARN || level == GGML_LOG_LEVEL_ERROR || level == GGML_LOG_LEVEL_DEBUG;
fprintf(fcur, "%s", msg.data());
size_t end = strlen(msg.data());
while (end > 0 && msg[end - 1] == '\n') {
end--;
if (level == GGML_LOG_LEVEL_WARN || level == GGML_LOG_LEVEL_ERROR || level == GGML_LOG_LEVEL_DEBUG) {
fprintf(fcur, "%s", g_col[COMMON_LOG_COL_DEFAULT]);
}
fprintf(fcur, "%.*s%s%s", (int) end, msg.data(), reset ? g_col[COMMON_LOG_COL_DEFAULT] : "", msg.data() + end);
fflush(fcur);
}
};
@@ -162,7 +171,6 @@ struct common_log {
// default capacity
common_log(size_t capacity = 512) {
file = nullptr;
colors = false;
prefix = false;
timestamps = false;
running = false;
@@ -190,7 +198,6 @@ private:
FILE * file;
bool colors;
bool prefix;
bool timestamps;
bool running;
@@ -400,16 +407,10 @@ public:
resume();
}
bool get_colors() const {
return colors;
}
void set_colors(bool colors) {
pause();
this->colors = colors && tty_enable_ansi();
if (this->colors) {
if (colors) {
g_col[COMMON_LOG_COL_DEFAULT] = LOG_COL_DEFAULT;
g_col[COMMON_LOG_COL_BOLD] = LOG_COL_BOLD;
g_col[COMMON_LOG_COL_RED] = LOG_COL_RED;
@@ -512,10 +513,6 @@ void common_log_set_colors(struct common_log * log, log_colors colors) {
log->set_colors(true);
}
bool common_log_get_colors(struct common_log * log) {
return log->get_colors();
}
void common_log_set_prefix(struct common_log * log, bool prefix) {
log->set_prefix(prefix);
}
-1
View File
@@ -93,7 +93,6 @@ void common_log_add(struct common_log * log, enum ggml_log_level level, const ch
void common_log_set_file (struct common_log * log, const char * file); // not thread-safe
void common_log_set_colors (struct common_log * log, log_colors colors); // not thread-safe
bool common_log_get_colors (struct common_log * log); // whether colors are enabled
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_flush (struct common_log * log); // flush all pending log messages
-193
View File
@@ -1,193 +0,0 @@
#include "parsers.h"
// K2 Horizon format:
// - Reasoning: <ifm|think>...</ifm|think>, or <ifm|think_fast>/<ifm|think_faster> for medium/low reasoning_effort
// - Tool calls: <ifm|tool_calls><ifm|tool_call>...</ifm|tool_call>...</ifm|tool_calls>, one call per <ifm|tool_call>:
// xml (default): name <ifm|arg_key>k</ifm|arg_key> [<ifm|arg_type>t</ifm|arg_type>] <ifm|arg_value>v</ifm|arg_value> ...
// json: {"name": "...", "arguments": {...}}
common_chat_params common_chat_params_init_k2_horizon(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
// The template requires a thinking field on every assistant message
auto messages = inputs.messages;
for (auto & msg : messages) {
if (msg.value("role", "") == "assistant" && !msg.contains("reasoning_content")) {
msg["reasoning_content"] = "";
}
}
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs, messages);
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs, messages);
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
data.supports_thinking = true;
const std::string effort = inputs.extra_context.value("reasoning_effort", "high");
const std::string call_format = inputs.extra_context.value("tool_call_format", "xml");
// Templates that handle enable_thinking disable it with an empty <ifm|think></ifm|think> block for every effort
const bool thinking_off = !inputs.enable_thinking && tmpl.source().find("enable_thinking") != std::string::npos;
const std::string think = thinking_off ? "ifm|think" :
effort == "medium" ? "ifm|think_fast" :
effort == "low" ? "ifm|think_faster" : "ifm|think";
const std::string GEN_PREFIX = "<|ifm|im_start|>assistant\n";
const std::string THINK_START = "<" + think + ">";
const std::string THINK_END = "</" + think + ">";
const std::string SECTION_START = "<ifm|tool_calls>";
const std::string SECTION_END = "</ifm|tool_calls>";
const std::string CALL_START = "<ifm|tool_call>";
const std::string CALL_END = "</ifm|tool_call>";
const std::string ARG_KEY = "<ifm|arg_key>";
const std::string ARG_KEY_END = "</ifm|arg_key>";
const std::string ARG_TYPE = "<ifm|arg_type>";
const std::string ARG_TYPE_END = "</ifm|arg_type>";
const std::string ARG_VAL = "<ifm|arg_value>";
const std::string ARG_VAL_END = "</ifm|arg_value>";
data.thinking_start_tag = THINK_START;
data.thinking_end_tags = { THINK_END };
data.preserved_tokens = data.thinking_end_tags;
data.preserved_tokens.insert(data.preserved_tokens.end(), {
THINK_START, SECTION_START, SECTION_END, CALL_START, CALL_END,
ARG_KEY, ARG_KEY_END, ARG_TYPE, ARG_TYPE_END, ARG_VAL, ARG_VAL_END,
});
data.message_delimiters = {
{ COMMON_CHAT_ROLE_ASSISTANT, "<|ifm|im_start|>assistant" },
{ COMMON_CHAT_ROLE_USER, "<|ifm|im_start|>user" },
{ COMMON_CHAT_ROLE_TOOL, "<|ifm|im_start|>tool" },
{ COMMON_CHAT_ROLE_SYSTEM, "<|ifm|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 + THINK_START + "\n" + 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_PREFIX);
auto think_end = p.choice();
for (const auto & tag : data.thinking_end_tags) {
think_end |= p.literal(tag);
}
auto think_body = p.until_one_of(data.thinking_end_tags);
auto think_block = [&](const common_peg_parser & body) {
return p.optional(THINK_START + p.space() + p.ac(body + think_end, data.thinking_end_tags));
};
auto reasoning = extract_reasoning ? think_block(p.reasoning(think_body)) : p.eps();
if (has_response_format) {
// The answer must be bare JSON, so the think block is consumed even when it is not extracted
auto thoughts = extract_reasoning ? reasoning : think_block(think_body);
return generation_prompt + (thoughts << p.content(p.schema(p.json(), "response-format", inputs.json_schema)));
}
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return generation_prompt + (reasoning << p.content(p.rest()));
}
auto tool_choice = p.choice();
if (call_format == "json") {
tool_choice = p.standard_json_tools(CALL_START, CALL_END, inputs.tools, false, true);
} else {
auto arg_close = p.tool_arg_close(p.literal(ARG_VAL_END));
auto arg_string = p.rule("xml-arg-string", p.ac(p.tool_arg_string_value(p.until(ARG_VAL_END)) + arg_close, ARG_VAL_END));
// The models leave out <ifm|arg_type> even when asked for xml_typed
auto arg_type = call_format == "xml_typed" ? p.optional(ARG_TYPE + p.until(ARG_TYPE_END) + ARG_TYPE_END + p.space()) : p.eps();
foreach_function(inputs.tools, [&](const json & tool) {
const auto & function = tool.at("function");
std::string name = function.at("name");
std::vector<common_peg_parser> required_args;
std::vector<common_peg_parser> optional_args;
foreach_parameter(function, [&](const common_chat_schema_property & param, const common_chat_schema_document_ptr & doc) {
auto rule_name = "tool-" + name + "-arg-" + param.name;
auto types = param.schema->value_types();
auto arg_value = arg_string;
if (!types.has(common_chat_schema::TYPE_STRING)) {
arg_value = p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
}
if (types.has(common_chat_schema::TYPE_STRING) && !types.is_only(common_chat_schema::TYPE_STRING)) {
// The string alternative accepts any text, so only the parser needs the JSON alternatives.
auto json_value = p.choice();
if (types.has(common_chat_schema::TYPE_OBJECT)) {
json_value |= p.json_object();
}
if (types.has(common_chat_schema::TYPE_ARRAY)) {
json_value |= p.json_array();
}
if (types.has(common_chat_schema::TYPE_NUMBER) || types.has(common_chat_schema::TYPE_INTEGER)) {
json_value |= p.json_number();
}
if (types.has(common_chat_schema::TYPE_BOOLEAN)) {
json_value |= p.json_bool();
}
if (types.has(common_chat_schema::TYPE_NULL)) {
json_value |= p.json_null();
}
arg_value = p.gbnf(p.atomic(p.tool_arg_json_value(json_value) + arg_close) | arg_string, "xml-arg-string");
}
auto arg = p.space() + p.tool_arg(p.tool_arg_open(ARG_KEY + p.tool_arg_name(p.literal(param.name)) + ARG_KEY_END) <<
arg_type + ARG_VAL + arg_value);
(param.required ? required_args : optional_args).push_back(p.rule(rule_name, arg));
});
auto args = p.permute("tool-" + name + "-args", required_args);
if (!optional_args.empty()) {
args = args + p.zero_or_more(p.choice(optional_args));
}
tool_choice |= p.rule("tool-" + name, p.tool(
p.tool_open(CALL_START + p.tool_name(p.literal(name)) + "\n") + p.tool_args(args) << p.tool_close(p.literal(CALL_END))));
});
}
auto required = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;
auto calls = inputs.parallel_tool_calls ? tool_choice + p.zero_or_more(p.space() + tool_choice) : tool_choice;
auto tool_calls = p.trigger_rule("tool-calls", p.repeat(SECTION_START << calls << SECTION_END, required ? 1 : 0, 1));
// Keep thinking inline when required calls bypass the content parser.
if (required && !extract_reasoning) {
reasoning = p.content(think_block(think_body));
}
// A required call follows the reasoning directly, the models otherwise keep writing content
auto content = required ? p.eps() : p.content(p.until(SECTION_START));
return generation_prompt + (reasoning << content << tool_calls);
});
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !(has_response_format || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED);
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
parser.build_grammar(builder, data.grammar_lazy);
});
if (data.grammar_lazy) {
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, SECTION_START },
};
}
}
return data;
}
+4 -12
View File
@@ -75,10 +75,9 @@ common_chat_params common_chat_params_init_ling3(const common_chat_template &
(last_close == std::string::npos || last_open > last_close);
}
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);
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;
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
auto end = p.end();
@@ -102,13 +101,6 @@ common_chat_params common_chat_params_init_ling3(const common_chat_template &
// a trailing end-of-turn token is consumed instead of leaking into content
auto tail = p.optional(p.content(p.until(ROLE_END))) + p.optional(p.literal(ROLE_END));
// the think block must close before the JSON, so the turn cannot end inside the reasoning
if (has_response_format) {
auto closed_reasoning = p.literal(THINK_START) + think_body + p.literal(THINK_END);
auto response_format = p.content(p.schema(p.json(), "response-format", inputs.json_schema));
return opener + (closed_reasoning << response_format) + end;
}
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
return opener + reasoning + tail + end;
}
@@ -188,7 +180,7 @@ common_chat_params common_chat_params_init_ling3(const common_chat_template &
data.parser = parser.save();
if (include_grammar) {
data.grammar_lazy = !has_response_format && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
parser.build_grammar(builder, data.grammar_lazy);
});
-164
View File
@@ -1,164 +0,0 @@
#include "parsers.h"
// LLM-jp-4.1: the GPT-OSS (Harmony) format with two differences
// - the tokenizer emits a space after every special token: "<|channel|> analysis<|message|> ..."
// - parallel tool calls are consecutive assistant messages, all but the last closed by <|end|>
common_chat_params common_chat_params_init_llm_jp_harmony(const common_chat_template & tmpl,
const autoparser::generation_params & inputs) {
common_chat_params data;
// Copy reasoning to the "thinking" field as expected by the 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) {
// tokenizer space after special tokens; not p.space() since GBNF `space` allows one space only
auto sp = p.chars("[ ]", 0, -1);
auto channel_tag = p.literal("<|channel|>") + sp;
// one space only: keep an intentional leading space in the body
auto message = p.literal("<|message|>") + p.optional(p.literal(" "));
auto start = p.rule("start", p.literal("<|start|>") + sp + p.literal("assistant"));
auto end = p.rule("end", p.literal("<|end|>"));
auto content = p.rule("message-content", p.until("<|end|>"));
auto channel = channel_tag + (p.literal("commentary") | p.literal("analysis"));
auto constrain_type = p.chars("[A-Za-z0-9_-]", 1, -1);
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>") + sp) + constrain_type);
auto start_analysis = channel_tag + p.literal("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", channel_tag + p.literal("commentary") + message + p.content(content) + end);
auto final_msg = p.rule("final", channel_tag + p.literal("final") + message + p.content(content));
auto any = p.rule("any", preamble | analysis);
if (has_response_format) {
auto response_format = p.rule("response-format",
channel_tag + p.literal("final") + constraint + 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 = common_chat_tool_parameters(function);
auto func_name = p.literal(" to=functions.") + p.tool_name(p.literal(name));
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 + 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 + message) + args);
tool_choice |= p.rule("tool-" + name, tool_in_role | tool_in_channel);
});
// parallel calls are separated by <|end|>; inside the trigger rule so the lazy grammar covers all of them
auto tool_calls = inputs.parallel_tool_calls
? tool_choice + p.zero_or_more(end + start + tool_choice)
: tool_choice;
auto tool_call = p.trigger_rule("tool-call", tool_calls);
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;
});
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) {
parser.build_grammar(builder, data.grammar_lazy);
});
data.grammar_triggers = {
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^\\s+to$" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^<\\|channel\\|>\\s*(?:commentary|analysis)\\s+to=functions$" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>\\s*assistant(\\s+to)" },
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>\\s*assistant(<\\|channel\\|>\\s*(?:commentary|analysis)\\s+to)" }
};
}
return data;
}
+4 -14
View File
@@ -43,10 +43,9 @@ common_chat_params common_chat_params_init_muse_glimmer(const common_chat_templa
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
// Constrained grammar whenever tools are offered or a response format is requested.
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_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"));
@@ -66,15 +65,6 @@ common_chat_params common_chat_params_init_muse_glimmer(const common_chat_templa
auto final_msg = p.rule("final", recipient + p.literal("<|message|>") +
p.content(p.until_one_of({ "<|eot|>", "<|eom|>" })));
if (has_response_format) {
auto response_json = p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema));
auto response_format = p.rule("response-format",
recipient + p.literal("<|message|>") +
((p.literal("```json") + p.space() + response_json + p.space() + p.literal("```")) | response_json));
return p.zero_or_more(start + analysis) + start + response_format;
}
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>")),
@@ -134,7 +124,7 @@ common_chat_params common_chat_params_init_muse_glimmer(const common_chat_templa
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_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
parser.build_grammar(builder, data.grammar_lazy);
});
-4
View File
@@ -59,8 +59,6 @@ common_chat_params common_chat_params_init_gigachat_v3(const common_chat_templat
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_k2_horizon(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);
@@ -70,8 +68,6 @@ common_chat_params common_chat_params_init_ling3(const common_chat_template & tm
// 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_llm_jp_harmony(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
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);
-2
View File
@@ -9,12 +9,10 @@ set(LLAMA_CHAT_PARSERS_SOURCES
${CMAKE_CURRENT_LIST_DIR}/gemma4.cpp
${CMAKE_CURRENT_LIST_DIR}/gigachat-v3.cpp
${CMAKE_CURRENT_LIST_DIR}/gpt-oss.cpp
${CMAKE_CURRENT_LIST_DIR}/k2-horizon.cpp
${CMAKE_CURRENT_LIST_DIR}/kimi-k2.cpp
${CMAKE_CURRENT_LIST_DIR}/kimi-k3.cpp
${CMAKE_CURRENT_LIST_DIR}/ling3.cpp
${CMAKE_CURRENT_LIST_DIR}/lfm2.cpp
${CMAKE_CURRENT_LIST_DIR}/llm-jp-harmony.cpp
${CMAKE_CURRENT_LIST_DIR}/minicpm5.cpp
${CMAKE_CURRENT_LIST_DIR}/minimax-m3.cpp
${CMAKE_CURRENT_LIST_DIR}/ministral3.cpp
+1 -8
View File
@@ -2,7 +2,6 @@
#include "json-schema.h"
#include "json.h"
#include "llama.h"
#include <memory>
#include <set>
@@ -183,8 +182,7 @@ inline common_peg_parse_flags operator~(common_peg_parse_flags a) {
}
struct common_peg_parse_context {
std::string input; // [h, e, l, l, o, _, w, o, r, l, d]
std::vector<llama_token> tokens; // [id, -1, -1, -1, -1, id, -1, -1, -1, -1, -1]
std::string input;
common_peg_parse_flags flags;
common_peg_ast_arena ast;
@@ -196,11 +194,6 @@ struct common_peg_parse_context {
common_peg_parse_context(const std::string & input, common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_NONE)
: input(input), flags(flags), parse_depth(0) {}
common_peg_parse_context(std::string input, std::vector<llama_token> tokens, common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_NONE)
: input(std::move(input)), tokens(std::move(tokens)), flags(flags), parse_depth(0) {
GGML_ASSERT(this->tokens.empty() || this->tokens.size() == this->input.size());
}
bool is_lenient() const { return flags & COMMON_PEG_PARSE_FLAG_LENIENT; }
bool is_debug() const { return flags & COMMON_PEG_PARSE_FLAG_DEBUG; }
};
+51 -63
View File
@@ -167,16 +167,16 @@ void common_preset::apply_to_params(common_params & params, const std::set<std::
}
}
static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_file(const std::filesystem::path & path) {
static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_file(const std::string & path) {
std::map<std::string, std::map<std::string, std::string>> parsed;
if (!std::filesystem::exists(path)) {
throw std::runtime_error("preset file does not exist: " + fs_path_to_utf8(path));
throw std::runtime_error("preset file does not exist: " + path);
}
std::ifstream file(path);
if (!file.good()) {
throw std::runtime_error("failed to open server preset file: " + fs_path_to_utf8(path));
throw std::runtime_error("failed to open server preset file: " + path);
}
std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
@@ -225,7 +225,7 @@ static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_
common_peg_parse_context ctx(contents);
const auto result = parser.parse(ctx);
if (!result.success()) {
throw std::runtime_error("failed to parse server config file: " + fs_path_to_utf8(path));
throw std::runtime_error("failed to parse server config file: " + path);
}
std::string current_section = COMMON_PRESET_DEFAULT_NAME;
@@ -282,7 +282,7 @@ common_preset_context::common_preset_context(llama_example ex)
key_to_opt = get_map_key_opt(ctx_params);
}
common_presets common_preset_context::load_from_ini(const std::filesystem::path & path, common_preset & global) const {
common_presets common_preset_context::load_from_ini(const std::string & path, common_preset & global) const {
common_presets out;
auto ini_data = parse_ini_from_file(path);
@@ -323,7 +323,7 @@ common_presets common_preset_context::load_from_ini(const std::filesystem::path
}
LOG_DBG("accepted option: %s = %s\n", key.c_str(), preset.options[opt].c_str());
} else if (ignore_unknown_keys) {
LOG_WRN("ignoring option '%s' from %s: not supported by this program\n", key.c_str(), fs_path_to_utf8(path).c_str());
LOG_WRN("ignoring option '%s' from %s: not supported by this program\n", key.c_str(), path.c_str());
} else {
throw std::runtime_error(string_format(
"option '%s' not recognized in preset '%s'",
@@ -385,75 +385,63 @@ static bool is_draft_file(const std::string & fname) {
}
common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const {
const std::filesystem::path dir = std::filesystem::u8path(models_dir);
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
if (!std::filesystem::exists(models_dir) || !std::filesystem::is_directory(models_dir)) {
throw std::runtime_error(string_format("error: '%s' does not exist or is not a directory\n", models_dir.c_str()));
}
std::vector<local_model> models;
auto scan_subdir = [&models](const std::filesystem::path & subdir_path, const std::string & name) {
std::filesystem::path model_file;
std::filesystem::path first_shard_file;
std::filesystem::path mmproj_file;
std::filesystem::path draft_file;
std::error_code ec;
for (const auto & entry : std::filesystem::directory_iterator(subdir_path)) {
if (!entry.is_regular_file(ec)) {
continue;
}
const std::string fname = fs_path_to_utf8(entry.path().filename());
if (!string_ends_with(fname, ".gguf")) {
continue;
}
if (is_mmproj_file(fname)) {
mmproj_file = entry.path();
} else if (is_draft_file(fname)) {
if (draft_file.empty()) {
draft_file = entry.path(); // first sidecar found wins
auto scan_subdir = [&models](const std::string & subdir_path, const std::string & name) {
auto files = fs_list(subdir_path, false);
common_file_info model_file;
common_file_info first_shard_file;
common_file_info mmproj_file;
common_file_info draft_file;
for (const auto & file : files) {
if (string_ends_with(file.name, ".gguf")) {
if (is_mmproj_file(file.name)) {
mmproj_file = file;
} else if (is_draft_file(file.name)) {
if (draft_file.path.empty()) {
draft_file = file; // first sidecar found wins
}
} else if (file.name.find("-00001-of-") != std::string::npos) {
first_shard_file = file;
} else {
model_file = file;
}
} else if (fname.find("-00001-of-") != std::string::npos) {
first_shard_file = entry.path();
} else {
model_file = entry.path();
}
}
// single file model
const std::filesystem::path & path = first_shard_file.empty() ? model_file : first_shard_file;
if (!path.empty()) {
models.push_back({
/* name */ name,
/* path */ fs_path_to_utf8(path),
/* path_mmproj */ fs_path_to_utf8(mmproj_file), // can be empty
/* path_draft */ fs_path_to_utf8(draft_file) // can be empty
});
local_model model{
/* name */ name,
/* path */ first_shard_file.path.empty() ? model_file.path : first_shard_file.path,
/* path_mmproj */ mmproj_file.path, // can be empty
/* path_draft */ draft_file.path // can be empty
};
if (!model.path.empty()) {
models.push_back(model);
}
};
for (const auto & entry : std::filesystem::directory_iterator(dir)) {
std::error_code ec;
if (entry.is_directory(ec)) {
scan_subdir(entry.path(), fs_path_to_utf8(entry.path().filename()));
continue;
auto files = fs_list(models_dir, true);
for (const auto & file : files) {
if (file.is_dir) {
scan_subdir(file.path, file.name);
} else if (string_ends_with(file.name, ".gguf")) {
if (is_mmproj_file(file.name) || is_draft_file(file.name)) {
continue; // companion file, cannot be loaded as a model on its own
}
// single file model
std::string name = file.name;
string_replace_all(name, ".gguf", "");
local_model model{
/* name */ name,
/* path */ file.path,
/* path_mmproj */ "",
/* path_draft */ ""
};
models.push_back(model);
}
if (!entry.is_regular_file(ec)) {
continue;
}
const std::string fname = fs_path_to_utf8(entry.path().filename());
if (!string_ends_with(fname, ".gguf")) {
continue;
}
if (is_mmproj_file(fname) || is_draft_file(fname)) {
continue; // companion file, cannot be loaded as a model on its own
}
// single file model
std::string name = fname;
string_replace_all(name, ".gguf", "");
models.push_back({
/* name */ name,
/* path */ fs_path_to_utf8(entry.path()),
/* path_mmproj */ "",
/* path_draft */ ""
});
}
// convert local models to presets
+1 -1
View File
@@ -67,7 +67,7 @@ struct common_preset_context {
common_preset_context(llama_example ex);
// load presets from INI file
common_presets load_from_ini(const std::filesystem::path & path, common_preset & global) const;
common_presets load_from_ini(const std::string & path, common_preset & global) const;
// generate presets from cached models
common_presets load_from_cache() const;
+3 -136
View File
@@ -12,7 +12,6 @@
#include <climits>
#include <cmath>
#include <cstring>
#include <random>
#include <unordered_map>
#include <vector>
@@ -122,9 +121,6 @@ struct common_sampler {
llama_token_data_array cur_p;
// for rejection sampling; independent of the draft, or the target distribution is not preserved
std::mt19937 rng;
void reset() {
prev.clear();
@@ -399,11 +395,8 @@ struct common_sampler * common_sampler_init(
// only if user explicitly included adaptive-p sampler
samplers.push_back(llama_sampler_init_adaptive_p(params.adaptive_target, params.adaptive_decay, params.seed));
} else {
// Keep distribution sampling when callers request probabilities.
const bool greedy = params.n_probs == 0 && !params.samplers.empty() &&
((params.samplers.back() == COMMON_SAMPLER_TYPE_TEMPERATURE && params.temp == 0.0f && params.dynatemp_range == 0.0f) ||
(params.samplers.back() == COMMON_SAMPLER_TYPE_TOP_K && params.top_k == 1));
samplers.push_back(greedy ? llama_sampler_init_greedy() : llama_sampler_init_dist(params.seed));
// default: sample from distribution
samplers.push_back(llama_sampler_init_dist(params.seed));
}
} else if (params.mirostat == 1) {
samplers.push_back(llama_sampler_init_temp(params.temp));
@@ -439,8 +432,6 @@ struct common_sampler * common_sampler_init(
/* .prev = */ ring_buffer<llama_token>(std::max(32, params.n_prev)),
/* .cur = */ {},
/* .cur_p = */ {},
// mix it, the chain and the draft are seeded from this one too
/* .rng = */ std::mt19937(llama_sampler_get_seed(chain) ^ 0x9e3779b9u),
};
return result;
@@ -524,7 +515,6 @@ struct common_sampler * common_sampler_clone(common_sampler * gsmpl) {
/* .prev = */ gsmpl->prev,
/* .cur = */ gsmpl->cur,
/* .cur_p = */ gsmpl->cur_p,
/* .rng = */ gsmpl->rng,
};
}
@@ -545,7 +535,6 @@ void common_sampler_copy(const common_sampler * src, common_sampler * dst) {
dst->cur = src->cur;
dst->cur_p = src->cur_p;
dst->cur_p.data = src->cur_p.data ? dst->cur.data() : nullptr; // re-point to dst's buffer
dst->rng = src->rng;
dst->t_total_us = src->t_total_us;
}
@@ -692,8 +681,6 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
std::vector<llama_token> result;
result.reserve(idxs.size());
const llama_vocab * vocab = llama_model_get_vocab(llama_get_model(ctx));
size_t i = 0;
for (; i < draft.size(); i++) {
const llama_token id = common_sampler_sample(gsmpl, ctx, idxs[i], grammar_first);
@@ -702,9 +689,7 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
result.push_back(id);
// do not accept draft tokens after an EOG - they are not output but would stay in the context
// on replay the last token is from the target and can be EOG, so a trailing EOG is still accepted
if (draft[i] != id || (llama_vocab_is_eog(vocab, id) && i + 1 < draft.size())) {
if (draft[i] != id) {
break;
}
}
@@ -720,124 +705,6 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
return result;
}
static float prob_of(const llama_token_data * data, size_t n, llama_token id) {
for (size_t k = 0; k < n; ++k) {
if (data[k].id == id) {
return data[k].p;
}
}
return 0.0f;
}
// Accept a drafted token with probability min(1, p/q), else draw from norm(max(0, p - q)).
// Preserves the target distribution exactly, and accepts more often than matching does when the
// draft samples instead of taking its argmax.
std::vector<llama_token> common_sampler_sample_and_accept_n_rejection(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, const std::vector<std::vector<llama_token_data>> & draft_q, bool grammar_first) {
GGML_ASSERT(idxs.size() == draft.size() + 1 && "idxs.size() must be draft.size() + 1");
GGML_ASSERT(draft_q.size() == draft.size() && "draft_q must have one entry per draft token");
std::vector<llama_token> result;
result.reserve(idxs.size());
// draws come from the sampler's own stream, so they stay independent of what was drafted
std::uniform_real_distribution<float> uni(0.0f, 1.0f);
std::vector<llama_token_data> residual;
std::vector<llama_token_data> cand; // candidate array masked by the grammar, if there is one
size_t i = 0;
for (; i < draft.size(); i++) {
// leaves the target distribution in the candidate array
const llama_token id_tgt = common_sampler_sample(gsmpl, ctx, idxs[i], grammar_first);
const auto * cur_p = common_sampler_get_candidates(gsmpl, true);
const auto & q = draft_q[i];
const bool masked = !grammar_first && grammar_should_apply(gsmpl);
if (masked) {
cand.assign(cur_p->data, cur_p->data + cur_p->size);
llama_token_data_array arr = { cand.data(), cand.size(), -1, false };
llama_sampler_apply(gsmpl->grmr, &arr);
}
// a candidate the grammar rejects carries no probability, whatever the target thinks
auto p_raw = [&](size_t k) {
return masked && cand[k].logit == -INFINITY ? 0.0f : cur_p->data[k].p;
};
// masking drops probability mass, so rescale what is left or the residual is over-weighted
float p_sum = 0.0f;
if (masked) {
for (size_t k = 0; k < cur_p->size; ++k) {
p_sum += p_raw(k);
}
}
const float p_norm = masked && p_sum > 0.0f ? 1.0f/p_sum : 1.0f;
auto p_of = [&](size_t k) {
return p_raw(k)*p_norm;
};
// q_x is never 0 for a token the draft produced, but guard the divide
const float q_x = prob_of(q.data(), q.size(), draft[i]);
float p_x = 0.0f;
for (size_t k = 0; k < cur_p->size; ++k) {
if (cur_p->data[k].id == draft[i]) {
p_x = p_of(k);
break;
}
}
if (q_x > 0.0f && (p_x >= q_x || uni(gsmpl->rng) < p_x / q_x)) {
common_sampler_accept(gsmpl, draft[i], true);
result.push_back(draft[i]);
continue;
}
// rejected: tokens outside q's support keep all of p
residual.clear();
float sum = 0.0f;
for (size_t k = 0; k < cur_p->size; ++k) {
const float r = p_of(k) - prob_of(q.data(), q.size(), cur_p->data[k].id);
if (r > 0.0f) {
residual.push_back({ cur_p->data[k].id, 0.0f, r });
sum += r;
}
}
llama_token id = id_tgt;
if (sum > 0.0f) {
float u = uni(gsmpl->rng) * sum;
id = residual.back().id;
for (const auto & e : residual) {
u -= e.p;
if (u <= 0.0f) {
id = e.id;
break;
}
}
}
common_sampler_accept(gsmpl, id, true);
result.push_back(id);
break;
}
if (i == draft.size()) {
const llama_token id = common_sampler_sample(gsmpl, ctx, idxs[i], grammar_first);
common_sampler_accept(gsmpl, id, true);
result.push_back(id);
}
return result;
}
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first) {
std::vector<int> idxs(draft.size() + 1);
for (size_t i = 0; i < idxs.size(); ++i) {
-3
View File
@@ -85,9 +85,6 @@ llama_token common_sampler_sample(struct common_sampler * gsmpl, struct llama_co
//
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, bool grammar_first = false);
// as above, but verifies by rejection sampling; draft_q holds the draft's candidates per token
std::vector<llama_token> common_sampler_sample_and_accept_n_rejection(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, const std::vector<std::vector<llama_token_data>> & draft_q, bool grammar_first = false);
// assume idxs == [ 0, 1, 2, ..., draft.size() ]
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first = false);
+197 -268
View File
@@ -30,45 +30,6 @@
#define SPEC_VOCAB_MAX_SIZE_DIFFERENCE 128
#define SPEC_VOCAB_CHECK_START_TOKEN_ID 5
// Rebuild seq_id's draft sampler at the target's temperature: rejection weighs q against p, so
// both have to sample alike. Only temp and seed carry over; the draft keeps its own top_k.
static void spec_retune(
std::vector<common_sampler_ptr> & smpls,
std::vector<common_params_sampling> & cfg,
const llama_model * model,
llama_seq_id seq_id,
float temp,
uint32_t seed) {
if (cfg.size() != smpls.size()) {
const size_t n_old = cfg.size();
cfg.resize(smpls.size());
// the initial sampler has no temperature, so no request may match the cache and skip a rebuild
for (size_t i = n_old; i < cfg.size(); ++i) {
cfg[i].temp = NAN;
}
}
auto & cur = cfg[seq_id];
if (cur.temp == temp && cur.seed == seed) {
return;
}
cur.temp = temp;
cur.seed = seed;
common_params_sampling sparams;
sparams.no_perf = false;
sparams.top_k = 10;
sparams.temp = cur.temp;
// must be explicit, the default reseeds at random; mixed so it differs from the target's
sparams.seed = cur.seed == LLAMA_DEFAULT_SEED ? cur.seed : cur.seed ^ 0x85ebca6bu;
sparams.samplers = { COMMON_SAMPLER_TYPE_TOP_K, COMMON_SAMPLER_TYPE_TEMPERATURE };
smpls[seq_id].reset(common_sampler_init(model, sparams));
}
const std::map<std::string, common_speculative_type> common_speculative_type_from_name_map = {
{"none", COMMON_SPECULATIVE_TYPE_NONE},
{"draft-simple", COMMON_SPECULATIVE_TYPE_DRAFT_SIMPLE},
@@ -103,7 +64,7 @@ struct common_speculative_config {
const common_params_speculative & p = common_params_speculative{}) : type(t), params(p) {}
};
bool common_speculative_are_compatible(
static bool common_speculative_are_compatible(
const llama_model * model_tgt,
const llama_model * model_dft) {
const llama_vocab * vocab_tgt = llama_model_get_vocab(model_tgt);
@@ -204,7 +165,7 @@ struct common_speculative_impl {
virtual void begin(llama_seq_id seq_id, const llama_tokens & prompt) = 0;
virtual bool process(const common_batch & batch) = 0;
virtual bool process(const llama_batch & batch) = 0;
virtual void draft(common_speculative_draft_params_vec & dparams) = 0;
@@ -218,16 +179,10 @@ struct common_speculative_impl {
struct common_speculative_impl_draft_simple : public common_speculative_impl {
common_params_speculative_draft params;
common_batch batch;
// zero row at the draft input width, stands in for target embeddings the draft cannot read
std::vector<float> zeros;
bool zeros_warned = false; // the substitution is reported once
llama_batch batch;
std::vector<common_sampler_ptr> smpls;
std::vector<common_params_sampling> smpls_cfg;
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)
, params(params.draft)
@@ -239,8 +194,6 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
throw std::runtime_error("draft-simple requires a draft context");
}
zeros.assign(llama_model_n_embd_inp(llama_get_model(ctx_dft)), 0.0f);
SPC_TRC("%s", "adding speculative implementation 'draft-simple'\n");
SPC_TRC("- n_max=%d, n_min=%d, p_min=%f\n", this->params.n_max, this->params.n_min, this->params.p_min);
SPC_TRC("- gpu_layers=%d, cache_k=%s, cache_v=%s, ctx_tgt=%s, ctx_dft=%s, devices=[%s]\n",
@@ -251,7 +204,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
ctx_dft ? "yes" : "no",
common_speculative_get_devices_str(this->params.devices).c_str());
batch = common_batch(ctx_dft);
batch = llama_batch_init(llama_n_batch(ctx_dft), 0, 1);
// TODO: optimize or pass from outside?
// {
@@ -296,47 +249,21 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
}
}
void begin(llama_seq_id seq_id, const llama_tokens & /*prompt*/) override {
// reset here rather than per round, or two identical requests differ
common_sampler_reset(smpls[seq_id].get());
~common_speculative_impl_draft_simple() override {
llama_batch_free(batch);
}
bool process(const common_batch & batch_in) override {
void begin(llama_seq_id /*seq_id*/, const llama_tokens & /*prompt*/) override {
// noop
}
bool process(const llama_batch & batch) override {
auto * ctx_dft = params.ctx_dft;
// copy the entries to a batch owned by the draft context, only the last token is output
batch.clear();
const int32_t n_tokens = batch_in.size();
for (int32_t k = 0; k < n_tokens; ++k) {
const auto & t = batch_in.tokens[k];
const bool output = k == n_tokens - 1;
if (t.id != LLAMA_TOKEN_NULL) {
const int32_t idx = batch.add(t.id, t.pos[0], t.seq_id, output);
if (t.embd.data) {
batch.set_embd(idx, t.embd);
}
} else {
// mtmd input is projected by the target encoder, a draft with a different width cannot read it
// it gets zeros instead, keeping its positions contiguous
// ref: https://github.com/ggml-org/llama.cpp/pull/29385#discussion_r4124743243
const size_t n_embd = t.embd.n_rows * t.embd.n_embd;
const bool same_width = n_embd == zeros.size();
if (!same_width && !zeros_warned) {
SPC_WRN("target embeddings of size %zu do not fit the draft input width %zu, "
"the draft receives zero rows for them and drafts after multimodal input will be poor\n",
n_embd, zeros.size());
zeros_warned = true;
}
const llama_embd embd = same_width ? t.embd : llama_embd{ zeros.data(), 1, zeros.size() };
batch.add_embd(embd, t.pos.data(), t.seq_id, output);
}
}
llama_batch batch_dft = batch;
batch_dft.logits = nullptr;
if (batch.size() == 0) {
return true;
}
const int ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
const int ret = llama_decode(ctx_dft, batch_dft);
if (ret != 0) {
SPC_ERR("failed to decode draft batch, ret = %d\n", ret);
@@ -350,7 +277,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
void draft(common_speculative_draft_params_vec & dparams) override {
auto & ctx_dft = params.ctx_dft;
batch.clear();
common_batch_clear(batch);
// keep track of which sequences are still drafting
int n_drafting = 0;
@@ -365,27 +292,14 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
n_drafting++;
drafting[seq_id] = true;
// greedy drafting leaves no candidates behind, so the verifier falls back to sample-and-match
if (!params.probabilistic) {
dp.result_q = nullptr;
}
common_sampler_reset(smpls[seq_id].get());
// result_q is only set when the caller wants rejection, so it also gates the retune
if (dp.result_q) {
spec_retune(smpls, smpls_cfg, llama_get_model(ctx_dft), seq_id, dp.temp, dp.seed);
}
// a reset reseeds the chain, which breaks probabilistic drafting
if (!dp.result_q) {
common_sampler_reset(smpls[seq_id].get());
}
batch.add(dp.id_last, dp.pos0, seq_id, true);
common_batch_add(batch, dp.id_last, dp.pos0, { seq_id }, true);
}
int ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
int ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
SPC_ERR("llama_process returned %d\n", ret);
SPC_ERR("llama_decode returned %d\n", ret);
return;
}
@@ -394,7 +308,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
while (n_drafting > 0) {
int i_batch = 0;
batch.clear();
common_batch_clear(batch);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
if (!drafting[seq_id]) {
@@ -403,7 +317,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
auto * smpl = smpls[seq_id].get();
const llama_token id_sampled = common_sampler_sample(smpl, ctx_dft, i_batch, true);
common_sampler_sample(smpl, ctx_dft, i_batch, true);
++i_batch;
const auto * cur_p = common_sampler_get_candidates(smpl, true);
@@ -415,7 +329,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
}
// add drafted token for each sequence
const llama_token id = dparams.at(seq_id).result_q ? id_sampled : cur_p->data[0].id;
const llama_token id = cur_p->data[0].id;
// only collect very high-confidence draft tokens
if (cur_p->data[0].p < params.p_min) {
@@ -432,10 +346,6 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
result.push_back(id);
if (dp.result_q) {
dp.result_q->emplace_back(cur_p->data, cur_p->data + cur_p->size);
}
if ((params.n_max <= (int) result.size()) ||
(dp.n_max > 0 && dp.n_max <= (int) result.size())) {
drafting[seq_id] = false;
@@ -443,17 +353,17 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
continue;
}
batch.add(id, dp.pos0 + i + 1, seq_id, true);
common_batch_add(batch, id, dp.pos0 + i + 1, { seq_id }, true);
}
if (batch.size() == 0) {
if (batch.n_tokens == 0) {
break;
}
// evaluate the drafted tokens on the draft model
ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
SPC_ERR("llama_process[%d] returned %d\n", i, ret);
SPC_ERR("llama_decode[%d] returned %d\n", i, ret);
break;
}
@@ -513,8 +423,7 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
// encoder+decoder on n_accepted+1 rows).
struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
common_params_speculative_draft params;
common_batch batch; // decoder input, (token, g_embd) pairs
common_batch batch_enc; // encoder input, built from the extracted target features
llama_batch batch;
std::vector<common_sampler_ptr> smpls;
@@ -568,8 +477,11 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
n_embd_enc = (int32_t) target_layer_ids_n * n_embd_tgt;
n_layer_tgt = llama_model_n_layer(model_tgt);
batch = common_batch(ctx_dft);
batch_enc = common_batch(ctx_dft);
const int32_t n_b = (int32_t) llama_n_batch(ctx_dft);
batch = llama_batch_init(/*n_tokens=*/ n_b, /*embd=*/ n_embd_dec, /*n_seq_max=*/ 1);
// llama_batch_init allocates only one of token/embd; eagle3 decoder needs both.
// TODO: fix, how to call without malloc
batch.token = (llama_token *) malloc(sizeof(llama_token) * n_b);
smpls.resize(n_seq);
for (auto & s : smpls) {
@@ -631,6 +543,12 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
llama_sampler_free(backend_chains[seq_id]);
}
backend_chains.clear();
if (batch.token != nullptr) {
free(batch.token);
batch.token = nullptr;
}
llama_batch_free(batch);
}
void begin(llama_seq_id seq_id, const llama_tokens & prompt) override {
@@ -649,16 +567,16 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
}
}
bool process(const common_batch & batch_in) override {
if (batch_in.size() <= 0) {
bool process(const llama_batch & batch_in) override {
if (batch_in.n_tokens <= 0) {
return true;
}
if (!batch_in.has_token() || batch_in.has_embd()) {
if (batch_in.token == nullptr || batch_in.embd != nullptr) {
return true;
}
const int32_t n_tokens = batch_in.size();
const int32_t n_tokens = batch_in.n_tokens;
// i_batch_beg[seq] / i_batch_end[seq]: inclusive batch indices of this seq's
// first/last token in batch_in. Assumes per-seq tokens are contiguous within
@@ -666,7 +584,8 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
std::vector<int32_t> i_batch_beg(n_seq, -1);
std::vector<int32_t> i_batch_end(n_seq, -1);
for (int k = 0; k < n_tokens; ++k) {
const llama_seq_id seq_id = batch_in.tokens[k].seq_id;
GGML_ASSERT(batch_in.n_seq_id[k] == 1);
const llama_seq_id seq_id = batch_in.seq_id[k][0];
if (seq_id < 0 || seq_id >= (llama_seq_id) n_seq) {
continue;
}
@@ -700,23 +619,24 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
g_embd_buf.resize((size_t) n_tokens * n_embd_dec);
// llama_process() requires the full encoder batch to fit in n_ubatch.
// llama_encode() requires the full encoder batch to fit in n_ubatch.
// Allow batch > ubatch: eagle3's per-token encoder can be chunked safely.
const int32_t n_ubatch_dft = (int32_t) llama_n_ubatch(ctx_dft);
for (int32_t i = 0; i < n_tokens; i += n_ubatch_dft) {
const int32_t n_chunk = std::min(n_ubatch_dft, n_tokens - i);
// the per-token encoder does not use positions, generate placeholder ones from the memory state
batch_enc.clear();
llama_pos pos = llama_memory_seq_pos_max(llama_get_memory(ctx_dft), 0) + 1;
for (int32_t j = 0; j < n_chunk; ++j) {
batch_enc.add_embd({ features_buf.data() + (size_t) (i + j) * n_embd_enc, 1, (size_t) n_embd_enc }, &pos, 0, true);
pos++;
}
const int32_t rc = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_ENCODE, batch_enc.get());
llama_batch enc_batch = {
/*.n_tokens =*/ n_chunk,
/*.token =*/ nullptr,
/*.embd =*/ features_buf.data() + (size_t) i * n_embd_enc,
/*.pos =*/ nullptr,
/*.n_seq_id =*/ nullptr,
/*.seq_id =*/ nullptr,
/*.logits =*/ nullptr,
};
const int32_t rc = llama_encode(ctx_dft, enc_batch);
if (rc != 0) {
SPC_ERR("llama_process(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
SPC_ERR("llama_encode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
rc, (int) n_chunk, (int) i);
return false;
}
@@ -744,7 +664,7 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
// deferred boundary, completed by the next process() or draft() call.
// (c) refresh deferred state — stash this ubatch's full g_embd into verify_g,
// update pending_g_last / pending_pos_last to the last row.
batch.clear();
common_batch_clear(batch);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
const int32_t beg = i_batch_beg[seq_id];
@@ -759,34 +679,36 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
// 2) pending_pos_last + 1 == pos[beg]
// 3) pending_pos_last > dft_pos_max // TODO: is this check needed?
const llama_pos pending_pos = pending_pos_last[seq_id];
if (pending_pos >= 0 && pending_pos + 1 == batch_in.tokens[beg].pos[0]) {
if (pending_pos >= 0 && pending_pos + 1 == batch_in.pos[beg]) {
const llama_pos dft_pos_max = llama_memory_seq_pos_max(llama_get_memory(ctx_dft), seq_id);
if (pending_pos > dft_pos_max) {
const int32_t idx = batch.add(batch_in.tokens[beg].id, pending_pos, seq_id, /*output=*/ false);
batch.set_embd(idx, { pending_g_last[seq_id].data(), 1, (size_t) n_embd_dec });
common_batch_add(batch, batch_in.token[beg], pending_pos, { seq_id }, /*logits=*/ false);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd_dec,
pending_g_last[seq_id].data(), row_bytes);
}
}
for (int32_t k = beg; k < end; ++k) {
const int32_t idx = batch.add(batch_in.tokens[k + 1].id, batch_in.tokens[k].pos[0], seq_id, /*output=*/ false);
batch.set_embd(idx, { g_embd + (size_t) k * n_embd_dec, 1, (size_t) n_embd_dec });
common_batch_add(batch, batch_in.token[k + 1], batch_in.pos[k], { seq_id }, /*logits=*/ false);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd_dec,
g_embd + (size_t) k * n_embd_dec, row_bytes);
}
// refresh deferred state
const int32_t n_rows = end - beg + 1;
verify_pos_first[seq_id] = batch_in.tokens[beg].pos[0];
pending_pos_last[seq_id] = batch_in.tokens[end].pos[0];
verify_pos_first[seq_id] = batch_in.pos[beg];
pending_pos_last[seq_id] = batch_in.pos[end];
verify_g_rows[seq_id] = n_rows;
verify_g[seq_id].resize((size_t) n_rows * n_embd_dec, 0.0f);
std::memcpy(verify_g[seq_id].data(), g_embd + (size_t) beg * n_embd_dec, row_bytes * n_rows);
std::memcpy(pending_g_last[seq_id].data(), g_embd + (size_t) end * n_embd_dec, row_bytes);
}
if (batch.size() > 0) {
const int32_t rc = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
if (batch.n_tokens > 0) {
const int32_t rc = llama_decode(ctx_dft, batch);
if (rc != 0) {
SPC_ERR("llama_process(ctx_dft) failed rc=%d (n_tokens=%d, ubatch_pos[0]=%d)\n",
rc, (int) batch.size(), (int) batch_in.tokens[0].pos[0]);
SPC_ERR("llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, ubatch_pos[0]=%d)\n",
rc, (int) batch.n_tokens, (int) batch_in.pos[0]);
return false;
}
}
@@ -797,12 +719,14 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
void draft(common_speculative_draft_params_vec & dparams) override {
auto & ctx_dft = params.ctx_dft;
batch.clear();
common_batch_clear(batch);
// keep track of which sequences are still drafting
int n_drafting = 0;
std::vector<bool> drafting(n_seq);
const size_t row_bytes = (size_t) n_embd_dec * sizeof(float);
// Complete the deferred boundary pair (dp.id_last, pending_g_last) at memory
// pos pending_pos_last. dp.id_last is target's freshest sample (= corrected
// token after verify, or first generated token after prefill), matching the
@@ -823,17 +747,19 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
llama_memory_seq_rm(llama_get_memory(ctx_dft), seq_id, pending_pos_last[seq_id], -1);
const int32_t idx = batch.add(dp.id_last, pending_pos_last[seq_id], seq_id, true);
batch.set_embd(idx, { pending_g_last[seq_id].data(), 1, (size_t) n_embd_dec });
common_batch_add(batch, dp.id_last, pending_pos_last[seq_id], { seq_id }, true);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd_dec,
pending_g_last[seq_id].data(),
row_bytes);
}
if (batch.size() == 0) {
if (batch.n_tokens == 0) {
return;
}
int ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
int ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
SPC_ERR("llama_process returned %d\n", ret);
SPC_ERR("llama_decode returned %d\n", ret);
return;
}
@@ -842,7 +768,7 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
while (n_drafting > 0) {
int i_batch = 0;
batch.clear();
common_batch_clear(batch);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
if (!drafting[seq_id]) {
@@ -888,17 +814,17 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
continue;
}
const int32_t idx = batch.add(id, pending_pos_last[seq_id] + (i + 1), seq_id, true);
batch.set_embd(idx, { prenorm, 1, (size_t) n_embd_dec });
common_batch_add(batch, id, pending_pos_last[seq_id] + (i + 1), { seq_id }, true);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd_dec, prenorm, row_bytes);
}
if (batch.size() == 0) {
if (batch.n_tokens == 0) {
break;
}
ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
SPC_ERR("llama_process[%d] returned %d\n", i, ret);
SPC_ERR("llama_decode[%d] returned %d\n", i, ret);
break;
}
@@ -982,10 +908,8 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
struct common_speculative_impl_draft_dflash : public common_speculative_impl {
common_params_speculative_draft params;
common_batch batch; // noise tokens
common_batch batch_inject; // target features for KV cache injection
std::vector<float> features_buf; // [n_chunk, n_embd_enc] gathered target features
llama_batch batch; // noise tokens
llama_batch batch_inject; // target features for KV cache injection
std::vector<common_sampler_ptr> smpls;
@@ -1081,11 +1005,15 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
}
this->n_max = this->params.n_max;
batch = common_batch(ctx_dft);
batch_inject = common_batch(ctx_dft);
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 carry 4 position rows per token
// 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));
}
smpls.resize(n_seq);
for (auto & s : smpls) {
@@ -1134,6 +1062,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
llama_sampler_free(backend_chains[seq_id]);
}
backend_chains.clear();
llama_batch_free(batch);
llama_batch_free(batch_inject);
}
void begin(llama_seq_id seq_id, const llama_tokens & prompt) override {
@@ -1154,8 +1085,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
}
}
bool process(const common_batch & batch_in) override {
if (batch_in.size() <= 0) {
bool process(const llama_batch & batch_in) override {
if (batch_in.n_tokens <= 0) {
return true;
}
@@ -1163,19 +1094,20 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
// produce the target-layer features used to seed the draft KV cache, so
// embeddings are injected too, except the pinned ones skipped below.
// TODO: revisit after https://github.com/ggml-org/llama.cpp/pull/24669 is merged
const bool has_tokens = batch_in.has_token();
const bool has_embeddings = batch_in.has_embd();
const bool has_tokens = batch_in.token != nullptr;
const bool has_embeddings = batch_in.embd != nullptr;
if (has_tokens == has_embeddings) {
return true;
}
const int32_t n_tokens = batch_in.size();
const int32_t n_tokens = batch_in.n_tokens;
// per-seq inclusive batch range (assumes each seq's tokens are contiguous in the batch)
std::vector<int32_t> i_batch_beg(n_seq, -1);
std::vector<int32_t> i_batch_end(n_seq, -1);
for (int32_t k = 0; k < n_tokens; ++k) {
const llama_seq_id seq_id = batch_in.tokens[k].seq_id;
GGML_ASSERT(batch_in.n_seq_id[k] == 1);
const llama_seq_id seq_id = batch_in.seq_id[k][0];
if (seq_id < 0 || seq_id >= (llama_seq_id) n_seq) {
continue;
}
@@ -1198,7 +1130,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
// an M-RoPE image pins all its rows to one position, so a windowed draft
// cache cannot free cells for it - skip it, the draft can jump over the gap
const bool pos_pinned = batch_in.tokens[i_batch_beg[seq_id]].pos[0] == batch_in.tokens[i_batch_end[seq_id]].pos[0];
const bool pos_pinned = batch_in.pos[i_batch_beg[seq_id]] == batch_in.pos[i_batch_end[seq_id]];
if (has_embeddings && n_rows > 1 && pos_pinned) {
continue;
}
@@ -1208,28 +1140,34 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
// gather target features per extract layer; the fused decode encodes and
// injects them into the K/V cache at the target positions
features_buf.resize((size_t) n_chunk * n_embd_enc);
batch_inject.n_tokens = n_chunk;
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 = features_buf.data() + (size_t) i * n_embd_enc + k * (size_t) n_embd_tgt;
float * dst = batch_inject.embd + (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));
}
}
batch_inject.clear();
for (int32_t i = 0; i < n_chunk; ++i) {
const llama_pos p = batch_in.tokens[i_batch_beg[seq_id] + offset + i].pos[0];
const llama_pos pos_arr[4] = { p, p, p, 0 };
batch_inject.add_embd({ features_buf.data() + (size_t) i * n_embd_enc, 1, (size_t) n_embd_enc }, pos_arr, seq_id, false);
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.n_seq_id[i] = 1;
batch_inject.seq_id[i][0] = seq_id;
batch_inject.logits[i] = false;
}
const int32_t rc = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch_inject.get());
const int32_t rc = llama_decode(ctx_dft, batch_inject);
if (rc != 0) {
LOG_ERR("%s: llama_process(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
LOG_ERR("%s: llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
__func__, rc, (int) n_chunk, (int) offset);
return false;
}
@@ -1242,7 +1180,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
void draft(common_speculative_draft_params_vec & dparams) override {
auto & ctx_dft = params.ctx_dft;
batch.clear();
common_batch_clear(batch);
// build one batch holding every drafting sequence's noise block into a single decode)
// record where each block starts and its size
@@ -1262,21 +1200,21 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
const int32_t n_draft = params.n_max;
const int32_t n_block_tokens = n_draft + (is_dspark && sample_from_anchor ? 0 : 1);
i_block_beg[seq_id] = batch.size();
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) {
batch.add(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 }, !is_dflash2);
}
}
if (batch.size() == 0) {
if (batch.n_tokens == 0) {
return;
}
// decode all sequence's noise block in a single batch
int ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
int ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
LOG_WRN("%s: llama_process returned %d\n", __func__, ret);
LOG_WRN("%s: llama_decode returned %d\n", __func__, ret);
return;
}
@@ -1390,12 +1328,10 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
struct common_speculative_impl_draft_mtp : public common_speculative_impl {
common_params_speculative_draft params; // reuses the draft-model params slot (ctx_tgt/ctx_dft)
common_batch batch;
llama_batch batch;
std::vector<common_sampler_ptr> smpls;
std::vector<common_params_sampling> smpls_cfg;
// backend sampler chain per seq, attached to ctx_dft
std::vector<llama_sampler *> backend_chains;
@@ -1448,7 +1384,11 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
ctx_dft ? "yes" : "no",
common_speculative_get_devices_str(this->params.devices).c_str());
batch = common_batch(ctx_dft);
const int32_t n_b = (int32_t) llama_n_batch(ctx_dft);
batch = llama_batch_init(/*n_tokens=*/ n_b, /*embd=*/ n_embd, /*n_seq_max=*/ 1);
// llama_batch_init allocates only one of token/embd; MTP needs both.
// TODO: fix, how to call without malloc
batch.token = (llama_token *) malloc(sizeof(llama_token) * n_b);
smpls.resize(n_seq);
for (auto & s : smpls) {
@@ -1513,12 +1453,15 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
llama_sampler_free(backend_chains[seq_id]);
}
backend_chains.clear();
if (batch.token != nullptr) {
free(batch.token);
batch.token = nullptr;
}
llama_batch_free(batch);
}
void begin(llama_seq_id seq_id, const llama_tokens & prompt) override {
// reset here rather than per round, or two identical requests differ
common_sampler_reset(smpls[seq_id].get());
const int32_t N = (int32_t) prompt.size();
if (N <= 0) {
return;
@@ -1530,23 +1473,23 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
if (pos_max < N - 1 && !is_mem_shared) {
SPC_WRN("ctx_dft pos_max=%d < N-1=%d - "
"process() hook may not have run on every prefill ubatch "
"(need_embd / output flag on every prompt position?). "
"(need_embd / logits=1 on every prompt position?). "
"Drafts may degrade.\n",
(int) pos_max, N - 1);
}
}
bool process(const common_batch & batch_in) override {
if (batch_in.size() <= 0) {
bool process(const llama_batch & batch_in) override {
if (batch_in.n_tokens <= 0) {
return true;
}
// TODO: how to make it work with vision tokens?
if (!batch_in.has_token() || batch_in.has_embd()) {
if (batch_in.token == nullptr || batch_in.embd != nullptr) {
return true;
}
const int32_t n_tokens = batch_in.size();
const int32_t n_tokens = batch_in.n_tokens;
// remember the first and last batch index for each sequence
std::fill(i_batch_beg.begin(), i_batch_beg.end(), -1);
@@ -1554,7 +1497,9 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
for (int k = 0; k < n_tokens; ++k) {
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
if (batch_in.tokens[k].seq_id == seq_id) {
GGML_ASSERT(batch_in.n_seq_id[k] == 1);
if (batch_in.seq_id[k][0] == seq_id) {
i_batch_end[seq_id] = k;
if (i_batch_beg[seq_id] < 0) {
i_batch_beg[seq_id] = k;
@@ -1570,26 +1515,33 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
// if kv is shared with target (e.g Gemma4), then we can skip this catch-up decode
if (!is_mem_shared) {
batch.clear();
common_batch_clear(batch);
// pair each token with the tgt embedding shifted right by one position, and
// the first token of each sequence with the pending embedding from a previous run
for (int k = 0; k < n_tokens; ++k) {
common_batch_add(batch, batch_in.token[k], batch_in.pos[k], { batch_in.seq_id[k][0] }, 0);
}
// shift the tgt embeddings to the right by one position
// assumes that the tokens in the batch are sequential for each sequence
// i.e. we cannot have seq_id like this: [0, 0, 0, 1, 1, 0, 1, 1]
// ^--- this is a problem
// TODO:this is generally true, but would be nice to assert it
const float * h_tgt = llama_get_embeddings_nextn(ctx_tgt);
{
const float * h_tgt = llama_get_embeddings_nextn(ctx_tgt);
std::memcpy(batch.embd + (size_t) 1 * n_embd, h_tgt, row_bytes * (n_tokens-1));
}
for (int k = 0; k < n_tokens; ++k) {
const llama_seq_id seq_id = batch_in.tokens[k].seq_id;
// fill the pending embeddings from a previous run
auto set_h = [&](int idx, const float * h_row) {
std::memcpy(batch.embd + (size_t) idx * n_embd, h_row, row_bytes);
};
const int32_t idx = batch.add(batch_in.tokens[k].id, batch_in.tokens[k].pos[0], seq_id, false);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
if (i_batch_beg[seq_id] < 0) {
continue;
}
const float * h_row = k == i_batch_beg[seq_id]
? pending_h[seq_id].data()
: h_tgt + (size_t) (k - 1) * n_embd;
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
set_h(i_batch_beg[seq_id], pending_h[seq_id].data());
}
auto * mem_dft = llama_get_memory(ctx_dft);
@@ -1602,15 +1554,15 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
if (i_batch_beg[seq_id] < 0) {
continue;
}
llama_memory_seq_rm(mem_dft, seq_id, batch_in.tokens[i_batch_beg[seq_id]].pos[0], -1);
llama_memory_seq_rm(mem_dft, seq_id, batch_in.pos[i_batch_beg[seq_id]], -1);
}
llama_set_nextn_layer_offset(ctx_dft, head);
}
const int32_t rc = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
const int32_t rc = llama_decode(ctx_dft, batch);
if (rc != 0) {
SPC_ERR("llama_process(ctx_dft) head=%d failed rc=%d (pos=%d)\n",
head, (int) rc, (int) batch_in.tokens[0].pos[0]);
SPC_ERR("llama_decode(ctx_dft) head=%d failed rc=%d (pos=%d)\n",
head, (int) rc, (int) batch_in.pos[0]);
ok = false;
break;
}
@@ -1648,12 +1600,14 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
void draft(common_speculative_draft_params_vec & dparams) override {
auto & ctx_dft = params.ctx_dft;
batch.clear();
common_batch_clear(batch);
// keep track of which sequences are still drafting
int n_drafting = 0;
std::vector<bool> drafting(n_seq);
const size_t row_bytes = (size_t) n_embd * sizeof(float);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
auto & dp = dparams[seq_id];
@@ -1663,25 +1617,12 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
n_drafting++;
drafting[seq_id] = true;
// greedy drafting leaves no candidates behind, so the verifier falls back to sample-and-match
if (!params.probabilistic) {
dp.result_q = nullptr;
}
common_sampler_reset(smpls[seq_id].get());
// result_q is only set when the caller wants rejection, so it also gates the retune
if (dp.result_q) {
spec_retune(smpls, smpls_cfg, llama_get_model(ctx_dft), seq_id, dp.temp, dp.seed);
}
common_batch_add(batch, dp.id_last, dp.pos0, { seq_id }, true);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd, pending_h[seq_id].data(), row_bytes);
// a reset reseeds the chain, which breaks probabilistic drafting
if (!dp.result_q) {
common_sampler_reset(smpls[seq_id].get());
}
const int32_t idx = batch.add(dp.id_last, dp.pos0, seq_id, true);
batch.set_embd(idx, { pending_h[seq_id].data(), 1, (size_t) n_embd });
i_last[seq_id] = idx;
i_last[seq_id] = batch.n_tokens - 1;
if (chain_heads) {
chain_h[seq_id].assign(pending_h[seq_id].begin(), pending_h[seq_id].end());
@@ -1707,16 +1648,16 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
llama_set_nextn_layer_offset(ctx_dft, i);
}
int ret = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch.get());
int ret = llama_decode(ctx_dft, batch);
if (ret != 0) {
SPC_ERR("llama_process[%d] returned %d\n", i, ret);
SPC_ERR("llama_decode[%d] returned %d\n", i, ret);
break;
}
// rebuild the batch for the next step: the growing-KV paths re-add only the
// new token (the KV already holds the prefix), while chained heads re-add the
// whole prefix at the next head. dropped sequences are simply not re-added.
batch.clear();
common_batch_clear(batch);
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
if (!drafting[seq_id]) {
@@ -1725,7 +1666,7 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
auto * smpl = smpls[seq_id].get();
const llama_token id_sampled = common_sampler_sample(smpl, ctx_dft, i_last[seq_id], true);
common_sampler_sample(smpl, ctx_dft, i_last[seq_id], true);
const float * h_row = llama_get_embeddings_nextn_ith(ctx_dft, i_last[seq_id]);
const auto * cur_p = common_sampler_get_candidates(smpl, true);
@@ -1737,7 +1678,7 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
}
// add drafted token for each sequence
const llama_token id = dparams.at(seq_id).result_q ? id_sampled : cur_p->data[0].id;
const llama_token id = cur_p->data[0].id;
// only collect very high-confidence draft tokens
if (cur_p->data[0].p < params.p_min) {
@@ -1754,10 +1695,6 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
result.push_back(id);
if (dp.result_q) {
dp.result_q->emplace_back(cur_p->data, cur_p->data + cur_p->size);
}
if (params.n_max <= (int) result.size()) {
drafting[seq_id] = false;
n_drafting--;
@@ -1771,24 +1708,24 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
const int n_rows = (int) result.size() + 1; // id_last + tokens drafted so far
for (int t = 0; t < n_rows; ++t) {
const llama_token tok = (t == 0) ? dp.id_last : result[t - 1];
const int32_t idx = batch.add(tok, dp.pos0 + t, seq_id, t == n_rows - 1);
batch.set_embd(idx, { chain_h[seq_id].data() + (size_t) t * n_embd, 1, (size_t) n_embd });
i_last[seq_id] = idx;
common_batch_add(batch, tok, dp.pos0 + t, { seq_id }, t == n_rows - 1);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd,
chain_h[seq_id].data() + (size_t) t * n_embd, row_bytes);
}
} else if (is_mem_shared) {
// note: with shared memory (e.g. Gemma4 assistants) we use the same position for all draft tokens
// ref: https://github.com/huggingface/transformers/blob/effde20942e3f82a1b97449f60b3a48c5ff96145/docs/source/en/model_doc/gemma4_assistant.md?plain=1#L36-L37
const int32_t idx = batch.add(id, dp.pos0, seq_id, true);
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
i_last[seq_id] = idx;
common_batch_add(batch, id, dp.pos0, { seq_id }, true);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd, h_row, row_bytes);
} else {
const int32_t idx = batch.add(id, dp.pos0 + i + 1, seq_id, true);
batch.set_embd(idx, { h_row, 1, (size_t) n_embd });
i_last[seq_id] = idx;
common_batch_add(batch, id, dp.pos0 + i + 1, { seq_id }, true);
std::memcpy(batch.embd + (size_t) (batch.n_tokens - 1) * n_embd, h_row, row_bytes);
}
i_last[seq_id] = batch.n_tokens - 1;
}
if (batch.size() == 0) {
if (batch.n_tokens == 0) {
break;
}
@@ -1850,7 +1787,7 @@ struct common_speculative_impl_ngram_simple : public common_speculative_impl {
// noop
}
bool process(const common_batch & /*batch*/) override {
bool process(const llama_batch & /*batch*/) override {
// TODO: implement
return true;
}
@@ -1898,7 +1835,7 @@ struct common_speculative_impl_ngram_map_k : public common_speculative_impl {
common_ngram_map_begin(config[seq_id], prompt);
}
bool process(const common_batch & /*batch*/) override {
bool process(const llama_batch & /*batch*/) override {
// TODO: implement
return true;
}
@@ -2056,7 +1993,7 @@ struct common_speculative_impl_ngram_mod : public common_speculative_impl {
sinfo.n_draft_last = result.size();
}
bool process(const common_batch & /*batch*/) override {
bool process(const llama_batch & /*batch*/) override {
// TODO: implement
return true;
}
@@ -2218,7 +2155,7 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
}
}
bool process(const common_batch & /*batch*/) override {
bool process(const llama_batch & /*batch*/) override {
// TODO: implement
return true;
}
@@ -2561,9 +2498,6 @@ common_params common_base_params_to_speculative(const common_params & params) {
result.n_outputs_max = params.n_parallel;
result.n_outputs_max_per_seq = 1;
// the MoE cache is only used by the target context
result.moe_cache_size = 0;
// dflash/dspark decode the whole noise block in a single pass and sample every block position on the backend
// TODO: refactor such properties to be announced by the speculative types
// something like `struct common_speculative_type_props common_speculative_type_get_props(...);`
@@ -2623,7 +2557,7 @@ common_speculative_init_result::common_speculative_init_result(
model_path = params.speculative.draft.mparams.path;
LOG_INF("%s: loading draft model '%s'\n", __func__, model_path.c_str());
llama_model * model_dft = llama_model_load_from_file(model_path.c_str(), mparams);
llama_model * model_dft = llama_model_load_from_file(params.model.path.c_str(), mparams);
if (model_dft == NULL) {
LOG_ERR("%s: failed to load draft model, '%s'\n", __func__, model_path.c_str());
return;
@@ -2854,7 +2788,7 @@ void common_speculative_begin(common_speculative * spec, llama_seq_id seq_id, co
}
}
bool common_speculative_process(common_speculative * spec, const common_batch & batch) {
bool common_speculative_process(common_speculative * spec, const llama_batch & batch) {
bool result = true;
if (spec == nullptr) {
@@ -2917,11 +2851,6 @@ void common_speculative_draft(common_speculative * spec) {
if (!result.empty() && (int) result.size() > dp.n_max) {
SPC_DBG("truncating draft to %d tokens\n", dp.n_max);
result.resize(dp.n_max);
// trim the candidates only if the drafter produced them (n-gram drafters do not)
if (dp.result_q && !dp.result_q->empty()) {
dp.result_q->resize(dp.n_max);
}
}
}
+1 -11
View File
@@ -46,9 +46,6 @@ struct common_speculative_output_limits {
common_speculative_output_limits common_speculative_get_output_limits(
int32_t n_batch, int32_t n_parallel, int32_t n_draft);
// return true if the target and draft models have compatible vocabs
bool common_speculative_are_compatible(const llama_model * model_tgt, const llama_model * model_dft);
common_speculative * common_speculative_init(common_params_speculative & params, uint32_t n_seq);
void common_speculative_free(common_speculative * spec);
@@ -72,13 +69,6 @@ struct common_speculative_draft_params {
// the generated draft from the last _draft() call
llama_tokens * result;
// candidate distribution per drafted token; set it to make draft-simple and draft-mtp sample
std::vector<std::vector<llama_token_data>> * result_q = nullptr;
// the target's temp and seed, read only when the drafter samples probabilistically
float temp = 1.0f;
uint32_t seed = LLAMA_DEFAULT_SEED;
};
common_speculative_draft_params & common_speculative_get_draft_params(common_speculative * spec, llama_seq_id seq_id);
@@ -87,7 +77,7 @@ common_speculative_draft_params & common_speculative_get_draft_params(common_spe
void common_speculative_begin(common_speculative * spec, llama_seq_id seq_id, const llama_tokens & prompt);
// process the batch and update the internal state of the speculative context
bool common_speculative_process(common_speculative * spec, const common_batch & batch);
bool common_speculative_process(common_speculative * spec, const llama_batch & batch);
// generate drafts for the sequences specified with `common_speculative_get_draft_params`
void common_speculative_draft(common_speculative * spec);
-17
View File
@@ -42,7 +42,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"ChameleonForConditionalGeneration": "chameleon",
"ChatGLMForConditionalGeneration": "chatglm",
"ChatGLMModel": "chatglm",
"ClefModel": "clef",
"CodeShellForCausalLM": "codeshell",
"CogVLMForCausalLM": "cogvlm",
"Cohere2MoeForCausalLM": "command_r",
@@ -50,7 +49,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"CohereForCausalLM": "command_r",
"DbrxForCausalLM": "dbrx",
"DeciLMForCausalLM": "deci",
"PplxDeciderModel": "pplx_decider",
"DeepseekForCausalLM": "deepseek",
"DeepseekOCRForCausalLM": "deepseek",
"DeepseekV2ForCausalLM": "deepseek",
@@ -74,7 +72,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"Dots3NoteTextForCausalLM": "dots3",
"DotsOCRForCausalLM": "qwen",
"DreamModel": "dream",
"EmbeddingGemma2Model": "gemma",
"Ernie4_5ForCausalLM": "ernie",
"Ernie4_5_ForCausalLM": "ernie",
"Ernie4_5_MoeForCausalLM": "ernie",
@@ -109,7 +106,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"Glm4MoeLiteForCausalLM": "glm",
"Glm4vForConditionalGeneration": "glm",
"Glm4vMoeForConditionalGeneration": "glm",
"Glm5NextForConditionalGeneration": "glm",
"GlmForCausalLM": "chatglm",
"GlmMoeDsaForCausalLM": "glm",
"GlmOcrForConditionalGeneration": "glm",
@@ -142,7 +138,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"JinaBertForMaskedLM": "bert",
"JinaBertModel": "bert",
"JinaEmbeddingsV5Model": "bert",
"K2HorizonForCausalLM": "k2_horizon",
"KORMoForCausalLM": "qwen",
"KimiK25ForConditionalGeneration": "deepseek",
"KimiK3ForConditionalGeneration": "kimi_k3",
@@ -154,11 +149,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"LLaDAMoEModelLM": "llada",
"LLaDAModelLM": "llada",
"LLaMAForCausalLM": "llama",
"KevModel": "lev",
"LevModel": "lev",
"NimbleModel": "lev",
"Lfm25AudioTokenizer": "lfm2",
"Lfm2BidirectionalForMaskedLM": "lfm2",
"Lfm2BidirectionalModel": "lfm2",
"Lfm2ForCausalLM": "lfm2",
"Lfm2Model": "lfm2",
@@ -196,7 +187,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"Mistral3ForConditionalGeneration": "mistral3",
"MistralForCausalLM": "llama",
"MixtralForCausalLM": "llama",
"ModernBertDecisionModel": "bert",
"ModernBertForMaskedLM": "bert",
"ModernBertForSequenceClassification": "bert",
"ModernBertModel": "bert",
@@ -216,7 +206,6 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MuseGlimmerAssistantModel": "muse_glimmer",
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
"OpenELMForCausalLM": "openelm",
"OpenJevModel": "qwen",
"OrionForCausalLM": "orion",
"PLMForCausalLM": "plm",
"PLaMo2ForCausalLM": "plamo",
@@ -301,17 +290,13 @@ TEXT_MODEL_MAP: dict[str, str] = {
MMPROJ_MODEL_MAP: dict[str, str] = {
"AudioFlamingo3ForConditionalGeneration": "ultravox",
"ClefModel": "clef",
"CogVLMForCausalLM": "cogvlm",
"Cohere2VisionForConditionalGeneration": "command_r",
"PplxDeciderModel": "pplx_decider",
"DeepseekOCR2ForCausalLM": "deepseek",
"DeepseekOCRForCausalLM": "deepseek",
"DeepseekV4ForCausalLM": "deepseek",
"Dots3NoteForCausalLM": "dots3",
"Dots3NoteForConditionalGeneration": "dots3",
"DotsOCRForCausalLM": "dotsocr",
"EmbeddingGemma2Model": "gemma",
"Exaone4_5_ForConditionalGeneration": "exaone",
"Gemma3ForConditionalGeneration": "gemma",
"Gemma3nForConditionalGeneration": "gemma",
@@ -320,7 +305,6 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Glm4vForConditionalGeneration": "qwen3vl",
"BailingMoeV3VLForConditionalGeneration": "bailingmoe3",
"Glm4vMoeForConditionalGeneration": "qwen3vl",
"Glm5NextForConditionalGeneration": "qwen3vl",
"Glm5vForConditionalGeneration": "kimivl",
"GlmOcrForConditionalGeneration": "qwen3vl",
"GlmasrModel": "ultravox",
@@ -358,7 +342,6 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Qwen3TTSForConditionalGeneration": "qwen3tts",
"Qwen3VLForConditionalGeneration": "qwen3vl",
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
"OpenJevModel": "qwen3vl",
"Qwen3_5ForConditionalGeneration": "qwen3vl",
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
+14 -55
View File
@@ -234,7 +234,7 @@ class ModelBase:
prefix = "model" if not self.is_mistral_format else "consolidated"
part_names: list[str] = ModelBase.get_model_part_names(self.dir_model, prefix, ".safetensors")
is_safetensors: bool = len(part_names) > 0 or (not self.is_mistral_format and (self.dir_model / "model.safetensors.index.json").is_file())
is_safetensors: bool = len(part_names) > 0
if not is_safetensors:
part_names = ModelBase.get_model_part_names(self.dir_model, "pytorch_model", ".bin")
@@ -1268,24 +1268,22 @@ class ModelBase:
return inner
@staticmethod
def load_hparams(dir_model: Path, is_mistral_format: bool, guess: bool = True):
def load_hparams(dir_model: Path, is_mistral_format: bool):
if is_mistral_format:
with open(dir_model / "params.json", "r", encoding="utf-8") as f:
config = json.load(f)
return config
# checkpoints with a non-HF layout are matched by their own loader
# models with a HF layout can also register a hparams loader to switch to a custom class
config = ModelBase.load_hparams_guess(dir_model) if guess and dir_model.is_dir() else None
if config is not None:
return config
try:
# for security reason, we don't allow loading remote code by default
# if a model need remote code, we will fallback to config.json
config = AutoConfig.from_pretrained(dir_model, trust_remote_code=False).to_dict()
except Exception as e:
logger.warning(f"Failed to load model config from {dir_model}: {e}")
if not (dir_model / "config.json").is_file():
config = ModelBase.load_hparams_guess(dir_model)
if config is not None:
return config
logger.warning("Trying to load config.json instead")
with open(dir_model / "config.json", "r", encoding="utf-8") as f:
config = json.load(f)
@@ -1529,7 +1527,7 @@ class TextModel(ModelBase):
self.gguf_writer.add_expert_group_used_count(n_group_used)
logger.info(f"gguf: expert groups used count = {n_group_used}")
if (score_func := self.find_hparam(["score_function", "scoring_func", "score_func", "moe_router_activation", "moe_router_activation_func", "expert_selection_fn", "router_score_func"], optional=True)) is not None:
if (score_func := self.find_hparam(["score_function", "scoring_func", "score_func", "moe_router_activation", "moe_router_activation_func", "expert_selection_fn"], optional=True)) is not None:
if score_func == "sigmoid":
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
elif score_func == "softmax":
@@ -1713,9 +1711,6 @@ class TextModel(ModelBase):
if chkhsh == "0a766d034107bc736a3f2dc4968fd62e54a3570f1454443e0c5a4cc6bd7941ed":
# ref: https://huggingface.co/XHToken/Spark-X2.5-1.7B
res = "spark2_5"
if chkhsh == "1f9825a388f700a6b591722f17d470cbbcf10973ece35d2fd14239a14110ae1a":
# ref: https://huggingface.co/IFM/K2-Horizon-0.9B
res = "k2-horizon"
if chkhsh == "0ef9807a4087ebef797fc749390439009c3b9eda9ad1a097abbe738f486c01e5":
# ref: https://huggingface.co/meta-llama/Meta-Llama-3-8B
res = "llama-bpe"
@@ -1941,12 +1936,6 @@ class TextModel(ModelBase):
if chkhsh == "653660222fb704f61cbf2b618a8ae6502b7f8b20c980f9a5de07ed78e13319cd":
# ref: https://huggingface.co/ufakai/ufakzeka-1
res = "ufakzeka"
if chkhsh == "4b05e02dad1c5ae07d266fd3342ddb644c6f6be058d728bc0a33af31a1d6ee66":
# ref: https://huggingface.co/jhu-clsp/mmBERT-base
res = "mmbert"
if chkhsh == "a9af07a84191f55098b248ae6f3dfe9e32d3190bebe8eafd91c1ddec9bc3449f":
# ref: https://huggingface.co/IFM/K2-Horizon-36B
res = "k2-horizon"
if res is None:
logger.warning("\n")
@@ -2337,12 +2326,6 @@ class TextModel(ModelBase):
raise NotImplementedError("Only MEAN, CLS, and LAST pooling types supported")
self.gguf_writer.add_pooling_type(pooling_type)
# pooling before a classification head (e.g. ModernBertForSequenceClassification)
if (classifier_pooling := self.hparams.get("classifier_pooling")) is not None:
if classifier_pooling not in ("cls", "mean"):
raise NotImplementedError(f"Unsupported classifier_pooling: {classifier_pooling}")
self.gguf_writer.add_classifier_pooling_type(mode_mapping[classifier_pooling])
def _set_vocab_glmedge(self):
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
@@ -2499,11 +2482,7 @@ class TextModel(ModelBase):
if template is not None:
self.gguf_writer.add_chat_template(template)
def _set_vocab_plamo(
self,
eot_token: str,
normal_tokens: Iterable[str] = (),
):
def _set_vocab_plamo(self):
# PLaMo models use a custom tokenizer with a .jsonl file
tokenizer_jsonl_path = self.dir_model / "tokenizer.jsonl"
tokenizer_config_path = self.dir_model / "tokenizer_config.json"
@@ -2515,42 +2494,31 @@ class TextModel(ModelBase):
with open(tokenizer_config_path, "r", encoding="utf-8") as f:
tokenizer_config = json.load(f)
tokenizer_class = tokenizer_config.get("tokenizer_class")
if tokenizer_class == "Plamo2Tokenizer":
tokenizer_model = "plamo2"
elif tokenizer_class == "Plamo3Tokenizer":
tokenizer_model = "plamo3"
else:
raise ValueError(f"Unsupported PLaMo tokenizer class: {tokenizer_class}")
# Load tokens from JSONL file (actually a list format)
tokens = []
scores = []
toktypes = []
normal_tokens = set(normal_tokens)
with open(tokenizer_jsonl_path, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f):
if line.strip():
token_data = json.loads(line)
# Format: [token, score, type, ?, ?, ?, ?]
token_str = token_data[0]
token = token_str.encode("utf-8")
token = token_data[0].encode("utf-8")
score = float(token_data[1])
token_type_str = token_data[2] if len(token_data) > 2 else "NORMAL"
tokens.append(token)
scores.append(score)
if token_str in normal_tokens:
toktypes.append(gguf.TokenType.NORMAL)
elif token_type_str == "UNKNOWN":
if token_type_str == "UNKNOWN":
toktypes.append(gguf.TokenType.UNKNOWN)
elif token_type_str == "CONTROL":
toktypes.append(gguf.TokenType.CONTROL)
elif token_type_str == "BYTE":
toktypes.append(gguf.TokenType.BYTE)
else:
token_str = token_data[0]
if token_str.startswith("<|plamo:") and token_str.endswith("|>"):
toktypes.append(gguf.TokenType.CONTROL)
else:
@@ -2565,7 +2533,7 @@ class TextModel(ModelBase):
scores.append(-1000.0)
toktypes.append(gguf.TokenType.UNUSED)
self.gguf_writer.add_tokenizer_model(tokenizer_model)
self.gguf_writer.add_tokenizer_model("plamo2")
self.gguf_writer.add_tokenizer_pre("default")
self.gguf_writer.add_token_list(tokens)
self.gguf_writer.add_token_scores(scores)
@@ -2587,15 +2555,11 @@ class TextModel(ModelBase):
token_id = tokens.index(tokenizer_config["unk_token"].encode("utf-8"))
self.gguf_writer.add_unk_token_id(token_id)
self.gguf_writer.add_eot_token_id(tokens.index(eot_token.encode("utf-8")))
# Add <|plamo:op|> as EOT to ensure appropriate end of generation
self.gguf_writer.add_eot_token_id(4)
self.gguf_writer.add_add_space_prefix(False)
if (add_bos := tokenizer_config.get("add_bos_token")) is not None:
self.gguf_writer.add_add_bos_token(add_bos)
if (add_eos := tokenizer_config.get("add_eos_token")) is not None:
self.gguf_writer.add_add_eos_token(add_eos)
class MmprojModel(ModelBase):
model_type = ModelType.MMPROJ
@@ -2905,11 +2869,6 @@ else:
LazyTorchTensor._dtype_str_map["F8_E8M0"] = torch.uint8
def jinja_str_or_json(name: str) -> str:
# jinja expression that renders a variable as-is if it is a string, as JSON otherwise
return "{{ " + name + " if " + name + " is string else " + name + " | tojson }}"
def get_model_architecture(hparams: dict[str, Any], model_type: ModelType) -> str:
# TODO @ngxson : this won't work correctly if the model has both audio & vision encoders
# maybe we should fallback to text model's arch in that case, since not many models have both
+1 -107
View File
@@ -11,7 +11,7 @@ import torch
if TYPE_CHECKING:
from torch import Tensor
from .base import ModelBase, SentencePieceTokenTypes, TextModel, gguf, jinja_str_or_json, logger
from .base import ModelBase, SentencePieceTokenTypes, TextModel, gguf, logger
@ModelBase.register("BertModel", "BertForMaskedLM", "CamembertModel", "BertForSequenceClassification")
@@ -606,17 +606,6 @@ class ModernBertModel(BertModel):
self.gguf_writer.add_add_sep_token(True)
self._set_vocab_gpt2()
def get_vocab_base(self) -> tuple[list[str], list[int], str]:
tokens, toktypes, tokpre = super().get_vocab_base()
if tokpre == "mmbert":
# the added tokens for runs of spaces are never matched by the reference tokenizer
space = b"\xe2\x96\x81".decode("utf-8")
for i, token in enumerate(tokens):
if toktypes[i] == gguf.TokenType.USER_DEFINED and token and not token.strip(" "):
tokens[i] = space * len(token)
toktypes[i] = gguf.TokenType.NORMAL
return tokens, toktypes, tokpre
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_sliding_window(self.hparams["local_attention"])
@@ -650,98 +639,3 @@ class ModernBertModel(BertModel):
name = "classifier.out_proj.bias"
yield from super().modify_tensors(data_torch, name, bid)
def _is_decision_checkpoint(dir_model: Path) -> bool:
if not (dir_model / "encoder" / "config.json").is_file():
return False
return (dir_model / "rl_agent_config.json").is_file() or (dir_model / "julia_config.json").is_file()
@ModelBase.register_hparams_loader(_is_decision_checkpoint)
def _load_decision_hparams(dir_model: Path) -> dict[str, Any]:
logger.info("gguf: detected ModernBert decision checkpoint")
hparams = ModelBase.load_hparams(dir_model / "encoder", False, guess=False)
is_julia = (dir_model / "julia_config.json").is_file()
with open(dir_model / ("julia_config.json" if is_julia else "rl_agent_config.json"), encoding="utf-8") as f:
decision = json.load(f)
n_layer = hparams["num_hidden_layers"]
n_layer_head = decision["head_layers"]
hparams["architectures"] = ["ModernBertDecisionModel"]
hparams["decision"] = decision
# the head blocks are appended to the encoder blocks, they use a plain 4x MLP
hparams["num_hidden_layers"] = n_layer + n_layer_head
hparams["intermediate_size"] = [hparams["intermediate_size"]] * n_layer + [4 * hparams["hidden_size"]] * n_layer_head
return hparams
@ModelBase.register("ModernBertDecisionModel")
@ModelBase.example("convaiinnovations/laya", "SupersonicLabs/Julia-1")
class ModernBertDecisionModel(ModernBertModel):
model_arch = gguf.MODEL_ARCH.MODERN_BERT
def set_vocab(self):
# vocab loaders read self.dir_model, point it to the tokenizer sub-directory
dir_model = self.dir_model
self.dir_model = dir_model / "tokenizer"
try:
super().set_vocab()
finally:
self.dir_model = dir_model
self.gguf_writer.add_token_type_count(3) # choice, score, noul
self.gguf_writer.add_chat_template([{"name": "systemone", "template": self._systemone_template()}])
def _systemone_template(self) -> str:
with open(self.dir_model / "tokenizer" / "tokenizer_config.json", encoding="utf-8") as f:
tokenizer_config = json.load(f)
tok_cls, tok_sep, tok_mask = (tokenizer_config[k] for k in ("cls_token", "sep_token", "mask_token"))
description = jinja_str_or_json("o.description")
if self.hparams["decision"].get("architecture") == "JuliaDecisionModel":
option = "{% if o.description %}" + description + "{% else %}{{ o.key }}{% endif %}"
else:
option = (
"{% if type == 'choice' %}{{ o.key }}{% if o.description %}: " + description + "{% endif %}"
"{% elif type == 'score' %}level {{ o.key }}: " + description
+ "{% else %}{{ o.key }}: {% if o.description %}" + description
+ "{% elif o.key == 'true' %}yes, the statement holds"
"{% else %}no, the statement does not hold{% endif %}{% endif %}"
)
return (
tok_cls + "{{ type }} question: " + jinja_str_or_json("instructions") + tok_sep
+ "{% for o in options %}" + tok_mask + " " + option + "{% endfor %}"
+ tok_sep + jinja_str_or_json("state") + tok_sep
)
def set_gguf_parameters(self):
super().set_gguf_parameters()
decision = self.hparams["decision"]
self.gguf_writer.add_decision_type(gguf.DecisionType.LAYA)
self.gguf_writer.add_decision_block_count(decision["head_layers"])
self.gguf_writer.add_decision_max_head_tokens(decision.get("head_max_len", 256))
for name, value in zip(("choice", "score", "noul"), decision.get("temperature", [])):
self.gguf_writer.add_decision_temperature(name, value)
# "choice:3-5" -> "choice.3_5", "choice:11+" -> "choice.11"
for name, value in decision.get("temperature_by_options", {}).items():
self.gguf_writer.add_decision_temperature(name.replace(":", ".").replace("-", "_").rstrip("+"), value)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
name, gen = item
# act_head is not used for the answer, the fitted temperatures come from the config
if name.startswith("act_head.") or name == "temperature":
return None
if name.startswith("encoder."):
name = name[8:]
return super().filter_tensors((name, gen))
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.startswith("head.layers.") and bid is not None:
# the head blocks come after the encoder blocks
suffix = name.split(".", 3)[3].replace("in_proj_", "in_proj.")
bid += self.block_count - self.hparams["decision"]["head_layers"]
name = f"head.layers.{bid}.{suffix}"
yield from super().modify_tensors(data_torch, name, bid)

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