mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-29 07:57:38 -05:00
Compare commits
164
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf48103ad1 | ||
|
|
6d5ac3c8d5 | ||
|
|
7ac5727d60 | ||
|
|
2e0d86bebc | ||
|
|
e09a78a25d | ||
|
|
369dcf309c | ||
|
|
d0ff6317cc | ||
|
|
cf24317bea | ||
|
|
74a4fb0037 | ||
|
|
5220213255 | ||
|
|
c3df4b7e22 | ||
|
|
ac8e1694d8 | ||
|
|
c02b5c641e | ||
|
|
ff0d396e3d | ||
|
|
838093f192 | ||
|
|
604cc43023 | ||
|
|
39febd2adb | ||
|
|
f5222f25c1 | ||
|
|
28cb47c7c8 | ||
|
|
8642541b25 | ||
|
|
d4f1b18b3a | ||
|
|
d1eb0debad | ||
|
|
5d7793d6fc | ||
|
|
ec941ee852 | ||
|
|
e486a4b559 | ||
|
|
f1a7dea99a | ||
|
|
a7af149a2a | ||
|
|
2864937a77 | ||
|
|
a71c00f0bb | ||
|
|
f4d8af608a | ||
|
|
02d502f314 | ||
|
|
b1756b23f3 | ||
|
|
6886628cb1 | ||
|
|
8ca4060b7e | ||
|
|
d05706b4f3 | ||
|
|
1b4554eff1 | ||
|
|
a919179942 | ||
|
|
338fce6511 | ||
|
|
19ff817fb2 | ||
|
|
eb7818c0b8 | ||
|
|
6c44591049 | ||
|
|
19cce69199 | ||
|
|
e86f5fd1e9 | ||
|
|
27b7a8a009 | ||
|
|
7ecb57f7ad | ||
|
|
8542e94350 | ||
|
|
922f53ed33 | ||
|
|
0c8c59ef3d | ||
|
|
3ab37aedfe | ||
|
|
85cc432e2c | ||
|
|
74a1fd3d03 | ||
|
|
7eed6b7685 | ||
|
|
276378515c | ||
|
|
d78f40d9d3 | ||
|
|
fae9ac561f | ||
|
|
a8276b70cc | ||
|
|
de5122d879 | ||
|
|
6666a7330c | ||
|
|
7a2e5aba90 | ||
|
|
0d5ca60098 | ||
|
|
d930fe3c26 | ||
|
|
a2b3e121ce | ||
|
|
823df7d0fb | ||
|
|
1a97e387d3 | ||
|
|
bc262f33cc | ||
|
|
f5892ca5ad | ||
|
|
7aca0d2ea8 | ||
|
|
961c376a2d | ||
|
|
4244ad7acd | ||
|
|
63b4b303bd | ||
|
|
caa8191df1 | ||
|
|
a4699400e3 | ||
|
|
4b851fbee8 | ||
|
|
4d71b42fd8 | ||
|
|
70c5f26324 | ||
|
|
5cd62ffee9 | ||
|
|
38645c2ad9 | ||
|
|
02269b4c00 | ||
|
|
5320c87ff0 | ||
|
|
74b104c9ba | ||
|
|
c6fb3b5be2 | ||
|
|
8b28c23943 | ||
|
|
fd8f8451a2 | ||
|
|
9125a2ebe8 | ||
|
|
9b8446b914 | ||
|
|
dc9e3bed42 | ||
|
|
69b3cc406e | ||
|
|
224d94c02b | ||
|
|
48c36110ab | ||
|
|
a074215352 | ||
|
|
d854d98bae | ||
|
|
48c9f08b7b | ||
|
|
6050b467c0 | ||
|
|
2280f0fd16 | ||
|
|
1a0b56cec7 | ||
|
|
63412d8737 | ||
|
|
5c965fd8ca | ||
|
|
f1168ac417 | ||
|
|
fa3645feb5 | ||
|
|
7790a9231d | ||
|
|
c49edf0963 | ||
|
|
a55c16e4ee | ||
|
|
e321bdad7e | ||
|
|
459a1ba549 | ||
|
|
f0fb5984b0 | ||
|
|
9a4580b61c | ||
|
|
b083c384f7 | ||
|
|
344be0a600 | ||
|
|
b3fa69955e | ||
|
|
74bca39500 | ||
|
|
d327bf1f07 | ||
|
|
78a8ae14be | ||
|
|
f3269ef96a | ||
|
|
3f03b17724 | ||
|
|
9a0fde9967 | ||
|
|
e168e041ec | ||
|
|
ea1d7d66f3 | ||
|
|
71255fefc0 | ||
|
|
73adf89e38 | ||
|
|
df9beac715 | ||
|
|
4d32b29aaa | ||
|
|
3032209758 | ||
|
|
701fd25b3a | ||
|
|
3642238cc1 | ||
|
|
6b9ba4f2e8 | ||
|
|
8e5a58df87 | ||
|
|
1ddffe9f70 | ||
|
|
7894e8b924 | ||
|
|
edc353842c | ||
|
|
9ae73e68f5 | ||
|
|
0ae0e19766 | ||
|
|
21ec307146 | ||
|
|
9145ddb955 | ||
|
|
1a24df1639 | ||
|
|
9ff0d7e90f | ||
|
|
06230cb877 | ||
|
|
5c31142682 | ||
|
|
da2ee1e7a6 | ||
|
|
280141e6d4 | ||
|
|
36e36e63a5 | ||
|
|
e1e3b2c409 | ||
|
|
4231b23dc2 | ||
|
|
f00c9083b2 | ||
|
|
f5027fb213 | ||
|
|
f64a49819c | ||
|
|
1ab09d632e | ||
|
|
f9599d0cc8 | ||
|
|
643b0cdacb | ||
|
|
0a9ef00678 | ||
|
|
7377bada8c | ||
|
|
184f653602 | ||
|
|
d814cbb88c | ||
|
|
970b51ce7c | ||
|
|
a08ab75ae7 | ||
|
|
19ab5a5e65 | ||
|
|
36cbf415f1 | ||
|
|
5c19a5a756 | ||
|
|
d9ee007a06 | ||
|
|
d81d0d8fb9 | ||
|
|
d77256999f | ||
|
|
0693dbfb91 | ||
|
|
a5d22276be | ||
|
|
5f5acfe250 | ||
|
|
1f4c2eeaa5 |
@@ -0,0 +1,158 @@
|
||||
name: Behavior Check
|
||||
|
||||
on:
|
||||
workflow_dispatch: # allows manual triggering
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths: [
|
||||
'.github/workflows/behavior-check.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
'**/*.hpp',
|
||||
'**/*.c',
|
||||
'**/*.cpp',
|
||||
]
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths: [
|
||||
'.github/workflows/behavior-check.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
'**/*.hpp',
|
||||
'**/*.c',
|
||||
'**/*.cpp'
|
||||
]
|
||||
|
||||
cache-mode: none
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GGML_NLOOP: 3
|
||||
GGML_N_THREADS: 1
|
||||
GGML_SCHED_DEBUG_REALLOC: 1
|
||||
LLAMA_ARG_LOG_COLORS: 1
|
||||
LLAMA_ARG_LOG_PREFIX: 1
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: Build Dependencies
|
||||
id: build_depends
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential libssl-dev git-lfs libjpeg-dev python3 python3-venv python3-pip
|
||||
|
||||
- name: Python Dependencies
|
||||
id: python_depends
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install jinja2==3.1.6
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: behavior-check-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
cmake -B build \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_BACKEND_DL=OFF \
|
||||
-DLLAMA_FATAL_WARNINGS=ON
|
||||
time cmake --build build --config Release -j $(nproc)
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: behavior-check-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
cd build
|
||||
ctest -L behavior -LE 'model|backend' --verbose --timeout 900
|
||||
|
||||
windows:
|
||||
runs-on: windows-2025
|
||||
|
||||
cache-mode: write
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
key: behavior-check-windows-2025
|
||||
evict-old-files: 1d
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
|
||||
- name: Python Dependencies
|
||||
id: python_depends
|
||||
shell: bash
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
source .venv/Scripts/activate
|
||||
pip install jinja2==3.1.6
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
cmake -B build -G "Ninja Multi-Config" ^
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake ^
|
||||
-DCMAKE_BUILD_TYPE=Release ^
|
||||
-DGGML_NATIVE=OFF ^
|
||||
-DGGML_BACKEND_DL=OFF
|
||||
set /A NINJA_JOBS=%NUMBER_OF_PROCESSORS%-1
|
||||
cmake --build build --config Release -j %NINJA_JOBS%
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
shell: bash
|
||||
run: |
|
||||
source .venv/Scripts/activate
|
||||
cd build
|
||||
ctest -C Release -L behavior -LE 'model|backend' --verbose --timeout 900
|
||||
@@ -105,63 +105,7 @@ jobs:
|
||||
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-llama-context" --verbose --timeout 900
|
||||
|
||||
macos-latest-x64:
|
||||
runs-on: macos-15-intel
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: apple-x64
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
sysctl -a
|
||||
# Metal is disabled due to intermittent failures with Github runners not having a GPU:
|
||||
# https://github.com/ggml-org/llama.cpp/actions/runs/8635935781/job/23674807267#step:5:2313
|
||||
cmake -B build \
|
||||
-DCMAKE_BUILD_RPATH="@loader_path" \
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
-DLLAMA_BUILD_BORINGSSL=ON \
|
||||
-DGGML_METAL=OFF \
|
||||
-DGGML_RPC=ON \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.3
|
||||
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: apple-x64
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
run: |
|
||||
cd build
|
||||
ctest -L main --verbose --timeout 900
|
||||
ctest -L main -E "test-llama-archs|test-llama-context|test-speculative-rejection-sampling" --verbose --timeout 900
|
||||
|
||||
macos-latest-ios-xcode:
|
||||
runs-on: macos-latest
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: [self-hosted, Linux, CPU]
|
||||
runs-on: [self-hosted, Linux, fast]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
id: cmake_test
|
||||
run: |
|
||||
cd build
|
||||
ctest -L 'main|python' --verbose --timeout 900
|
||||
ctest -L main --verbose --timeout 900
|
||||
|
||||
- name: Test llama2c conversion
|
||||
id: llama2c_test
|
||||
|
||||
@@ -43,9 +43,9 @@ env:
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
jobs:
|
||||
ubuntu-24-s390x:
|
||||
name: ubuntu-24-s390x (VXE ${{ matrix.vxe }})
|
||||
runs-on: ubuntu-24.04-s390x
|
||||
ubuntu-26-s390x:
|
||||
name: ubuntu-26-s390x (VXE ${{ matrix.vxe }})
|
||||
runs-on: ubuntu-26.04-s390x
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -66,12 +66,6 @@ jobs:
|
||||
libjpeg-dev build-essential libssl-dev \
|
||||
git-lfs
|
||||
|
||||
- name: Toolchain workaround (GCC 14)
|
||||
run: |
|
||||
sudo apt-get install -y gcc-14 g++-14
|
||||
echo "CC=gcc-14" >> "$GITHUB_ENV"
|
||||
echo "CXX=g++-14" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Python Dependencies
|
||||
id: python_depends
|
||||
run: |
|
||||
@@ -116,30 +110,19 @@ jobs:
|
||||
|
||||
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
|
||||
runs-on: ubuntu-26.04-s390x
|
||||
|
||||
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)
|
||||
- name: Build Dependencies
|
||||
id: build_depends
|
||||
run: |
|
||||
apt-get install -y gcc-15 g++-15
|
||||
echo "CC=gcc-15" >> "$GITHUB_ENV"
|
||||
echo "CXX=g++-15" >> "$GITHUB_ENV"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential libssl-dev libzdnn-dev
|
||||
|
||||
- name: Build with zDNN Backend
|
||||
id: cmake_build
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- sanitizer: THREAD
|
||||
machine: ubuntu-24.04
|
||||
- sanitizer: UNDEFINED
|
||||
machine: [self-hosted, X64, Linux]
|
||||
machine: [self-hosted, X64, Linux, CPU]
|
||||
|
||||
runs-on: ${{ matrix.machine }}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ env:
|
||||
|
||||
jobs:
|
||||
cpu-x64-high-perf:
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
runs-on: [self-hosted, Linux, X64, CPU]
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
- name: Test
|
||||
id: ggml-ci
|
||||
run: |
|
||||
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
cpu-arm64-high-perf-graviton4:
|
||||
runs-on: ah-ubuntu_24_04-c8g_8x
|
||||
|
||||
@@ -60,4 +60,4 @@ jobs:
|
||||
- name: Test
|
||||
id: ggml-ci
|
||||
run: |
|
||||
GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
# - name: ccache-buckets-save
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
# - name: ccache-buckets-save
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
gpu-vulkan-intel-linux:
|
||||
runs-on: [self-hosted, Linux, Intel]
|
||||
|
||||
@@ -58,7 +58,12 @@ jobs:
|
||||
- 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 libssl-dev time unzip wget python3 python3-venv python3-pip
|
||||
sudo apt install -y software-properties-common
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
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 libssl-dev time unzip wget python3.12 python3.12-venv
|
||||
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
|
||||
sudo update-alternatives --config python3
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
@@ -131,4 +136,4 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
GG_BUILD_WEBGPU=1 GG_BUILD_WEBGPU_DAWN_PREFIX="$GITHUB_WORKSPACE/dawn" \
|
||||
bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
@@ -172,6 +172,8 @@ jobs:
|
||||
- [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 26.04 s390x (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-26.04-s390x.tar.gz)
|
||||
- [Ubuntu 26.04 s390x (zDNN)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-zdnn-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)
|
||||
|
||||
@@ -180,6 +180,8 @@ jobs:
|
||||
os: ubuntu-24.04-arm
|
||||
- build: 's390x'
|
||||
os: ubuntu-24.04-s390x
|
||||
- build: '26.04-s390x'
|
||||
os: ubuntu-26.04-s390x
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -200,7 +202,7 @@ jobs:
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential libssl-dev
|
||||
sudo apt-get install -y build-essential libssl-dev
|
||||
|
||||
- name: Toolchain workaround (GCC 14)
|
||||
if: ${{ contains(matrix.os, 'ubuntu-24.04') }}
|
||||
@@ -210,7 +212,7 @@ jobs:
|
||||
echo "CXX=g++-14" >> "$GITHUB_ENV"
|
||||
|
||||
- name: ccache
|
||||
if: ${{ matrix.build != 's390x' }}
|
||||
if: ${{ !contains(matrix.os, 's390x') }}
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
key: release-${{ matrix.os }}-cpu
|
||||
@@ -242,7 +244,7 @@ jobs:
|
||||
archive: false
|
||||
|
||||
- name: ccache-clear
|
||||
if: ${{ matrix.build != 's390x' }}
|
||||
if: ${{ !contains(matrix.os, 's390x') }}
|
||||
uses: ./.github/actions/ccache-clear
|
||||
with:
|
||||
key: release-${{ matrix.os }}-cpu
|
||||
@@ -968,6 +970,11 @@ jobs:
|
||||
evict-old-files: 1d
|
||||
max-size: "1G"
|
||||
|
||||
- name: Tune ccache for ROCm
|
||||
run: |
|
||||
# since ROCm 10.1, clang -E output has no GPU code, so the preprocessor fallback can reuse objects with old GPU code
|
||||
ccache --set-config=depend_mode=true
|
||||
|
||||
# - name: Cache ROCm Installation
|
||||
# id: cache-rocm
|
||||
# uses: actions/cache@v5
|
||||
@@ -1545,6 +1552,8 @@ jobs:
|
||||
# builds hit.
|
||||
ccache --set-config=compiler_check=content
|
||||
ccache --set-config=sloppiness=time_macros,include_file_mtime,include_file_ctime
|
||||
# since ROCm 10.1, clang -E output has no GPU code, so the preprocessor fallback can reuse objects with old GPU code
|
||||
ccache --set-config=depend_mode=true
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
@@ -1619,6 +1628,60 @@ jobs:
|
||||
with:
|
||||
key: release-ubuntu-24.04-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
|
||||
ubuntu-26-zdnn:
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-26.04-s390x
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential libssl-dev libzdnn-dev
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DGGML_BACKEND_DL=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_VXE=ON \
|
||||
-DGGML_ZDNN=ON \
|
||||
-DCMAKE_C_FLAGS="-march=arch15" \
|
||||
-DCMAKE_CXX_FLAGS="-march=arch15" \
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
- name: Pack artifacts
|
||||
id: pack_artifacts
|
||||
run: |
|
||||
cp LICENSE ./build/bin/
|
||||
tar -czvf llama-${{ needs.check-release.outputs.tag_name }}-bin-ubuntu-zdnn-s390x.tar.gz --transform "s,^\.,llama-${{ needs.check-release.outputs.tag_name }}," -C ./build/bin .
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: llama-${{ needs.check-release.outputs.tag_name }}-bin-ubuntu-zdnn-s390x.tar.gz
|
||||
archive: false
|
||||
|
||||
ios-xcode:
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ include(CheckIncludeFileCXX)
|
||||
|
||||
### llama.cpp version
|
||||
set(LLAMA_VERSION_MAJOR 0)
|
||||
set(LLAMA_VERSION_MINOR 7)
|
||||
set(LLAMA_VERSION_MINOR 8)
|
||||
set(LLAMA_VERSION_PATCH 0)
|
||||
set(LLAMA_VERSION_BASE "${LLAMA_VERSION_MAJOR}.${LLAMA_VERSION_MINOR}.${LLAMA_VERSION_PATCH}")
|
||||
|
||||
@@ -103,7 +103,7 @@ endif()
|
||||
|
||||
# subprocess spawning isn't a supported/sandbox-friendly operation on mobile OSes or in WASM
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android" OR ANDROID
|
||||
OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR EMSCRIPTEN)
|
||||
OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "WASI")
|
||||
set(LLAMA_SUBPROCESS_DEFAULT OFF)
|
||||
else()
|
||||
set(LLAMA_SUBPROCESS_DEFAULT ON)
|
||||
|
||||
+22
-29
@@ -2,20 +2,29 @@
|
||||
# multiple collaborators per item can be specified
|
||||
#
|
||||
# ggml-org/ci : CISC, danbev, ggerganov, netrunnereve, ngxson, taronaeo
|
||||
# ggml-org/ggml-base : aendk, fairydreaming, ggerganov
|
||||
# ggml-org/ggml-cann : hipudding
|
||||
# ggml-org/ggml-cuda : JohannesGaessler, am17an, IMbackK, ORippler
|
||||
# ggml-org/ggml-cpu : angt, chaxu01, ggerganov, max-krasnyansky, taronaeo
|
||||
# ggml-org/ggml-cuda : am17an, gaugarg-nv, IMbackK, JohannesGaessler, ORippler, thelittlefireman, ynankani
|
||||
# ggml-org/ggml-et : marty1885
|
||||
# ggml-org/ggml-hexagon : lhez, max-krasnyansky
|
||||
# ggml-org/ggml-metal : ggerganov
|
||||
# ggml-org/ggml-hip : IMbackK, thelittlefireman
|
||||
# ggml-org/ggml-metal : forforever73, ggerganov, jhen0409, masterFoad
|
||||
# ggml-org/ggml-opencl : lhez, max-krasnyansky
|
||||
# ggml-org/ggml-rpc : rgerganov
|
||||
# ggml-org/ggml-sycl : arthw
|
||||
# ggml-org/ggml-openvino : ravi9, wine99
|
||||
# ggml-org/ggml-riscv : xctan
|
||||
# ggml-org/ggml-rpc : am17an, rgerganov
|
||||
# ggml-org/ggml-sycl : arthw, Titaniumtown
|
||||
# ggml-org/ggml-vulkan : 0cc4m, jeffbolznv
|
||||
# ggml-org/ggml-webgpu : reeselevine, yomaytk
|
||||
# ggml-org/ggml-zdnn : taronaeo
|
||||
# ggml-org/llama-common : ggerganov, aldehir, angt, danbev, ngxson, pwilkin
|
||||
# ggml-org/ggml-zdnn : AlekseiNikiforovIBM, Andreas-Krebbel, taronaeo
|
||||
# ggml-org/llama-base : am17an, CISC, ggerganov, ngxson, sanmai, ServeurpersoCom
|
||||
# ggml-org/llama-common : aldehir, angt, bartowski1182, danbev, ggerganov, ngxson, nikwen, pwilkin, ruixiang63, sanmai
|
||||
# ggml-org/llama-examples : ggerganov
|
||||
# ggml-org/llama-models : CISC, danbev, ggerganov, ngxson
|
||||
# ggml-org/llama-mtmd : ngxson
|
||||
# ggml-org/llama-server : ggerganov, ngxson, allozaur, angt, ServeurpersoCom
|
||||
# ggml-org/llama-ui : allozaur
|
||||
# ggml-org/llama-server : allozaur, angt, ggerganov, ngxson, ServeurpersoCom
|
||||
# ggml-org/llama-ui : allozaur
|
||||
|
||||
/.devops/*.Dockerfile @ngxson
|
||||
/.github/actions/ @ggml-org/ci
|
||||
@@ -29,28 +38,12 @@
|
||||
/conversion/ @CISC
|
||||
/convert_*.py @CISC
|
||||
/docs/backend/snapdragon/ @ggml-org/ggml-hexagon
|
||||
/examples/batched.swift/ @ggerganov
|
||||
/examples/batched/ @ggerganov
|
||||
/examples/convert-llama2c-to-ggml/ @ggerganov
|
||||
/examples/ @ggml-org/llama-examples
|
||||
/examples/debug/ @danbev @pwilkin
|
||||
/examples/deprecation-warning/ @ggerganov
|
||||
/examples/diffusion/ @am17an
|
||||
/examples/embedding/ @ggerganov
|
||||
/examples/eval-callback/ @ggerganov
|
||||
/examples/export-docs/ @ggerganov
|
||||
/examples/gen-docs/ @ggerganov
|
||||
/examples/gguf/ @ggerganov
|
||||
/examples/llama.android/ @ggerganov @hanyin-arm @naco-siren
|
||||
/examples/llama.swiftui/ @ggerganov
|
||||
/examples/llama.vim @ggerganov
|
||||
/examples/lookahead/ @ggerganov
|
||||
/examples/lookup/ @JohannesGaessler
|
||||
/examples/model-conversion/ @danbev
|
||||
/examples/parallel/ @ggerganov
|
||||
/examples/passkey/ @ggerganov
|
||||
/examples/retrieval/ @ggerganov
|
||||
/examples/speculative-simple/ @ggerganov
|
||||
/examples/speculative/ @ggerganov
|
||||
/ggml/cmake/ @ggerganov
|
||||
/ggml/include/ @ggerganov
|
||||
/ggml/src/ggml-backend-meta.cpp @JohannesGaessler
|
||||
@@ -62,7 +55,7 @@
|
||||
/ggml/src/ggml-cuda/ @ggml-org/ggml-cuda
|
||||
/ggml/src/ggml-cuda/vendors/hip.h @IMbackK
|
||||
/ggml/src/ggml-hexagon/ @ggml-org/ggml-hexagon
|
||||
/ggml/src/ggml-hip/ @IMbackK
|
||||
/ggml/src/ggml-hip/ @ggml-org/ggml-hip
|
||||
/ggml/src/ggml-et/ @marty1885
|
||||
/ggml/src/ggml-impl.h @ggerganov
|
||||
/ggml/src/ggml-metal/ @ggml-org/ggml-metal
|
||||
@@ -76,7 +69,7 @@
|
||||
/ggml/src/ggml-virtgpu/ @kpouget
|
||||
/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-zdnn/ @ggml-org/ggml-zdnn
|
||||
/ggml/src/ggml-zendnn/ @avinashcpandey @Jiten1parmar
|
||||
/ggml/src/ggml.c @ggerganov
|
||||
/ggml/src/ggml.cpp @ggerganov
|
||||
@@ -87,14 +80,14 @@
|
||||
/scripts/get* @ggerganov
|
||||
/scripts/sync* @ggerganov
|
||||
/scripts/snapdragon/ @ggml-org/ggml-hexagon
|
||||
/src/ @ggerganov
|
||||
/src/ @ggml-org/llama-base
|
||||
/src/llama-adapter.* @CISC
|
||||
/src/llama-arch.* @CISC
|
||||
/src/llama-chat.* @ngxson
|
||||
/src/llama-graph.* @CISC
|
||||
/src/llama-model.* @CISC
|
||||
/src/llama-vocab.* @CISC
|
||||
/src/models/ @CISC
|
||||
/src/models/ @ggml-org/llama-models
|
||||
/tests/ @ggerganov
|
||||
/tests/test-chat.* @pwilkin
|
||||
/tests/test-server-component.cpp @ggml-org/llama-server
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
[](https://github.com/ggml-org/llama.cpp/actions/workflows/docker.yml)
|
||||
[](https://github.com/ggml-org/llama.cpp/actions/workflows/winget.yml)
|
||||
|
||||
[ggml](https://github.com/ggml-org/ggml) / [ops](https://github.com/ggml-org/llama.cpp/blob/master/docs/ops.md) / [maintainer PRs](https://github.com/ggml-org/llama.cpp/issues?q=is%3Apr%20is%3Aopen%20draft%3AFalse%20(author%3Argerganov%20OR%20author%3AKitaitiMakoto%20OR%20author%3Adanbev%20OR%20author%3Aaldehir%20OR%20author%3Amax-krasnyansky%20OR%20author%3ACISC%20OR%20author%3Aggerganov%20OR%20author%3Aam17an%20OR%20author%3Ajhen0409%20OR%20author%3Abartowski1182%20OR%20author%3Anikwen%20OR%20author%3Ahipudding%20OR%20author%3Aravi9%20OR%20author%3AServeurpersoCom%20OR%20author%3Apwilkin%20OR%20author%3Areeselevine%20OR%20author%3Angxson%20OR%20author%3Ajeffbolznv%20OR%20author%3Amarty1885%20OR%20author%3A0cc4m%20OR%20author%3ATitaniumtown%20OR%20author%3Aangt%20OR%20author%3AIMbackK%20OR%20author%3Aarthw%20OR%20author%3AJohannesGaessler%20OR%20author%3AORippler%20OR%20author%3Aruixiang63%20OR%20author%3Axctan%20OR%20author%3Aallozaur%20OR%20author%3Ayomaytk%20OR%20author%3Aaendk%20OR%20author%3Awine99%20OR%20author%3Agaugarg-nv%20OR%20author%3Ataronaeo%20OR%20author%3Aforforever73%20OR%20author%3Alhez%20OR%20author%3Anetrunnereve%20OR%20author%3Afairydreaming)%20sort%3Aupdated-desc) / [dev stats](https://github.com/ggml-org/llama.cpp-dev) / [lib llama API](https://github.com/ggml-org/llama.cpp/issues/9289) / [llama-server REST API](https://github.com/ggml-org/llama.cpp/issues/9291)
|
||||
[ggml](https://github.com/ggml-org/ggml) / [ops](https://github.com/ggml-org/llama.cpp/blob/master/docs/ops.md) / [maintainer PRs](https://github.com/ggml-org/llama.cpp/issues?q=is%3Apr%20is%3Aopen%20draft%3AFalse%20(author%3Asanmai%20OR%20author%3Argerganov%20OR%20author%3AKitaitiMakoto%20OR%20author%3Adanbev%20OR%20author%3Aaldehir%20OR%20author%3Amax-krasnyansky%20OR%20author%3ACISC%20OR%20author%3Aggerganov%20OR%20author%3Aam17an%20OR%20author%3Ajhen0409%20OR%20author%3Abartowski1182%20OR%20author%3Anikwen%20OR%20author%3Ahipudding%20OR%20author%3Aravi9%20OR%20author%3AServeurpersoCom%20OR%20author%3Athelittlefireman%20OR%20author%3Apwilkin%20OR%20author%3Areeselevine%20OR%20author%3Angxson%20OR%20author%3Ajeffbolznv%20OR%20author%3Amarty1885%20OR%20author%3A0cc4m%20OR%20author%3ATitaniumtown%20OR%20author%3Aangt%20OR%20author%3AIMbackK%20OR%20author%3Aarthw%20OR%20author%3AJohannesGaessler%20OR%20author%3AORippler%20OR%20author%3Aruixiang63%20OR%20author%3Axctan%20OR%20author%3AmasterFoad%20OR%20author%3Aallozaur%20OR%20author%3AAndreas-Krebbel%20OR%20author%3Ayomaytk%20OR%20author%3Aaendk%20OR%20author%3Awine99%20OR%20author%3Agaugarg-nv%20OR%20author%3Ataronaeo%20OR%20author%3Aforforever73%20OR%20author%3Achaxu01%20OR%20author%3Alhez%20OR%20author%3AAlekseiNikiforovIBM%20OR%20author%3Anetrunnereve%20OR%20author%3Afairydreaming%20OR%20author%3Aynankani)%20sort%3Aupdated-desc) / [dev stats](https://github.com/ggml-org/llama.cpp-dev) / [lib llama API](https://github.com/ggml-org/llama.cpp/issues/9289) / [llama-server REST API](https://github.com/ggml-org/llama.cpp/issues/9291)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -185,18 +185,28 @@ fi
|
||||
|
||||
## helpers
|
||||
|
||||
# download a file if it does not exist or if it is outdated
|
||||
# download a file unless already cached in $MNT
|
||||
function gg_wget {
|
||||
local out=$1
|
||||
local url=$2
|
||||
|
||||
local cwd=`pwd`
|
||||
local name=`basename $url`
|
||||
local tmp=.gg-wget.$$.tmp
|
||||
|
||||
mkdir -p $out
|
||||
cd $out
|
||||
|
||||
# should not re-download if file is the same
|
||||
wget -nv -c -N $url
|
||||
# cache first: HF artifacts are static, HF sends no Last-Modified for -N
|
||||
if [ ! -s $name ]; then
|
||||
# publish with mv: concurrent runners never read partial files
|
||||
rm -rf $tmp && mkdir $tmp
|
||||
(cd $tmp && wget -nv $url)
|
||||
if [ -s $tmp/$name ]; then
|
||||
mv -f $tmp/$name $name
|
||||
fi
|
||||
rm -rf $tmp
|
||||
fi
|
||||
|
||||
cd $cwd
|
||||
}
|
||||
@@ -254,7 +264,7 @@ function gg_run_ctest_release {
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
if [ -z ${GG_BUILD_LOW_PERF} ]; then
|
||||
(time ctest -C Release --output-on-failure -L 'main|python' ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
|
||||
(time ctest -C Release --output-on-failure -L main ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
|
||||
else
|
||||
(time ctest -C Release --output-on-failure -L main -E test-opt ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
|
||||
fi
|
||||
@@ -312,8 +322,8 @@ function gg_run_test_scripts {
|
||||
|
||||
set -e
|
||||
|
||||
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
|
||||
set +e
|
||||
}
|
||||
@@ -374,10 +384,14 @@ function gg_run_qwen3_0_6b {
|
||||
|
||||
|
||||
gg_wget models-mnt/wikitext/ https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip
|
||||
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d models-mnt/wikitext/
|
||||
|
||||
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
|
||||
mkdir -p $OUT/scratch/qwen3-0.6B
|
||||
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d $OUT/scratch/wikitext/
|
||||
|
||||
path_models="../models-mnt/qwen3/0.6B"
|
||||
path_wiki="../models-mnt/wikitext/wikitext-2-raw"
|
||||
path_local="$OUT/scratch/qwen3-0.6B"
|
||||
path_wiki="$OUT/scratch/wikitext/wikitext-2-raw"
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -386,72 +400,15 @@ function gg_run_qwen3_0_6b {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf --outtype f16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-bf16.gguf --outtype bf16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf --outtype f16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-bf16.gguf --outtype bf16
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_bf16="${path_models}/ggml-model-bf16.gguf"
|
||||
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
|
||||
model_q4_0="${path_models}/ggml-model-q4_0.gguf"
|
||||
model_q4_1="${path_models}/ggml-model-q4_1.gguf"
|
||||
model_q5_0="${path_models}/ggml-model-q5_0.gguf"
|
||||
model_q5_1="${path_models}/ggml-model-q5_1.gguf"
|
||||
model_q2_k="${path_models}/ggml-model-q2_k.gguf"
|
||||
model_q3_k="${path_models}/ggml-model-q3_k.gguf"
|
||||
model_q4_k="${path_models}/ggml-model-q4_k.gguf"
|
||||
model_q5_k="${path_models}/ggml-model-q5_k.gguf"
|
||||
model_q6_k="${path_models}/ggml-model-q6_k.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
model_bf16="${path_local}/ggml-model-bf16.gguf"
|
||||
model_q4_0="${path_local}/ggml-model-q4_0.gguf"
|
||||
|
||||
wiki_test="${path_wiki}/wiki.test.raw"
|
||||
|
||||
./bin/llama-quantize ${model_bf16} ${model_q8_0} q8_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_0} q4_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_1} q4_1 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_0} q5_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_1} q5_1 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q2_k} q2_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q3_k} q3_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_k} q4_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_k} q5_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q6_k} q6_k $(nproc)
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q8_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q2_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q3_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q6_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
|
||||
|
||||
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
fi
|
||||
(time ./bin/llama-perplexity --model ${model_q8_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
|
||||
(time ./bin/llama-perplexity --model ${model_q2_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q3_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q6_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
|
||||
|
||||
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
|
||||
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
|
||||
function check_ppl {
|
||||
qnt="$1"
|
||||
ppl=$(echo "$2" | grep -oE "[0-9]+\.[0-9]+" | tail -n 1)
|
||||
@@ -465,20 +422,56 @@ function gg_run_qwen3_0_6b {
|
||||
return 0
|
||||
}
|
||||
|
||||
for qnt in q8_0 q4_0 q4_1 q5_0 q5_1 q2_k q3_k q4_k q5_k q6_k; do
|
||||
model="${path_local}/ggml-model-${qnt}.gguf"
|
||||
|
||||
./bin/llama-quantize ${model_bf16} ${model} ${qnt} $(nproc)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
|
||||
(time ./bin/llama-perplexity --model ${model} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
|
||||
|
||||
# note: ppl > 20.0 for q2_k with this model
|
||||
if [ "${qnt}" != "q2_k" ]; then
|
||||
check_ppl "${qnt}" "$(cat $OUT/${ci}-tg-${qnt}.log | grep "^\[1\]")"
|
||||
fi
|
||||
|
||||
if [ "${qnt}" = "q4_0" ]; then
|
||||
# q4_0 is kept for other tests
|
||||
q4_0="${path_models}/ggml-model-q4_0.gguf"
|
||||
ln ${model} "$q4_0" 2>/dev/null || { cp ${model} $q4_0.tmp.$$ && mv -f $q4_0.tmp.$$ $q4_0; }
|
||||
else
|
||||
rm -f ${model}
|
||||
fi
|
||||
done
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
|
||||
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
fi
|
||||
|
||||
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
|
||||
|
||||
# TODO: these tests are extremely slow on WebGPU - re-enable when fixed
|
||||
# https://github.com/ggml-org/llama.cpp/actions/runs/39639985732
|
||||
if [ -z ${GG_BUILD_WEBGPU} ]; then
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
fi
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
|
||||
check_ppl "f16" "$(cat $OUT/${ci}-tg-f16.log | grep "^\[1\]")"
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
check_ppl "bf16" "$(cat $OUT/${ci}-tg-bf16.log | grep "^\[1\]")"
|
||||
fi
|
||||
check_ppl "q8_0" "$(cat $OUT/${ci}-tg-q8_0.log | grep "^\[1\]")"
|
||||
check_ppl "q4_0" "$(cat $OUT/${ci}-tg-q4_0.log | grep "^\[1\]")"
|
||||
check_ppl "q4_1" "$(cat $OUT/${ci}-tg-q4_1.log | grep "^\[1\]")"
|
||||
check_ppl "q5_0" "$(cat $OUT/${ci}-tg-q5_0.log | grep "^\[1\]")"
|
||||
check_ppl "q5_1" "$(cat $OUT/${ci}-tg-q5_1.log | grep "^\[1\]")"
|
||||
#check_ppl "q2_k" "$(cat $OUT/${ci}-tg-q2_k.log | grep "^\[1\]")" # note: ppl > 20.0 for this quant and model
|
||||
check_ppl "q3_k" "$(cat $OUT/${ci}-tg-q3_k.log | grep "^\[1\]")"
|
||||
check_ppl "q4_k" "$(cat $OUT/${ci}-tg-q4_k.log | grep "^\[1\]")"
|
||||
check_ppl "q5_k" "$(cat $OUT/${ci}-tg-q5_k.log | grep "^\[1\]")"
|
||||
check_ppl "q6_k" "$(cat $OUT/${ci}-tg-q6_k.log | grep "^\[1\]")"
|
||||
|
||||
# scratch is rebuilt on every run; q4_0 is already published to $MNT
|
||||
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
|
||||
|
||||
set +e
|
||||
}
|
||||
@@ -501,6 +494,8 @@ function gg_run_embd_bge_small {
|
||||
gg_wget models-mnt/bge-small/1_Pooling https://huggingface.co/BAAI/bge-small-en-v1.5/raw/main/1_Pooling/config.json
|
||||
|
||||
path_models="../models-mnt/bge-small"
|
||||
path_local="$OUT/scratch/bge-small"
|
||||
rm -rf $path_local && mkdir -p $path_local
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -509,10 +504,10 @@ function gg_run_embd_bge_small {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
model_q8_0="${path_local}/ggml-model-q8_0.gguf"
|
||||
|
||||
./bin/llama-quantize ${model_f16} ${model_q8_0} q8_0
|
||||
|
||||
@@ -521,6 +516,8 @@ function gg_run_embd_bge_small {
|
||||
(time ./bin/llama-embedding --model ${model_f16} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-embedding --model ${model_q8_0} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
|
||||
rm -rf $path_local
|
||||
|
||||
set +e
|
||||
}
|
||||
|
||||
@@ -537,6 +534,8 @@ function gg_run_rerank_tiny {
|
||||
gg_wget models-mnt/rerank-tiny/ https://huggingface.co/jinaai/jina-reranker-v1-tiny-en/raw/main/vocab.json
|
||||
|
||||
path_models="../models-mnt/rerank-tiny"
|
||||
path_local="$OUT/scratch/rerank-tiny"
|
||||
rm -rf $path_local && mkdir -p $path_local
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -545,9 +544,9 @@ function gg_run_rerank_tiny {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
@@ -577,6 +576,8 @@ function gg_run_rerank_tiny {
|
||||
check_score "rerank score 1" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 1")" "0.00" "0.05" | tee -a $OUT/${ci}-rk-f16.log
|
||||
check_score "rerank score 2" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 2")" "0.10" "0.30" | tee -a $OUT/${ci}-rk-f16.log
|
||||
|
||||
rm -rf $path_local
|
||||
|
||||
set +e
|
||||
}
|
||||
|
||||
@@ -683,11 +684,11 @@ if [ -z ${GG_BUILD_LOW_PERF} ]; then
|
||||
ln -sfn ${mnt_models} ${SRC}/models-mnt
|
||||
|
||||
# Create a fresh python3 venv and enter it
|
||||
if ! python3 -m venv "$MNT/venv"; then
|
||||
echo "Error: Failed to create Python virtual environment at $MNT/venv."
|
||||
if ! python3 -m venv "$OUT/venv"; then
|
||||
echo "Error: Failed to create Python virtual environment at $OUT/venv."
|
||||
exit 1
|
||||
fi
|
||||
source "$MNT/venv/bin/activate"
|
||||
source "$OUT/venv/bin/activate"
|
||||
|
||||
pip install -r ${SRC}/requirements.txt --disable-pip-version-check
|
||||
pip install --editable gguf-py --disable-pip-version-check
|
||||
|
||||
@@ -78,6 +78,8 @@ add_library(${TARGET}
|
||||
download.h
|
||||
fit.cpp
|
||||
fit.h
|
||||
file-lock.cpp
|
||||
file-lock.h
|
||||
hf-cache.cpp
|
||||
hf-cache.h
|
||||
http.h
|
||||
@@ -184,3 +186,8 @@ if (LLAMA_LLGUIDANCE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PUBLIC llama Threads::Threads)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "AIX")
|
||||
# AIX's flock() function comes from libbsd.a
|
||||
target_link_libraries(${TARGET} PRIVATE -lbsd)
|
||||
endif()
|
||||
|
||||
+45
-22
@@ -470,6 +470,14 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
auto opts = handler.opts; // copy
|
||||
opts.callback = callback;
|
||||
|
||||
// another process may hold the cache lock during finalization:
|
||||
// let the download callback cancel that wait too
|
||||
auto finalize = [callback](const hf_cache::hf_file & f) {
|
||||
return hf_cache::finalize_file(f, [callback]() {
|
||||
return !(callback && callback->is_cancelled());
|
||||
});
|
||||
};
|
||||
|
||||
// handle plain "url" if needed
|
||||
auto handle_url = [&](common_params_model & model) {
|
||||
if (!model.url.empty()) {
|
||||
@@ -518,7 +526,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
}
|
||||
|
||||
// handle hf_plan tasks
|
||||
auto add_tasks = [&opts, &tasks](const hf_cache::hf_files & model_files,
|
||||
auto add_tasks = [&opts, &tasks, &finalize](const hf_cache::hf_files & model_files,
|
||||
const hf_cache::hf_file & primary,
|
||||
common_params_model & model) {
|
||||
for (size_t i = 0; i < model_files.size(); ++i) {
|
||||
@@ -527,9 +535,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(model_file, opts, [&, is_primary]() {
|
||||
if (is_primary) {
|
||||
// the primary file is the first split (00001-of), use it as model path
|
||||
model.path = hf_cache::finalize_file(model_file);
|
||||
model.path = finalize(model_file);
|
||||
} else {
|
||||
hf_cache::finalize_file(model_file);
|
||||
finalize(model_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -581,9 +589,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.mtp, opts, [&]() {
|
||||
// only use the discovered MTP head when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.mtp);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.mtp);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.mtp);
|
||||
finalize(plan_spec.mtp);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -591,9 +599,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.dflash, opts, [&]() {
|
||||
// only use the discovered DFlash sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.dflash);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.dflash);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.dflash);
|
||||
finalize(plan_spec.dflash);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -601,9 +609,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.eagle3, opts, [&]() {
|
||||
// only use the discovered Eagle3 sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.eagle3);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.eagle3);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.eagle3);
|
||||
finalize(plan_spec.eagle3);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -611,9 +619,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.dspark, opts, [&]() {
|
||||
// only use the discovered DSpark sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.dspark);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.dspark);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.dspark);
|
||||
finalize(plan_spec.dspark);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -634,16 +642,16 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
}
|
||||
if (!plan.mmproj.local_path.empty()) {
|
||||
tasks.emplace_back(plan.mmproj, opts, [&]() {
|
||||
params.mmproj.path = hf_cache::finalize_file(plan.mmproj);
|
||||
params.mmproj.path = finalize(plan.mmproj);
|
||||
});
|
||||
}
|
||||
if (!plan.mtp.local_path.empty() && !had_spec_url) {
|
||||
tasks.emplace_back(plan.mtp, opts, [&]() {
|
||||
// only fall back to the discovered MTP head when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.mtp);
|
||||
params.speculative.draft.mparams.path = finalize(plan.mtp);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.mtp);
|
||||
finalize(plan.mtp);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -651,9 +659,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.dflash, opts, [&]() {
|
||||
// only fall back to the discovered DFlash sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.dflash);
|
||||
params.speculative.draft.mparams.path = finalize(plan.dflash);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.dflash);
|
||||
finalize(plan.dflash);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -661,9 +669,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.eagle3, opts, [&]() {
|
||||
// only fall back to the discovered Eagle3 sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.eagle3);
|
||||
params.speculative.draft.mparams.path = finalize(plan.eagle3);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.eagle3);
|
||||
finalize(plan.eagle3);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -671,9 +679,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.dspark, opts, [&]() {
|
||||
// only fall back to the discovered DSpark sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.dspark);
|
||||
params.speculative.draft.mparams.path = finalize(plan.dspark);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.dspark);
|
||||
finalize(plan.dspark);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -681,7 +689,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.preset, opts, [&]() {
|
||||
// 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);
|
||||
params.models_preset = finalize(plan.preset);
|
||||
// clear the model so the server starts in router mode
|
||||
params.model.path.clear();
|
||||
params.model.hf_repo.clear();
|
||||
@@ -916,7 +924,7 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
|
||||
// TODO @ngxson : maybe show a list of available models in CLI in this case
|
||||
bool can_skip_model = params.usage || params.completion || !params.server_base.empty();
|
||||
if (!can_skip_model && params.model.path.empty()) {
|
||||
throw std::invalid_argument("error: --model is required\n");
|
||||
throw std::invalid_argument("error: --model or -hf is required\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3700,6 +3708,13 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.models_max = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_MODELS_MAX"));
|
||||
add_opt(common_arg(
|
||||
{"--models-memory-margin"}, "N",
|
||||
string_format("for router server, MiB of device memory to keep free per device, unload LRU models until a new one fits (default: %d, 0 = disabled)", params.models_memory_margin),
|
||||
[](common_params & params, int value) {
|
||||
params.models_memory_margin = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_MODELS_MEMORY_MARGIN"));
|
||||
add_opt(common_arg(
|
||||
{"--models-autoload"},
|
||||
{"--no-models-autoload"},
|
||||
@@ -4497,6 +4512,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.tts_lang = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TTS}));
|
||||
add_opt(common_arg(
|
||||
{"--tts-voice"}, "VOICE",
|
||||
"voice for audio generation (model-dependent)\n"
|
||||
"see tts/README.md for per-model usage notes",
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.tts_voice = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TTS}));
|
||||
add_opt(common_arg(
|
||||
{"--tts-speaker-file"}, "FNAME",
|
||||
"speaker file path for audio generation",
|
||||
|
||||
+202
-29
@@ -190,10 +190,17 @@ json common_chat_msg::to_json_oaicompat(bool concat_typed_text) const {
|
||||
} else {
|
||||
auto & parts = jmsg["content"] = json::array();
|
||||
for (const auto & part : content_parts) {
|
||||
parts.push_back({
|
||||
{"type", part.type},
|
||||
{"text", part.text},
|
||||
});
|
||||
if (part.type == "tool_reference") {
|
||||
parts.push_back({
|
||||
{"type", part.type},
|
||||
{"name", part.name},
|
||||
});
|
||||
} else {
|
||||
parts.push_back({
|
||||
{"type", part.type},
|
||||
{"text", part.text},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -309,6 +316,7 @@ struct common_chat_templates {
|
||||
bool has_explicit_template; // Model had builtin template or template overridden was specified.
|
||||
std::unique_ptr<common_chat_template> template_default; // always set (defaults to chatml)
|
||||
std::unique_ptr<common_chat_template> template_tool_use;
|
||||
std::unique_ptr<common_chat_template> template_tts; // the prompt of a text to speak
|
||||
};
|
||||
|
||||
common_chat_tool_choice common_chat_tool_choice_parse_oaicompat(const std::string & tool_choice) {
|
||||
@@ -370,12 +378,16 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
|
||||
throw std::invalid_argument("Missing content part type: " + part.dump());
|
||||
}
|
||||
const auto & type = part.at("type");
|
||||
if (type != "text" && type != "media_marker") {
|
||||
throw std::invalid_argument("Unsupported content part type: " + type.dump());
|
||||
}
|
||||
common_chat_msg_content_part msg_part;
|
||||
msg_part.type = type;
|
||||
msg_part.text = part.at("text");
|
||||
if (type == "text" || type == "media_marker") {
|
||||
msg_part.text = part.at("text");
|
||||
} else if (type == "tool_reference") {
|
||||
// names a request tool, the template renders its definition
|
||||
msg_part.name = part.at("name");
|
||||
} else {
|
||||
throw std::invalid_argument("Unsupported content part type: " + type.dump());
|
||||
}
|
||||
msg.content_parts.push_back(msg_part);
|
||||
}
|
||||
} else if (!content.is_null()) {
|
||||
@@ -428,6 +440,41 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
|
||||
if (message.contains("tool_call_id")) {
|
||||
msg.tool_call_id = message.at("tool_call_id");
|
||||
}
|
||||
{
|
||||
json extra = json::object();
|
||||
for (const auto & [k, v] : message.items()) {
|
||||
if (k != "role" && k != "content" && k != "tool_calls" && k != "reasoning_content" &&
|
||||
k != "name" && k != "tool_call_id") {
|
||||
extra[k] = v;
|
||||
}
|
||||
}
|
||||
if (has_tool_calls) {
|
||||
// per tool call: keys besides type/function/id, and function keys besides name/arguments
|
||||
json tc_extra = json::array();
|
||||
bool any = false;
|
||||
for (const auto & tool_call : message.at("tool_calls")) {
|
||||
json e = json::object();
|
||||
for (const auto & [k, v] : tool_call.items()) {
|
||||
if (k != "type" && k != "function" && k != "id") {
|
||||
e[k] = v;
|
||||
}
|
||||
}
|
||||
for (const auto & [k, v] : tool_call.at("function").items()) {
|
||||
if (k != "name" && k != "arguments") {
|
||||
e["function"][k] = v;
|
||||
}
|
||||
}
|
||||
any = any || !e.empty();
|
||||
tc_extra.push_back(e);
|
||||
}
|
||||
if (any) {
|
||||
extra["__tool_call_extras"] = tc_extra;
|
||||
}
|
||||
}
|
||||
if (!extra.empty()) {
|
||||
msg.extra_json = extra.dump();
|
||||
}
|
||||
}
|
||||
|
||||
msgs.push_back(msg);
|
||||
}
|
||||
@@ -516,6 +563,50 @@ static json render_message_to_json(const std::vector<common_chat_msg> & msgs, co
|
||||
return messages_inp_normalizer(c).normalize(messages);
|
||||
}
|
||||
|
||||
// DeepSeek V4.1 reads non-OpenAI message keys and joins content blocks with "\n\n" around images,
|
||||
// so keep media markers as separate text parts and restore the extra keys
|
||||
static json render_message_to_json_deepseek_v4_1(const std::vector<common_chat_msg> & msgs, const jinja::caps & c) {
|
||||
json messages = json::array();
|
||||
for (const auto & msg : msgs) {
|
||||
json jmsg;
|
||||
if (msg.contains_media() && msg.content.empty()) {
|
||||
common_chat_msg typed = msg;
|
||||
for (auto & part : typed.content_parts) {
|
||||
part.type = "text";
|
||||
}
|
||||
jmsg = typed.to_json_oaicompat(/* concat_typed_text= */ false);
|
||||
} else {
|
||||
jmsg = msg.to_json_oaicompat(/* concat_typed_text= */ false);
|
||||
}
|
||||
if (!msg.extra_json.empty()) {
|
||||
const json extra = json::parse(msg.extra_json);
|
||||
for (const auto & [k, v] : extra.items()) {
|
||||
if (k == "__tool_call_extras") {
|
||||
if (!jmsg.contains("tool_calls")) {
|
||||
continue;
|
||||
}
|
||||
auto & tcs = jmsg.at("tool_calls");
|
||||
for (size_t i = 0; i < tcs.size() && i < v.size(); i++) {
|
||||
for (const auto & [tk, tv] : v[i].items()) {
|
||||
if (tk == "function") {
|
||||
for (const auto & [fk, fv] : tv.items()) {
|
||||
tcs[i]["function"][fk] = fv;
|
||||
}
|
||||
} else {
|
||||
tcs[i][tk] = tv;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!jmsg.contains(k)) {
|
||||
jmsg[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
messages.push_back(std::move(jmsg));
|
||||
}
|
||||
return messages_inp_normalizer(c).normalize(messages);
|
||||
}
|
||||
|
||||
// DEPRECATED: only used in tests
|
||||
json common_chat_msgs_to_json_oaicompat(const std::vector<common_chat_msg> & msgs, bool concat_typed_text) {
|
||||
jinja::caps c;
|
||||
@@ -531,13 +622,17 @@ json common_chat_tools_to_json_oaicompat(const std::vector<common_chat_tool> & t
|
||||
|
||||
auto result = json::array();
|
||||
for (const auto & tool : tools) {
|
||||
json function {
|
||||
{ "name", tool.name },
|
||||
{ "description", tool.description },
|
||||
{ "parameters", json::parse(tool.parameters) },
|
||||
};
|
||||
if (tool.defer_loading) {
|
||||
function["defer_loading"] = true;
|
||||
}
|
||||
result.push_back({
|
||||
{ "type", "function" },
|
||||
{ "function", {
|
||||
{ "name", tool.name },
|
||||
{ "description", tool.description },
|
||||
{ "parameters", json::parse(tool.parameters) },
|
||||
}},
|
||||
{ "function", function },
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -574,10 +669,19 @@ std::vector<common_chat_tool> common_chat_tools_parse_oaicompat(const json & too
|
||||
}
|
||||
|
||||
const auto & function = tool.at("function");
|
||||
|
||||
// "defer_loading" sits on the function, or on the tool next to "type"; the function wins
|
||||
const json & defer = function.contains("defer_loading") ? function.at("defer_loading")
|
||||
: tool.value("defer_loading", json(false));
|
||||
if (!defer.is_boolean()) {
|
||||
throw std::invalid_argument("Expected 'defer_loading' to be a boolean, got " + defer.dump());
|
||||
}
|
||||
|
||||
result.push_back({
|
||||
/* .name = */ function.at("name"),
|
||||
/* .description = */ function.value("description", ""),
|
||||
/* .parameters = */ function.value("parameters", json::object()).dump(),
|
||||
/* .defer_loading = */ defer.get<bool>(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -700,19 +804,70 @@ bool common_chat_templates_was_explicit(const struct common_chat_templates * tmp
|
||||
return tmpls->has_explicit_template;
|
||||
}
|
||||
|
||||
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates) {
|
||||
// renders a task template (e.g. "tts") with the given variables
|
||||
static std::string common_chat_template_render(const common_chat_template & tmpl, const json & vars) {
|
||||
jinja::context ctx(tmpl.source());
|
||||
jinja::global_from_json(ctx, vars, false);
|
||||
ctx.set_val("bos_token", jinja::mk_val<jinja::value_string>(tmpl.bos_token()));
|
||||
ctx.set_val("eos_token", jinja::mk_val<jinja::value_string>(tmpl.eos_token()));
|
||||
jinja::runtime runtime(ctx);
|
||||
return jinja::runtime::gather_string_parts(runtime.execute(tmpl.prog))->as_string().str();
|
||||
}
|
||||
|
||||
// LFM3 format detection: template renders deferred tools inside <additional_tools>...</additional_tools>
|
||||
static bool is_lfm3_template(const std::string & src) {
|
||||
return src.find("<additional_tools>") != std::string::npos;
|
||||
}
|
||||
|
||||
// LFM3-Audio: a known language code maps to its name, no language lets the model detect it
|
||||
static std::string lfm3_asr_system_prompt(const std::string & language) {
|
||||
static const std::map<std::string, std::string> names = {
|
||||
{ "ar", "Arabic" }, { "da", "Danish" }, { "de", "German" }, { "el", "Greek" }, { "en", "English" },
|
||||
{ "es", "Spanish" }, { "fi", "Finnish" }, { "fr", "French" }, { "he", "Hebrew" }, { "it", "Italian" },
|
||||
{ "ja", "Japanese" }, { "ko", "Korean" }, { "nl", "Dutch" }, { "pl", "Polish" }, { "pt", "Portuguese" },
|
||||
{ "sv", "Swedish" }, { "tr", "Turkish" }, { "zh", "Chinese" },
|
||||
};
|
||||
if (language.empty()) {
|
||||
return "Perform ASR.";
|
||||
}
|
||||
std::string code = language;
|
||||
std::transform(code.begin(), code.end(), code.begin(), [](unsigned char c) { return (char) std::tolower(c); });
|
||||
const auto it = names.find(code);
|
||||
return "Perform ASR in " + (it != names.end() ? it->second : language) + ".";
|
||||
}
|
||||
|
||||
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates, const std::string & language) {
|
||||
common_chat_prompt_preset asr_preset;
|
||||
asr_preset.system = "";
|
||||
asr_preset.user = "Transcribe audio to text";
|
||||
|
||||
if (chat_templates && chat_templates->template_default && is_lfm2_template(chat_templates->template_default->source())) {
|
||||
asr_preset.system = "Perform ASR.";
|
||||
asr_preset.user = "";
|
||||
if (chat_templates && chat_templates->template_default) {
|
||||
const std::string src = chat_templates->template_default->source();
|
||||
if (is_lfm2_template(src)) {
|
||||
asr_preset.system = "Perform ASR.";
|
||||
asr_preset.user = "";
|
||||
} else if (is_lfm3_template(src)) {
|
||||
asr_preset.system = lfm3_asr_system_prompt(language);
|
||||
asr_preset.user = "";
|
||||
asr_preset.has_language = true;
|
||||
}
|
||||
}
|
||||
|
||||
return asr_preset;
|
||||
}
|
||||
|
||||
std::string common_chat_get_tts_prompt(const common_chat_templates * chat_templates, const std::string & text,
|
||||
const std::string & language, const std::string & voice) {
|
||||
if (!chat_templates || !chat_templates->template_tts) {
|
||||
return "";
|
||||
}
|
||||
return common_chat_template_render(*chat_templates->template_tts, {
|
||||
{"messages", json::array({{{"role", "user"}, {"content", text}}})},
|
||||
{"language", language},
|
||||
{"voice", voice},
|
||||
});
|
||||
}
|
||||
|
||||
std::string common_chat_templates_source(const struct common_chat_templates * tmpls, const std::string & variant) {
|
||||
if (!variant.empty()) {
|
||||
if (variant == "tool_use") {
|
||||
@@ -823,6 +978,16 @@ common_chat_templates_ptr common_chat_templates_init(const struct llama_model *
|
||||
LOG_ERR("%s: failed to parse tool use chat template (ignoring it): %s\n", __func__, e.what());
|
||||
}
|
||||
}
|
||||
// task templates of the model, kept with a chat template override
|
||||
if (model) {
|
||||
if (const char * src = llama_model_chat_template(model, "tts")) {
|
||||
try {
|
||||
tmpls->template_tts = std::make_unique<common_chat_template>(src, token_bos, token_eos);
|
||||
} catch (const std::exception & e) {
|
||||
LOG_ERR("%s: failed to parse the tts chat template (ignoring it): %s\n", __func__, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmpls;
|
||||
}
|
||||
|
||||
@@ -1208,11 +1373,13 @@ static common_chat_params_init_fn common_chat_template_detect_params_init(const
|
||||
// DeepSeek V3.2/V4 format detection: template defines dsml_token and uses it for tool calls.
|
||||
// The template source contains the token as a variable assignment, not as a literal in markup.
|
||||
// V3.2 names the tool call block "function_calls", V4 names it "tool_calls".
|
||||
if (src.find("dsml_token") != std::string::npos &&
|
||||
src.find("DSML") != std::string::npos &&
|
||||
(src.find("function_calls") != std::string::npos ||
|
||||
src.find("tool_calls") != std::string::npos)) {
|
||||
LOG_DBG("Using specialized template: DeepSeek V3.2/V4\n");
|
||||
// V4.1 tag names have a leading space and its template does not name the dsml_token variable.
|
||||
if ((src.find("dsml_token") != std::string::npos &&
|
||||
src.find("DSML") != std::string::npos &&
|
||||
(src.find("function_calls") != std::string::npos ||
|
||||
src.find("tool_calls") != std::string::npos)) ||
|
||||
src.find("|DSML| calls>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: DeepSeek V3.2/V4/V4.1\n");
|
||||
return common_chat_params_init_deepseek_v3_2;
|
||||
}
|
||||
|
||||
@@ -1313,7 +1480,9 @@ static common_chat_params common_chat_templates_apply_jinja(const struct common_
|
||||
workaround::trim_all_content(trimmed_messages);
|
||||
messages_to_render = &trimmed_messages;
|
||||
}
|
||||
params.messages = render_message_to_json(*messages_to_render, tmpl.original_caps());
|
||||
params.messages = src.find("|DSML| calls>") != std::string::npos ?
|
||||
render_message_to_json_deepseek_v4_1(*messages_to_render, tmpl.original_caps()) :
|
||||
render_message_to_json(*messages_to_render, tmpl.original_caps());
|
||||
params.tool_choice = inputs.tool_choice;
|
||||
params.reasoning_format = inputs.reasoning_format;
|
||||
params.enable_thinking = inputs.enable_thinking;
|
||||
@@ -1634,6 +1803,9 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars
|
||||
//LOG_DBG("Parsing PEG input with format %s: %s\n", common_chat_format_name(params.format), effective_input.text.c_str());
|
||||
|
||||
common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_LENIENT;
|
||||
if (!is_partial) {
|
||||
flags |= COMMON_PEG_PARSE_FLAG_COMPLETE;
|
||||
}
|
||||
if (params.debug) {
|
||||
flags |= COMMON_PEG_PARSE_FLAG_DEBUG;
|
||||
}
|
||||
@@ -1771,12 +1943,13 @@ const common_chat_msg & common_chat_session::finish(const common_chat_input & ch
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, bool> common_chat_templates_get_caps(const common_chat_templates * chat_templates) {
|
||||
common_json common_chat_templates_get_caps(const common_chat_templates * chat_templates) {
|
||||
GGML_ASSERT(chat_templates != nullptr);
|
||||
GGML_ASSERT(chat_templates->template_default != nullptr);
|
||||
if (chat_templates->template_tool_use != nullptr) {
|
||||
// take the more expressive template when available
|
||||
return chat_templates->template_tool_use->caps.to_map();
|
||||
}
|
||||
return chat_templates->template_default->caps.to_map();
|
||||
// take the more expressive template when available
|
||||
const auto & caps = chat_templates->template_tool_use != nullptr ? chat_templates->template_tool_use->caps
|
||||
: chat_templates->template_default->caps;
|
||||
common_json result = caps.to_map();
|
||||
result["reasoning_effort_levels"] = caps.reasoning_effort_levels;
|
||||
return result;
|
||||
}
|
||||
|
||||
+15
-3
@@ -45,6 +45,7 @@ struct common_chat_tool_call {
|
||||
struct common_chat_msg_content_part {
|
||||
std::string type;
|
||||
std::string text;
|
||||
std::string name; // tool_reference: name of a tool from the request tools
|
||||
|
||||
// TODO @ngxson : no known chat templates support reasoning_content in content parts yet
|
||||
// this can be useful for models with interleaved thinking (like Kimi-K2)
|
||||
@@ -52,7 +53,7 @@ struct common_chat_msg_content_part {
|
||||
// std::string reasoning_content;
|
||||
|
||||
bool operator==(const common_chat_msg_content_part & other) const {
|
||||
return type == other.type && text == other.text;
|
||||
return type == other.type && text == other.text && name == other.name;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,6 +95,8 @@ struct common_chat_msg {
|
||||
std::string reasoning_content;
|
||||
std::string tool_name;
|
||||
std::string tool_call_id;
|
||||
// non-OpenAI message keys as a JSON object string, only for templates that read them (DeepSeek V4.1)
|
||||
std::string extra_json;
|
||||
|
||||
common_json to_json_oaicompat(bool concat_typed_text = false) const;
|
||||
|
||||
@@ -224,6 +227,9 @@ struct common_chat_tool {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string parameters;
|
||||
|
||||
// withheld from the system prompt, a tool_reference reveals it later in the conversation
|
||||
bool defer_loading = false;
|
||||
};
|
||||
|
||||
enum common_chat_tool_choice {
|
||||
@@ -466,7 +472,7 @@ common_json common_chat_tools_to_json_oaicompat(const std::vector<common_chat_to
|
||||
common_json common_chat_tool_parameters(const common_json & function);
|
||||
|
||||
// get template caps, useful for reporting to server /props endpoint
|
||||
std::map<std::string, bool> common_chat_templates_get_caps(const common_chat_templates * chat_templates);
|
||||
common_json common_chat_templates_get_caps(const common_chat_templates * chat_templates);
|
||||
|
||||
common_chat_prompt common_chat_template_direct_apply(
|
||||
const common_chat_template & tmpl,
|
||||
@@ -485,6 +491,12 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
|
||||
struct common_chat_prompt_preset {
|
||||
std::string system;
|
||||
std::string user;
|
||||
bool has_language = false; // the preset already names the requested language
|
||||
};
|
||||
|
||||
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates);
|
||||
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates, const std::string & language = "");
|
||||
|
||||
// prompt of the model's "tts" chat template for the text to speak, empty if the model has none
|
||||
// language and voice are optional, the template defines their defaults
|
||||
std::string common_chat_get_tts_prompt(const common_chat_templates * chat_templates, const std::string & text,
|
||||
const std::string & language = "", const std::string & voice = "");
|
||||
|
||||
+110
-82
@@ -26,6 +26,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -741,50 +742,56 @@ static inline bool glob_class_match(const char c, const char * pattern, const ch
|
||||
}
|
||||
|
||||
// simple glob: * matches non-/ chars, ** matches anything including /, [] matches character class
|
||||
static inline bool glob_match(const char * pattern, const char * str) {
|
||||
if (*pattern == '\0') {
|
||||
return *str == '\0';
|
||||
}
|
||||
if (pattern[0] == '*' && pattern[1] == '*') {
|
||||
const char * p = pattern + 2;
|
||||
if (glob_match(p, str)) return true;
|
||||
if (*str != '\0') return glob_match(pattern, str + 1);
|
||||
// only wildcards recurse, too many wildcards: no match
|
||||
static bool glob_match(const char * pattern, const char * str, int depth = 0) {
|
||||
if (depth >= COMMON_RECURSIVE_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
if (*pattern == '*') {
|
||||
const char * p = pattern + 1;
|
||||
for (; *str != '\0' && *str != '/'; str++) {
|
||||
if (glob_match(p, str)) return true;
|
||||
}
|
||||
return glob_match(p, str);
|
||||
}
|
||||
if (*pattern == '?' && *str != '\0' && *str != '/') {
|
||||
return glob_match(pattern + 1, str + 1);
|
||||
}
|
||||
if (*pattern == '[') {
|
||||
const char * class_end = pattern + 1;
|
||||
// If first character after '[' is ']' or '-', treat it as literal
|
||||
if (*class_end == ']' || *class_end == '-') {
|
||||
class_end++;
|
||||
}
|
||||
while (*class_end != '\0' && *class_end != ']') {
|
||||
class_end++;
|
||||
}
|
||||
if (*class_end == ']') {
|
||||
if (*str == '\0') return false;
|
||||
bool matched = glob_class_match(*str, pattern + 1, class_end);
|
||||
return matched && glob_match(class_end + 1, str + 1);
|
||||
} else {
|
||||
if (*str == '[') {
|
||||
return glob_match(pattern + 1, str + 1);
|
||||
while (*pattern != '\0') {
|
||||
if (pattern[0] == '*' && pattern[1] == '*') {
|
||||
const char * p = pattern + 2;
|
||||
for (;; str++) {
|
||||
if (glob_match(p, str, depth + 1)) return true;
|
||||
if (*str == '\0') return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (*pattern == '*') {
|
||||
const char * p = pattern + 1;
|
||||
for (; *str != '\0' && *str != '/'; str++) {
|
||||
if (glob_match(p, str, depth + 1)) return true;
|
||||
}
|
||||
return glob_match(p, str, depth + 1);
|
||||
}
|
||||
if (*pattern == '?' && *str != '\0' && *str != '/') {
|
||||
pattern++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
if (*pattern == '[') {
|
||||
const char * class_end = pattern + 1;
|
||||
// If first character after '[' is ']' or '-', treat it as literal
|
||||
if (*class_end == ']' || *class_end == '-') {
|
||||
class_end++;
|
||||
}
|
||||
while (*class_end != '\0' && *class_end != ']') {
|
||||
class_end++;
|
||||
}
|
||||
if (*class_end == ']') {
|
||||
if (*str == '\0' || !glob_class_match(*str, pattern + 1, class_end)) return false;
|
||||
pattern = class_end + 1;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
if (*str != '[') return false;
|
||||
pattern++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
if (*pattern != *str) return false;
|
||||
pattern++;
|
||||
str++;
|
||||
}
|
||||
if (*pattern == *str) {
|
||||
return glob_match(pattern + 1, str + 1);
|
||||
}
|
||||
return false;
|
||||
return *str == '\0';
|
||||
}
|
||||
|
||||
bool glob_match(const std::string & pattern, const std::string & str) {
|
||||
@@ -917,9 +924,13 @@ std::string fs_path_to_utf8(const std::filesystem::path & path) {
|
||||
}
|
||||
|
||||
void fs_write_atomic(const std::filesystem::path & path, const std::string & data) {
|
||||
// a random suffix gives every writer its own temporary file, across processes and containers
|
||||
std::random_device rd;
|
||||
const uint64_t id = ((uint64_t) rd() << 32) | rd();
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::path path_tmp = path;
|
||||
path_tmp += ".tmp";
|
||||
path_tmp += "." + std::to_string(id) + ".tmp";
|
||||
|
||||
if (path.has_parent_path()) {
|
||||
std::filesystem::create_directories(path.parent_path(), ec);
|
||||
@@ -1176,6 +1187,9 @@ static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NA
|
||||
{ COMMON_DECISION_TYPE_LFM2_D1_OMNI, "lfm2-d1-omni" },
|
||||
{ COMMON_DECISION_TYPE_GRANITE_SWITCH, "granite-switch"},
|
||||
{ COMMON_DECISION_TYPE_H2O_LIGHTNING, "h2o-lightning" },
|
||||
{ COMMON_DECISION_TYPE_MINICPM_OCR_JEV, "openbmb-minicpm-ocr-jev" },
|
||||
{ COMMON_DECISION_TYPE_LFM3_D1, "lfm3-d1" },
|
||||
{ COMMON_DECISION_TYPE_LFM3_D1_OMNI, "lfm3-d1-omni" },
|
||||
// for future models, ALWAYS add org name prefix to avoid name collisions, e.g. "orgname-modelname"
|
||||
};
|
||||
|
||||
@@ -1241,44 +1255,50 @@ common_gguf_info common_get_gguf_info(const std::string & fname) {
|
||||
return info;
|
||||
}
|
||||
|
||||
void common_fit_apply(common_params & params, llama_model_params & mparams, llama_context_params & cparams) {
|
||||
if (!params.fit_params) {
|
||||
return;
|
||||
}
|
||||
|
||||
COM_TRC("%s", "fitting params to device memory ...\n");
|
||||
COM_TRC("%s", "(for bugs during this step try to reproduce them with -fit off, or provide --verbose logs if the bug only occurs with -fit on)\n");
|
||||
|
||||
// the draft context is created from the same base params and follows the main context, fit both together
|
||||
const bool has_draft = params.speculative.has_dft();
|
||||
const bool spec_mtp = std::find(params.speculative.types.begin(), params.speculative.types.end(),
|
||||
COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
|
||||
|
||||
common_params params_dft = common_base_params_to_speculative(params);
|
||||
|
||||
auto mparams_dft = common_model_params_to_llama(params_dft);
|
||||
auto cparams_dft = common_context_params_to_llama(params_dft);
|
||||
if (spec_mtp) {
|
||||
cparams_dft.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
|
||||
}
|
||||
cparams_dft.n_rs_seq = 0;
|
||||
|
||||
const common_fit_extra_model extra = {
|
||||
/*.path_model =*/ params_dft.model.path.c_str(),
|
||||
/*.mparams =*/ &mparams_dft,
|
||||
/*.cparams =*/ &cparams_dft,
|
||||
/*.shares_model =*/ !has_draft, // an MTP context runs on the weights of the main model
|
||||
};
|
||||
|
||||
common_fit_params(params.model.path.c_str(), &mparams, &cparams,
|
||||
params.tensor_split,
|
||||
params.tensor_buft_overrides.data(),
|
||||
params.fit_params_target.data(),
|
||||
params.fit_params_min_ctx,
|
||||
has_draft || spec_mtp ? &extra : nullptr,
|
||||
params.verbosity >= LOG_LEVEL_DEBUG ? GGML_LOG_LEVEL_DEBUG : GGML_LOG_LEVEL_ERROR);
|
||||
}
|
||||
|
||||
common_init_result::common_init_result(common_params & params, bool model_only) :
|
||||
pimpl(new impl{}) {
|
||||
auto mparams = common_model_params_to_llama(params);
|
||||
auto cparams = common_context_params_to_llama(params);
|
||||
|
||||
if (params.fit_params) {
|
||||
COM_TRC("%s", "fitting params to device memory ...\n");
|
||||
COM_TRC("%s", "(for bugs during this step try to reproduce them with -fit off, or provide --verbose logs if the bug only occurs with -fit on)\n");
|
||||
|
||||
// the draft context is created from the same base params and follows the main context, fit both together
|
||||
const bool has_draft = params.speculative.has_dft();
|
||||
const bool spec_mtp = std::find(params.speculative.types.begin(), params.speculative.types.end(),
|
||||
COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
|
||||
|
||||
common_params params_dft = common_base_params_to_speculative(params);
|
||||
|
||||
auto mparams_dft = common_model_params_to_llama(params_dft);
|
||||
auto cparams_dft = common_context_params_to_llama(params_dft);
|
||||
if (spec_mtp) {
|
||||
cparams_dft.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
|
||||
}
|
||||
cparams_dft.n_rs_seq = 0;
|
||||
|
||||
const common_fit_extra_model extra = {
|
||||
/*.path_model =*/ params_dft.model.path.c_str(),
|
||||
/*.mparams =*/ &mparams_dft,
|
||||
/*.cparams =*/ &cparams_dft,
|
||||
/*.shares_model =*/ !has_draft, // an MTP context runs on the weights of the main model
|
||||
};
|
||||
|
||||
common_fit_params(params.model.path.c_str(), &mparams, &cparams,
|
||||
params.tensor_split,
|
||||
params.tensor_buft_overrides.data(),
|
||||
params.fit_params_target.data(),
|
||||
params.fit_params_min_ctx,
|
||||
has_draft || spec_mtp ? &extra : nullptr,
|
||||
params.verbosity >= LOG_LEVEL_DEBUG ? GGML_LOG_LEVEL_DEBUG : GGML_LOG_LEVEL_ERROR);
|
||||
}
|
||||
common_fit_apply(params, mparams, cparams);
|
||||
|
||||
llama_model * model = llama_model_load_from_file(params.model.path.c_str(), mparams);
|
||||
if (model == NULL) {
|
||||
@@ -1296,13 +1316,17 @@ common_init_result::common_init_result(common_params & params, bool model_only)
|
||||
// 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);
|
||||
// minicpm-ocr-jev reads its class scores from the pooled output, the pooling type is the one of the GGUF
|
||||
if (decision_type == COMMON_DECISION_TYPE_LAYA || decision_type == COMMON_DECISION_TYPE_KEV || decision_type == COMMON_DECISION_TYPE_CLEF ||
|
||||
decision_type == COMMON_DECISION_TYPE_LFM2_D1_OMNI || decision_type == COMMON_DECISION_TYPE_VLLM_DECISION2) {
|
||||
decision_type == COMMON_DECISION_TYPE_LFM2_D1_OMNI || decision_type == COMMON_DECISION_TYPE_VLLM_DECISION2 ||
|
||||
decision_type == COMMON_DECISION_TYPE_MINICPM_OCR_JEV) {
|
||||
const auto pooling_type = decision_type == COMMON_DECISION_TYPE_MINICPM_OCR_JEV ? LLAMA_POOLING_TYPE_UNSPECIFIED : LLAMA_POOLING_TYPE_NONE;
|
||||
|
||||
params.embedding = true;
|
||||
params.pooling_type = LLAMA_POOLING_TYPE_NONE;
|
||||
params.pooling_type = pooling_type;
|
||||
|
||||
cparams.embeddings = true;
|
||||
cparams.pooling_type = LLAMA_POOLING_TYPE_NONE;
|
||||
cparams.pooling_type = pooling_type;
|
||||
cparams.n_outputs_max = cparams.n_batch;
|
||||
cparams.n_outputs_max_per_seq = 1;
|
||||
|
||||
@@ -1449,7 +1473,7 @@ common_init_result_ptr common_init_from_params(common_params & params, bool mode
|
||||
if (params.control_vector_layer_start <= 0) params.control_vector_layer_start = 1;
|
||||
if (params.control_vector_layer_end <= 0) params.control_vector_layer_end = llama_model_n_layer(model);
|
||||
|
||||
const auto cvec = common_control_vector_load(params.control_vectors);
|
||||
const auto cvec = common_control_vector_load(params.control_vectors, llama_model_n_layer(model));
|
||||
if (cvec.n_embd == -1) {
|
||||
return res;
|
||||
}
|
||||
@@ -1971,7 +1995,7 @@ float common_embd_similarity_cos(const float * embd1, const float * embd2, int n
|
||||
// Control vector utils
|
||||
//
|
||||
|
||||
static common_control_vector_data common_control_vector_load_one(const common_control_vector_load_info & load_info) {
|
||||
static common_control_vector_data common_control_vector_load_one(const common_control_vector_load_info & load_info, int32_t n_layer) {
|
||||
common_control_vector_data result = { -1, {} };
|
||||
|
||||
ggml_context * ctx = nullptr;
|
||||
@@ -2012,6 +2036,10 @@ static common_control_vector_data common_control_vector_load_one(const common_co
|
||||
COM_ERR("invalid (zero) direction tensor layer index in %s\n", load_info.fname.c_str());
|
||||
result.n_embd = -1;
|
||||
break;
|
||||
} else if (layer_idx > n_layer) {
|
||||
COM_ERR("direction tensor layer index %d exceeds the model layer count %d in %s\n", layer_idx, n_layer, load_info.fname.c_str());
|
||||
result.n_embd = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
struct ggml_tensor * tensor = ggml_get_tensor(ctx, name.c_str());
|
||||
@@ -2035,10 +2063,10 @@ static common_control_vector_data common_control_vector_load_one(const common_co
|
||||
}
|
||||
|
||||
// extend if necessary - do not store data for layer 0 (it's not used)
|
||||
result.data.resize(std::max(result.data.size(), static_cast<size_t>(result.n_embd * layer_idx)), 0.0f);
|
||||
result.data.resize(std::max(result.data.size(), (size_t) result.n_embd * layer_idx), 0.0f);
|
||||
|
||||
const float * src = (const float *) tensor->data;
|
||||
float * dst = result.data.data() + result.n_embd * (layer_idx - 1); // layer 1 at [0]
|
||||
float * dst = result.data.data() + (size_t) result.n_embd * (layer_idx - 1); // layer 1 at [0]
|
||||
for (int j = 0; j < result.n_embd; j++) {
|
||||
dst[j] += src[j] * load_info.strength; // allows multiple directions for same layer in same file
|
||||
}
|
||||
@@ -2056,11 +2084,11 @@ static common_control_vector_data common_control_vector_load_one(const common_co
|
||||
return result;
|
||||
}
|
||||
|
||||
common_control_vector_data common_control_vector_load(const std::vector<common_control_vector_load_info> & load_infos) {
|
||||
common_control_vector_data common_control_vector_load(const std::vector<common_control_vector_load_info> & load_infos, int32_t n_layer) {
|
||||
common_control_vector_data result = { -1, {} };
|
||||
|
||||
for (const auto & info : load_infos) {
|
||||
auto cur = common_control_vector_load_one(info);
|
||||
auto cur = common_control_vector_load_one(info, n_layer);
|
||||
|
||||
if (cur.n_embd == -1) {
|
||||
result.n_embd = -1;
|
||||
|
||||
+12
-1
@@ -31,6 +31,8 @@
|
||||
#define DIRECTORY_SEPARATOR '/'
|
||||
#endif // _WIN32
|
||||
|
||||
#define COMMON_RECURSIVE_LIMIT 128
|
||||
|
||||
#define COM_DBG(fmt, ...) LOG_DBG("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
|
||||
#define COM_TRC(fmt, ...) LOG_TRC("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
|
||||
#define COM_INF(fmt, ...) LOG_INF("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
|
||||
@@ -699,6 +701,7 @@ struct common_params {
|
||||
std::string models_dir = ""; // directory containing models for the router server
|
||||
std::string models_preset = ""; // directory containing model presets for the router server
|
||||
int models_max = 4; // maximum number of models to load simultaneously
|
||||
int models_memory_margin = 0; // MiB of device memory to keep free when loading models (0 = no memory accounting)
|
||||
bool models_autoload = true; // automatically load models when requested via the router server
|
||||
std::string models_preset_hf = ""; // show a warning about remote presets on router loaded (if not empty)
|
||||
|
||||
@@ -769,6 +772,7 @@ struct common_params {
|
||||
|
||||
// TTS params
|
||||
std::string tts_lang = "";
|
||||
std::string tts_voice = "";
|
||||
std::string tts_speaker_file = "";
|
||||
|
||||
bool is_gen_docs = false; // whether we are running inside llama-gen-docs
|
||||
@@ -976,6 +980,9 @@ enum common_decision_type {
|
||||
COMMON_DECISION_TYPE_LFM2_D1_OMNI, // same as laya, other prompt layout
|
||||
COMMON_DECISION_TYPE_GRANITE_SWITCH, // same as openjev, run by the decision aLoRA of granite switch
|
||||
COMMON_DECISION_TYPE_H2O_LIGHTNING, // same as openjev, the labels are read after an answer prefill
|
||||
COMMON_DECISION_TYPE_MINICPM_OCR_JEV, // one score per class, the pooled embeddings output of the last token
|
||||
COMMON_DECISION_TYPE_LFM3_D1, // same as lfm2-d1, non-causal, label codes A..Z then AA, AB..
|
||||
COMMON_DECISION_TYPE_LFM3_D1_OMNI, // same as lfm3-d1
|
||||
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
|
||||
};
|
||||
|
||||
@@ -1015,6 +1022,9 @@ common_init_result_ptr common_init_from_params(common_params & params, bool mode
|
||||
struct llama_model_params common_model_params_to_llama ( common_params & params);
|
||||
struct llama_context_params common_context_params_to_llama(const common_params & params);
|
||||
|
||||
// apply --fit to mparams and cparams, no-op if params.fit_params is false
|
||||
void common_fit_apply(common_params & params, struct llama_model_params & mparams, struct llama_context_params & cparams);
|
||||
|
||||
// clear LoRA adapters from context, then apply new list of adapters
|
||||
void common_set_adapter_lora(struct llama_context * ctx, std::vector<common_adapter_lora_info> & lora);
|
||||
|
||||
@@ -1227,8 +1237,9 @@ struct common_control_vector_load_info {
|
||||
};
|
||||
|
||||
// Load control vectors, scale each by strength, and add them together.
|
||||
// Direction layer indices must be in [1, n_layer].
|
||||
// On error, returns {-1, empty}
|
||||
common_control_vector_data common_control_vector_load(const std::vector<common_control_vector_load_info> & load_infos);
|
||||
common_control_vector_data common_control_vector_load(const std::vector<common_control_vector_load_info> & load_infos, int32_t n_layer);
|
||||
|
||||
//
|
||||
// Split utils
|
||||
|
||||
+77
-28
@@ -4,6 +4,7 @@
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
#include "download.h"
|
||||
#include "file-lock.h"
|
||||
#include "hf-cache.h"
|
||||
#include "json.h"
|
||||
|
||||
@@ -77,6 +78,18 @@ std::pair<std::string, std::string> common_download_split_repo_tag(const std::st
|
||||
return {hf_repo, tag};
|
||||
}
|
||||
|
||||
// file name from url, without the query string
|
||||
static std::string url_get_filename(const std::string & url) {
|
||||
std::string filename = url;
|
||||
if (auto pos = filename.rfind('/'); pos != std::string::npos) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
if (auto pos = filename.find('?'); pos != std::string::npos) {
|
||||
filename = filename.substr(0, pos);
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
class ProgressBar : public common_download_callback {
|
||||
static inline std::mutex mutex;
|
||||
static inline std::map<const ProgressBar *, int> lines;
|
||||
@@ -100,14 +113,8 @@ public:
|
||||
ProgressBar() = default;
|
||||
|
||||
void on_start(const common_download_progress & p) override {
|
||||
filename = p.url;
|
||||
filename = url_get_filename(p.url);
|
||||
|
||||
if (auto pos = filename.rfind('/'); pos != std::string::npos) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
if (auto pos = filename.find('?'); pos != std::string::npos) {
|
||||
filename = filename.substr(0, pos);
|
||||
}
|
||||
for (size_t i = 0; i < filename.size(); ++i) {
|
||||
if ((filename[i] & 0xC0) != 0x80) {
|
||||
if (len++ == 39) {
|
||||
@@ -257,15 +264,62 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
static int common_download_file_single_online(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts,
|
||||
bool skip_etag) {
|
||||
bool skip_etag,
|
||||
const std::string & lock_path,
|
||||
const std::string & alt_path) {
|
||||
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 std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
const std::string path_lock = lock_path.empty() ? path + ".lock" : lock_path;
|
||||
|
||||
common_download_progress p;
|
||||
p.url = url;
|
||||
|
||||
if (opts.callback) {
|
||||
opts.callback->on_start(p);
|
||||
}
|
||||
|
||||
// the completion callback may itself take the same lock (the HF cache
|
||||
// finalizes blobs under it), so every exit goes through finish()
|
||||
common_file_lock lock(path_lock);
|
||||
auto finish = [&](int status) {
|
||||
lock.close();
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, is_http_status_ok(status));
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
// alt_path is the HF snapshot: the blob may have been moved there when symlinks are unavailable
|
||||
auto cache_hit = [&] {
|
||||
return std::filesystem::exists(std::filesystem::u8path(path)) ||
|
||||
(!alt_path.empty() && std::filesystem::exists(std::filesystem::u8path(alt_path)));
|
||||
};
|
||||
|
||||
if (skip_etag && cache_hit()) {
|
||||
LOG_DBG("%s: using cached file: %s\n", __func__, path.c_str());
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
|
||||
// take the lock before touching the cache, so two processes cannot race on the same file
|
||||
bool waited = false;
|
||||
if (!lock.acquire([&] {
|
||||
if (!waited) {
|
||||
LOG_INF("%s: file '%s' is being downloaded by another process, waiting...\n", __func__, url_get_filename(url).c_str());
|
||||
waited = true;
|
||||
}
|
||||
return !(opts.callback && opts.callback->is_cancelled());
|
||||
})) {
|
||||
return finish(-1);
|
||||
}
|
||||
|
||||
// under the lock: another process may have finished while we waited
|
||||
bool file_exists = cache_hit();
|
||||
|
||||
if (file_exists && skip_etag) {
|
||||
LOG_DBG("%s: using cached file: %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
|
||||
auto [cli, parts] = common_http_client(url);
|
||||
@@ -294,9 +348,9 @@ static int common_download_file_single_online(const std::string & url,
|
||||
LOG_TRC("%s: HEAD failed, status: %d\n", __func__, head ? head->status : -1);
|
||||
if (file_exists) {
|
||||
LOG_TRC("%s: using cached file (HEAD failed): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
return head ? head->status : -1;
|
||||
return finish(head ? head->status : -1);
|
||||
}
|
||||
|
||||
std::string etag;
|
||||
@@ -304,8 +358,6 @@ static int common_download_file_single_online(const std::string & url,
|
||||
etag = head->get_header_value("ETag");
|
||||
}
|
||||
|
||||
common_download_progress p;
|
||||
p.url = url;
|
||||
if (head->has_header("Content-Length")) {
|
||||
try {
|
||||
p.total = std::stoull(head->get_header_value("Content-Length"));
|
||||
@@ -322,16 +374,16 @@ static int common_download_file_single_online(const std::string & url,
|
||||
if (file_exists) {
|
||||
if (etag.empty()) {
|
||||
LOG_DBG("%s: using cached file (no server etag): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
if (!last_etag.empty() && last_etag == etag) {
|
||||
LOG_DBG("%s: using cached file (same etag): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
// pass this point, the file exists but is different from the server version, so we need to redownload it
|
||||
if (remove(path.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path.c_str());
|
||||
return -1;
|
||||
return finish(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +393,6 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
const std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
int delay = retry_delay_seconds;
|
||||
|
||||
if (!skip_etag && !etag.empty()) {
|
||||
@@ -349,7 +400,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
LOG_WRN("%s: partial file does not match the server etag, discarding it: %s\n", __func__, path_temporary.c_str());
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
|
||||
return -1;
|
||||
return finish(-1);
|
||||
}
|
||||
}
|
||||
write_etag(path_temporary, etag);
|
||||
@@ -359,7 +410,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
// report the resume offset, so a resumed file does not start at 0
|
||||
p.downloaded = supports_ranges && std::filesystem::exists(path_temporary) ?
|
||||
std::filesystem::file_size(path_temporary) : 0;
|
||||
opts.callback->on_start(p);
|
||||
opts.callback->on_update(p);
|
||||
}
|
||||
|
||||
for (int i = 0; i < max_attempts; ++i) {
|
||||
@@ -403,16 +454,12 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, success);
|
||||
}
|
||||
// a stopped download keeps path_temporary, so the next attempt resumes from it
|
||||
if (!success) {
|
||||
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
|
||||
return -1; // max attempts reached
|
||||
}
|
||||
|
||||
return head->status;
|
||||
return finish(success ? head->status : -1);
|
||||
}
|
||||
|
||||
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url,
|
||||
@@ -452,14 +499,16 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string
|
||||
int common_download_file_single(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts,
|
||||
bool skip_etag) {
|
||||
bool skip_etag,
|
||||
const std::string & lock_path,
|
||||
const std::string & alt_path) {
|
||||
if (!opts.offline) {
|
||||
ProgressBar tty_cb;
|
||||
common_download_opts online_opts = opts;
|
||||
if (!online_opts.callback) {
|
||||
online_opts.callback = &tty_cb;
|
||||
}
|
||||
return common_download_file_single_online(url, path, online_opts, skip_etag);
|
||||
return common_download_file_single_online(url, path, online_opts, skip_etag, lock_path, alt_path);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(std::filesystem::u8path(path))) {
|
||||
@@ -821,7 +870,7 @@ void common_download_run_tasks(const std::vector<common_download_task> & tasks)
|
||||
for (const auto & task : tasks) {
|
||||
futures.push_back(std::async(std::launch::async,
|
||||
[&task]() {
|
||||
return common_download_file_single(task.url, task.local_path, task.opts, task.is_hf);
|
||||
return common_download_file_single(task.url, task.local_path, task.opts, task.is_hf, task.lock_path, task.alt_path);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
+9
-2
@@ -69,12 +69,15 @@ struct common_download_task {
|
||||
std::string local_path;
|
||||
std::function<void()> on_done;
|
||||
bool is_hf = false;
|
||||
std::string lock_path;
|
||||
std::string alt_path; // HF snapshot path, also treated as cached (the blob may be moved there)
|
||||
|
||||
common_download_task() = default;
|
||||
common_download_task(hf_cache::hf_file f,
|
||||
const common_download_opts & opts,
|
||||
std::function<void()> on_done = nullptr)
|
||||
: opts(opts), url(f.url), local_path(f.local_path), on_done(on_done), is_hf(true) {}
|
||||
: opts(opts), url(f.url), local_path(f.local_path), on_done(on_done), is_hf(true),
|
||||
lock_path(hf_cache::get_lock_path(f.repo_id, f.oid)), alt_path(f.final_path) {}
|
||||
};
|
||||
|
||||
void common_download_run_tasks(const std::vector<common_download_task> & tasks);
|
||||
@@ -92,10 +95,14 @@ std::string common_download_resolve_path(const std::string & hf_repo_with_tag, c
|
||||
// download single file from url to local path
|
||||
// returns status code or -1 on error
|
||||
// skip_etag: if true, don't read/write .etag files (for HF cache where filename is the hash)
|
||||
// lock_path: lock file used to serialize downloads of the same file; when empty, "<path>.lock" is used
|
||||
// alt_path: additional path treated as present in cache (see cache_hit in download.cpp)
|
||||
int common_download_file_single(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts = {},
|
||||
bool skip_etag = false);
|
||||
bool skip_etag = false,
|
||||
const std::string & lock_path = "",
|
||||
const std::string & alt_path = "");
|
||||
|
||||
// resolve and download model from Docker registry
|
||||
// return local path to downloaded model file
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "file-lock.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
common_file_lock::common_file_lock(const std::string & lock_path) {
|
||||
this->lock_path = lock_path;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// the \\?\ prefix (for paths longer than 255 chars) is only accepted on absolute paths
|
||||
// with backslash separators: normalize first, then check the length of the absolute form
|
||||
static std::wstring as_extended_path(const std::string & path) {
|
||||
// convert from the narrow encoding std::filesystem uses on this system
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, path.c_str(), (int) path.size(), nullptr, 0);
|
||||
if (wlen <= 0) {
|
||||
return std::wstring(path.begin(), path.end()); // best effort for exotic encodings
|
||||
}
|
||||
std::wstring wide(wlen, L'\0');
|
||||
MultiByteToWideChar(CP_ACP, 0, path.c_str(), (int) path.size(), wide.data(), wlen);
|
||||
|
||||
if (wide.rfind(L"\\\\?\\", 0) == 0) {
|
||||
return wide;
|
||||
}
|
||||
|
||||
// GetFullPathNameW resolves relative paths and normalizes separators
|
||||
DWORD size = GetFullPathNameW(wide.c_str(), 0, nullptr, nullptr);
|
||||
if (size == 0) {
|
||||
return wide;
|
||||
}
|
||||
std::wstring full(size, L'\0');
|
||||
DWORD written = GetFullPathNameW(wide.c_str(), size, full.data(), nullptr);
|
||||
if (written == 0 || written >= size) {
|
||||
return wide;
|
||||
}
|
||||
full.resize(written);
|
||||
|
||||
if (full.size() <= 255) {
|
||||
return wide; // short enough: open the path exactly as given
|
||||
}
|
||||
if (full.rfind(L"\\\\", 0) == 0) {
|
||||
return L"\\\\?\\UNC\\" + full.substr(2); // \\server\share -> \\?\UNC\server\share
|
||||
}
|
||||
return L"\\\\?\\" + full;
|
||||
}
|
||||
#endif
|
||||
|
||||
common_file_lock::status common_file_lock::try_lock() {
|
||||
if (lock_path.empty()) {
|
||||
return status::acquired; // no lock path, nothing to lock
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (fd < 0) {
|
||||
// opened with a wide path: extended paths cannot go through the narrow CRT
|
||||
std::wstring lock_path_w = as_extended_path(lock_path);
|
||||
fd = _wopen(lock_path_w.c_str(), _O_RDWR | _O_CREAT | _O_BINARY | _O_NOINHERIT, _S_IREAD | _S_IWRITE);
|
||||
if (fd < 0) {
|
||||
LOG_ERR("%s: cannot open lock file: %s\n", __func__, lock_path.c_str());
|
||||
return status::error;
|
||||
}
|
||||
}
|
||||
// lock the first byte, the file itself stays empty
|
||||
OVERLAPPED ov = {};
|
||||
locked = LockFileEx((HANDLE) _get_osfhandle(fd),
|
||||
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov);
|
||||
if (locked) {
|
||||
return status::acquired;
|
||||
}
|
||||
if (GetLastError() == ERROR_LOCK_VIOLATION) {
|
||||
return status::busy;
|
||||
}
|
||||
LOG_ERR("%s: file locking failed (%d)\n", __func__, (int) GetLastError());
|
||||
return status::error;
|
||||
#else
|
||||
if (fd < 0) {
|
||||
int open_flags = O_RDWR | O_CREAT | O_CLOEXEC;
|
||||
#ifdef O_NOFOLLOW
|
||||
open_flags |= O_NOFOLLOW;
|
||||
#endif
|
||||
// group-writable, so users sharing a cache can wait on the lock
|
||||
fd = open(lock_path.c_str(), open_flags, 0664);
|
||||
if (fd < 0) {
|
||||
LOG_ERR("%s: cannot open lock file: %s\n", __func__, lock_path.c_str());
|
||||
return status::error;
|
||||
}
|
||||
// open() filters the mode through the umask
|
||||
(void) fchmod(fd, 0664);
|
||||
}
|
||||
|
||||
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
|
||||
locked = true;
|
||||
return status::acquired;
|
||||
}
|
||||
// EACCES is reported as contention on some filesystems
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EACCES) {
|
||||
return status::busy;
|
||||
}
|
||||
// flock is not implemented (some NFS/FUSE mounts): report it instead of racing
|
||||
LOG_ERR("%s: file locking failed (%s)\n", __func__, strerror(errno));
|
||||
return status::error;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool common_file_lock::acquire(const std::function<bool()> & keep_waiting) {
|
||||
// the lock file is opened by try_lock, its parent must exist by then
|
||||
if (!lock_path.empty()) {
|
||||
std::error_code ec;
|
||||
auto lock_dir = std::filesystem::path(lock_path).parent_path();
|
||||
if (!lock_dir.empty()) {
|
||||
std::filesystem::create_directories(lock_dir, ec);
|
||||
if (ec) {
|
||||
LOG_ERR("%s: unable to create lock directory '%s': %s\n", __func__, lock_dir.string().c_str(), ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
switch (try_lock()) {
|
||||
case status::acquired:
|
||||
return true;
|
||||
case status::busy:
|
||||
if (keep_waiting && !keep_waiting()) {
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
case status::error:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the lock file is never deleted: another process could then create a fresh
|
||||
// file at the same path and lock that
|
||||
void common_file_lock::close() {
|
||||
#if defined(_WIN32)
|
||||
if (fd >= 0) {
|
||||
if (locked) {
|
||||
OVERLAPPED ov = {};
|
||||
UnlockFileEx((HANDLE) _get_osfhandle(fd), 0, 1, 0, &ov);
|
||||
}
|
||||
_close(fd);
|
||||
}
|
||||
#else
|
||||
if (fd >= 0) {
|
||||
if (locked) {
|
||||
flock(fd, LOCK_UN);
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
fd = -1;
|
||||
locked = false;
|
||||
}
|
||||
|
||||
common_file_lock::~common_file_lock() {
|
||||
close();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
// Cross-process exclusive lock (flock on POSIX, LockFileEx on Windows).
|
||||
// Lazy: acquire() opens the lock file, so a read-only cache works if the lock is never acquired.
|
||||
class common_file_lock {
|
||||
public:
|
||||
common_file_lock(const std::string & lock_path);
|
||||
~common_file_lock();
|
||||
|
||||
// take the lock, waiting while another process holds it;
|
||||
// keep_waiting is polled before each retry, false cancels the wait.
|
||||
// Also returns false when locking is unavailable.
|
||||
bool acquire(const std::function<bool()> & keep_waiting = {});
|
||||
|
||||
void close();
|
||||
|
||||
common_file_lock(const common_file_lock &) = delete;
|
||||
common_file_lock & operator=(const common_file_lock &) = delete;
|
||||
|
||||
private:
|
||||
enum class status {
|
||||
acquired,
|
||||
busy, // another process holds the lock
|
||||
error, // file locking is unavailable: no exclusion is possible
|
||||
};
|
||||
|
||||
status try_lock();
|
||||
|
||||
std::string lock_path;
|
||||
|
||||
int fd = -1;
|
||||
bool locked = false;
|
||||
};
|
||||
+197
-103
@@ -1,6 +1,8 @@
|
||||
#include "fit.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "json.h"
|
||||
#include "llama-cpp.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "../src/llama-ext.h"
|
||||
@@ -27,6 +29,32 @@ class common_params_fit_exception : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
// RAII: send llama logs below min_level to the debug log
|
||||
struct common_fit_log_scope {
|
||||
common_fit_log_scope(ggml_log_level min_level) : min_level(min_level) {
|
||||
llama_log_get(&original_logger.callback, &original_logger.user_data);
|
||||
llama_log_set([](ggml_log_level level, const char * text, void * user_data) {
|
||||
const common_fit_log_scope * self = (const common_fit_log_scope *) user_data;
|
||||
const ggml_log_level level_eff = level >= self->min_level ? level : GGML_LOG_LEVEL_DEBUG;
|
||||
self->original_logger.callback(level_eff, text, self->original_logger.user_data);
|
||||
}, this);
|
||||
}
|
||||
|
||||
~common_fit_log_scope() {
|
||||
llama_log_set(original_logger.callback, original_logger.user_data);
|
||||
}
|
||||
|
||||
common_fit_log_scope(const common_fit_log_scope &) = delete;
|
||||
common_fit_log_scope & operator=(const common_fit_log_scope &) = delete;
|
||||
|
||||
private:
|
||||
struct {
|
||||
ggml_log_callback callback;
|
||||
void * user_data;
|
||||
} original_logger;
|
||||
ggml_log_level min_level;
|
||||
};
|
||||
|
||||
static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
const char * path_model,
|
||||
const llama_model_params * mparams,
|
||||
@@ -36,22 +64,7 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
uint32_t & hp_n_ctx_train,
|
||||
uint32_t & hp_n_expert,
|
||||
ggml_log_level log_level) {
|
||||
struct user_data_t {
|
||||
struct {
|
||||
ggml_log_callback callback;
|
||||
void * user_data;
|
||||
} original_logger;
|
||||
ggml_log_level min_level; // prints below this log level go to debug log
|
||||
};
|
||||
user_data_t ud;
|
||||
llama_log_get(&ud.original_logger.callback, &ud.original_logger.user_data);
|
||||
ud.min_level = log_level;
|
||||
|
||||
llama_log_set([](ggml_log_level level, const char * text, void * user_data) {
|
||||
const user_data_t * ud = (const user_data_t *) user_data;
|
||||
const ggml_log_level level_eff = level >= ud->min_level ? level : GGML_LOG_LEVEL_DEBUG;
|
||||
ud->original_logger.callback(level_eff, text, ud->original_logger.user_data);
|
||||
}, &ud);
|
||||
common_fit_log_scope log_scope(log_level);
|
||||
|
||||
llama_model_params mparams_copy = *mparams;
|
||||
mparams_copy.no_alloc = true;
|
||||
@@ -59,14 +72,12 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
|
||||
llama_model * model = llama_model_load_from_file(path_model, mparams_copy);
|
||||
if (model == nullptr) {
|
||||
llama_log_set(ud.original_logger.callback, ud.original_logger.user_data);
|
||||
throw std::runtime_error("failed to load model");
|
||||
}
|
||||
|
||||
llama_context * ctx = llama_init_from_model(model, *cparams);
|
||||
if (ctx == nullptr) {
|
||||
llama_model_free(model);
|
||||
llama_log_set(ud.original_logger.callback, ud.original_logger.user_data);
|
||||
throw std::runtime_error("failed to create llama_context from model");
|
||||
}
|
||||
|
||||
@@ -148,11 +159,81 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
|
||||
llama_free(ctx);
|
||||
llama_model_free(model);
|
||||
llama_log_set(ud.original_logger.callback, ud.original_logger.user_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<common_memory_breakdown_row> common_fit_measure(
|
||||
const char * path_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
uint32_t * n_ctx_train,
|
||||
uint32_t * n_ctx,
|
||||
ggml_log_level log_level) {
|
||||
common_fit_log_scope log_scope(log_level);
|
||||
|
||||
llama_model_params mparams_copy = *mparams;
|
||||
mparams_copy.no_alloc = true;
|
||||
mparams_copy.load_mode = LLAMA_LOAD_MODE_NONE;
|
||||
|
||||
llama_model_ptr model(llama_model_load_from_file(path_model, mparams_copy));
|
||||
if (!model) {
|
||||
throw std::runtime_error("failed to load model");
|
||||
}
|
||||
|
||||
llama_context_ptr ctx(llama_init_from_model(model.get(), *cparams));
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("failed to create llama_context from model");
|
||||
}
|
||||
|
||||
if (n_ctx_train) {
|
||||
*n_ctx_train = llama_model_n_ctx_train(model.get());
|
||||
}
|
||||
if (n_ctx) {
|
||||
*n_ctx = llama_n_ctx(ctx.get());
|
||||
}
|
||||
|
||||
return common_memory_breakdown_rows(ctx.get());
|
||||
}
|
||||
|
||||
common_memory_reports common_measure_memory(common_params & params) {
|
||||
const ggml_log_level log_level = params.verbosity >= LOG_LEVEL_DEBUG ? GGML_LOG_LEVEL_DEBUG : GGML_LOG_LEVEL_ERROR;
|
||||
const char * path_model = params.model.path.c_str();
|
||||
|
||||
common_memory_reports res;
|
||||
|
||||
auto mparams = common_model_params_to_llama(params);
|
||||
auto cparams = common_context_params_to_llama(params);
|
||||
|
||||
// max: what --fit picks now, no-op with -fit off
|
||||
{
|
||||
auto mparams_fit = mparams;
|
||||
auto cparams_fit = cparams;
|
||||
common_fit_apply(params, mparams_fit, cparams_fit);
|
||||
res.info_max = common_fit_measure(path_model, &mparams_fit, &cparams_fit, &res.n_ctx_train, &res.n_ctx_max, log_level);
|
||||
}
|
||||
|
||||
// min: the smallest context --fit may pick (--fit only changes n_ctx when it is 0)
|
||||
uint32_t n_ctx_min = cparams.n_ctx;
|
||||
if (n_ctx_min == 0 && params.fit_params) {
|
||||
const uint32_t n_streams = cparams.kv_unified ? 1 : std::max<uint32_t>(1, cparams.n_seq_max);
|
||||
// -c 0 sets fit_params_min_ctx to UINT32_MAX, cap at n_ctx_train like --fit
|
||||
n_ctx_min = (uint32_t) std::min<uint64_t>(
|
||||
uint64_t(params.fit_params_min_ctx) * n_streams,
|
||||
uint64_t(res.n_ctx_train) * n_streams);
|
||||
}
|
||||
if (n_ctx_min == 0 || n_ctx_min >= res.n_ctx_max) {
|
||||
// nothing to reduce, or -fit off with the trained context
|
||||
res.n_ctx_min = res.n_ctx_max;
|
||||
res.info_min = res.info_max;
|
||||
} else {
|
||||
cparams.n_ctx = n_ctx_min;
|
||||
res.info_min = common_fit_measure(path_model, &mparams, &cparams, nullptr, &res.n_ctx_min, log_level);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
common_device_memory_data_vec common_get_device_memory_data(
|
||||
const char * path_model,
|
||||
const llama_model_params * mparams,
|
||||
@@ -903,8 +984,7 @@ enum common_params_fit_status common_fit_params(
|
||||
return status;
|
||||
}
|
||||
|
||||
void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
//const auto & devices = ctx->get_model().devices;
|
||||
std::vector<common_memory_breakdown_row> common_memory_breakdown_rows(const struct llama_context * ctx) {
|
||||
const auto * model = llama_get_model(ctx);
|
||||
|
||||
std::vector<ggml_backend_dev_t> devices;
|
||||
@@ -914,18 +994,6 @@ void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
|
||||
llama_memory_breakdown memory_breakdown = llama_get_memory_breakdown(ctx);
|
||||
|
||||
std::vector<std::array<std::string, 9>> table_data;
|
||||
table_data.reserve(devices.size());
|
||||
|
||||
// same data as the table below, for --log-jsonl consumers
|
||||
common_json rows = common_json::array();
|
||||
const std::string template_header = "%s: | %s | %s %s %s %s %s %s %s |\n";
|
||||
const std::string template_gpu = "%s: | %s | %s = %s + (%s = %s + %s + %s) + %s |\n";
|
||||
const std::string template_other = "%s: | %s | %s %s %s = %s + %s + %s %s |\n";
|
||||
|
||||
table_data.push_back({template_header, "memory breakdown [MiB]", "total", "free", "self", "model", "context", "compute", "unaccounted"});
|
||||
|
||||
constexpr size_t MiB = 1024 * 1024;
|
||||
const std::vector<std::string> desc_prefixes_strip = {"NVIDIA ", "GeForce ", "Tesla ", "AMD ", "Radeon ", "Instinct "};
|
||||
|
||||
// track seen buffer types to avoid double counting:
|
||||
@@ -964,104 +1032,130 @@ void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// print memory breakdown for each device:
|
||||
std::vector<common_memory_breakdown_row> rows;
|
||||
rows.reserve(devices.size() + 1 + memory_breakdown.size());
|
||||
|
||||
for (size_t i = 0; i < devices.size(); i++) {
|
||||
ggml_backend_dev_t dev = devices[i];
|
||||
llama_memory_breakdown_data mb = mb_dev[i];
|
||||
|
||||
const std::string name = ggml_backend_dev_name(dev);
|
||||
std::string desc = ggml_backend_dev_description(dev);
|
||||
common_memory_breakdown_row row;
|
||||
row.kind = common_memory_breakdown_row::DEVICE;
|
||||
row.name = ggml_backend_dev_name(dev);
|
||||
row.description = ggml_backend_dev_description(dev);
|
||||
for (const std::string & prefix : desc_prefixes_strip) {
|
||||
if (desc.length() >= prefix.length() && desc.substr(0, prefix.length()) == prefix) {
|
||||
desc = desc.substr(prefix.length());
|
||||
if (row.description.length() >= prefix.length() && row.description.substr(0, prefix.length()) == prefix) {
|
||||
row.description = row.description.substr(prefix.length());
|
||||
}
|
||||
}
|
||||
|
||||
size_t free, total;
|
||||
ggml_backend_dev_memory(dev, &free, &total);
|
||||
|
||||
const size_t self = mb.model + mb.context + mb.compute;
|
||||
const int64_t unaccounted = static_cast<int64_t>(total) - static_cast<int64_t>(free) - static_cast<int64_t>(self);
|
||||
|
||||
table_data.push_back({
|
||||
template_gpu,
|
||||
" - " + name + " (" + desc + ")",
|
||||
std::to_string(total / MiB),
|
||||
std::to_string(free / MiB),
|
||||
std::to_string(self / MiB),
|
||||
std::to_string(mb.model / MiB),
|
||||
std::to_string(mb.context / MiB),
|
||||
std::to_string(mb.compute / MiB),
|
||||
std::to_string(unaccounted / static_cast<int64_t>(MiB))});
|
||||
|
||||
rows.push_back({
|
||||
{"kind", "device"},
|
||||
{"name", name},
|
||||
{"description", desc},
|
||||
{"total", total / MiB},
|
||||
{"free", free / MiB},
|
||||
{"self", self / MiB},
|
||||
{"model", mb.model / MiB},
|
||||
{"context", mb.context / MiB},
|
||||
{"compute", mb.compute / MiB},
|
||||
{"unaccounted", unaccounted / static_cast<int64_t>(MiB)},
|
||||
});
|
||||
row.total = total;
|
||||
row.free = free;
|
||||
row.model = mb_dev[i].model;
|
||||
row.context = mb_dev[i].context;
|
||||
row.compute = mb_dev[i].compute;
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
// print memory breakdown for host:
|
||||
{
|
||||
const size_t self = mb_host.model + mb_host.context + mb_host.compute;
|
||||
table_data.push_back({
|
||||
template_other,
|
||||
" - Host",
|
||||
"", // total
|
||||
"", // free
|
||||
std::to_string(self / MiB),
|
||||
std::to_string(mb_host.model / MiB),
|
||||
std::to_string(mb_host.context / MiB),
|
||||
std::to_string(mb_host.compute / MiB),
|
||||
""}); // unaccounted
|
||||
|
||||
rows.push_back({
|
||||
{"kind", "host"},
|
||||
{"name", "Host"},
|
||||
{"self", self / MiB},
|
||||
{"model", mb_host.model / MiB},
|
||||
{"context", mb_host.context / MiB},
|
||||
{"compute", mb_host.compute / MiB},
|
||||
});
|
||||
common_memory_breakdown_row row;
|
||||
row.kind = common_memory_breakdown_row::HOST;
|
||||
row.name = "Host";
|
||||
row.model = mb_host.model;
|
||||
row.context = mb_host.context;
|
||||
row.compute = mb_host.compute;
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
// print memory breakdown for all remaining buffer types:
|
||||
// all remaining buffer types:
|
||||
for (const auto & buft_mb : memory_breakdown) {
|
||||
ggml_backend_buffer_type_t buft = buft_mb.first;
|
||||
const llama_memory_breakdown_data & mb = buft_mb.second;
|
||||
if (seen_buffer_types.count(buft) == 1) {
|
||||
continue;
|
||||
}
|
||||
const std::string name = ggml_backend_buft_name(buft);
|
||||
const size_t self = mb.model + mb.context + mb.compute;
|
||||
common_memory_breakdown_row row;
|
||||
row.kind = common_memory_breakdown_row::BUFFER_TYPE;
|
||||
row.name = ggml_backend_buft_name(buft);
|
||||
row.model = mb.model;
|
||||
row.context = mb.context;
|
||||
row.compute = mb.compute;
|
||||
rows.push_back(std::move(row));
|
||||
|
||||
seen_buffer_types.insert(buft);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
const std::vector<common_memory_breakdown_row> rows = common_memory_breakdown_rows(ctx);
|
||||
|
||||
std::vector<std::array<std::string, 9>> table_data;
|
||||
table_data.reserve(rows.size() + 1);
|
||||
|
||||
// same data as the table below, for --log-jsonl consumers
|
||||
common_json json_rows = common_json::array();
|
||||
const std::string template_header = "%s: | %s | %s %s %s %s %s %s %s |\n";
|
||||
const std::string template_gpu = "%s: | %s | %s = %s + (%s = %s + %s + %s) + %s |\n";
|
||||
const std::string template_other = "%s: | %s | %s %s %s = %s + %s + %s %s |\n";
|
||||
|
||||
table_data.push_back({template_header, "memory breakdown [MiB]", "total", "free", "self", "model", "context", "compute", "unaccounted"});
|
||||
|
||||
constexpr size_t MiB = 1024 * 1024;
|
||||
|
||||
for (const auto & row : rows) {
|
||||
const size_t self = row.model + row.context + row.compute;
|
||||
|
||||
if (row.kind == common_memory_breakdown_row::DEVICE) {
|
||||
const int64_t unaccounted = row.total - row.free - static_cast<int64_t>(self);
|
||||
|
||||
table_data.push_back({
|
||||
template_gpu,
|
||||
" - " + row.name + " (" + row.description + ")",
|
||||
std::to_string(row.total / MiB),
|
||||
std::to_string(row.free / MiB),
|
||||
std::to_string(self / MiB),
|
||||
std::to_string(row.model / MiB),
|
||||
std::to_string(row.context / MiB),
|
||||
std::to_string(row.compute / MiB),
|
||||
std::to_string(unaccounted / static_cast<int64_t>(MiB))});
|
||||
|
||||
json_rows.push_back({
|
||||
{"kind", "device"},
|
||||
{"name", row.name},
|
||||
{"description", row.description},
|
||||
{"total", row.total / MiB},
|
||||
{"free", row.free / MiB},
|
||||
{"self", self / MiB},
|
||||
{"model", row.model / MiB},
|
||||
{"context", row.context / MiB},
|
||||
{"compute", row.compute / MiB},
|
||||
{"unaccounted", unaccounted / static_cast<int64_t>(MiB)},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
table_data.push_back({
|
||||
template_other,
|
||||
" - " + name,
|
||||
" - " + row.name,
|
||||
"", // total
|
||||
"", // free
|
||||
std::to_string(self / MiB),
|
||||
std::to_string(mb.model / MiB),
|
||||
std::to_string(mb.context / MiB),
|
||||
std::to_string(mb.compute / MiB),
|
||||
std::to_string(row.model / MiB),
|
||||
std::to_string(row.context / MiB),
|
||||
std::to_string(row.compute / MiB),
|
||||
""}); // unaccounted
|
||||
|
||||
rows.push_back({
|
||||
{"kind", "buffer_type"},
|
||||
{"name", name},
|
||||
json_rows.push_back({
|
||||
{"kind", row.kind == common_memory_breakdown_row::HOST ? "host" : "buffer_type"},
|
||||
{"name", row.name},
|
||||
{"self", self / MiB},
|
||||
{"model", mb.model / MiB},
|
||||
{"context", mb.context / MiB},
|
||||
{"compute", mb.compute / MiB},
|
||||
{"model", row.model / MiB},
|
||||
{"context", row.context / MiB},
|
||||
{"compute", row.compute / MiB},
|
||||
});
|
||||
|
||||
seen_buffer_types.insert(buft);
|
||||
}
|
||||
|
||||
for (size_t j = 1; j < table_data[0].size(); j++) {
|
||||
@@ -1081,7 +1175,7 @@ void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
|
||||
LOG_JSON("fit_memory_breakdown", common_json({
|
||||
{"unit", "MiB"},
|
||||
{"rows", rows},
|
||||
{"rows", json_rows},
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ggml.h"
|
||||
#include "llama.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum common_params_fit_status {
|
||||
@@ -45,6 +46,47 @@ void common_fit_print(
|
||||
|
||||
void common_memory_breakdown_print(const llama_context * ctx);
|
||||
|
||||
// one row of the memory breakdown: a device, the host or another buffer type
|
||||
struct common_memory_breakdown_row {
|
||||
enum kind_t { DEVICE, HOST, BUFFER_TYPE };
|
||||
|
||||
kind_t kind = DEVICE;
|
||||
std::string name; // device or buffer type name, "Host" for the host row
|
||||
std::string description; // device description, empty otherwise
|
||||
int64_t total = 0; // device memory in bytes, 0 unless kind == DEVICE
|
||||
int64_t free = 0; // free device memory in bytes, 0 unless kind == DEVICE
|
||||
size_t model = 0; // bytes held by this context, by kind of buffer
|
||||
size_t context = 0;
|
||||
size_t compute = 0;
|
||||
};
|
||||
|
||||
// memory a model needs at the min and max context --fit may pick
|
||||
struct common_memory_reports {
|
||||
uint32_t n_ctx_train = 0;
|
||||
uint32_t n_ctx_max = 0; // the largest context that fits the free device memory right now
|
||||
uint32_t n_ctx_min = 0; // --fit-ctx (the smallest context --fit may pick), with the placement set in params
|
||||
std::vector<common_memory_breakdown_row> info_max; // breakdown at n_ctx_max
|
||||
std::vector<common_memory_breakdown_row> info_min; // breakdown at n_ctx_min
|
||||
};
|
||||
|
||||
// rows of common_memory_breakdown_print: model devices first, then host
|
||||
std::vector<common_memory_breakdown_row> common_memory_breakdown_rows(const llama_context * ctx);
|
||||
|
||||
// load model + context with no_alloc and return the memory breakdown, params are used as-is
|
||||
// n_ctx_train and n_ctx are optional outputs; throws on failure
|
||||
std::vector<common_memory_breakdown_row> common_fit_measure(
|
||||
const char * path_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
uint32_t * n_ctx_train,
|
||||
uint32_t * n_ctx,
|
||||
ggml_log_level log_level);
|
||||
|
||||
struct common_params;
|
||||
|
||||
// measure min and max without allocating, params may be modified as with --fit; throws on failure
|
||||
common_memory_reports common_measure_memory(common_params & params);
|
||||
|
||||
struct common_device_memory_data {
|
||||
int64_t total;
|
||||
int64_t free;
|
||||
|
||||
+28
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "build-info.h"
|
||||
#include "common.h"
|
||||
#include "file-lock.h"
|
||||
#include "log.h"
|
||||
#include "http.h"
|
||||
#include "json.h"
|
||||
@@ -161,6 +162,14 @@ static bool is_valid_oid(const std::string & oid) {
|
||||
return is_hex_string(oid, 40) || is_hex_string(oid, 64);
|
||||
}
|
||||
|
||||
std::string get_lock_path(const std::string & repo_id, const std::string & oid) {
|
||||
if (!is_valid_repo_id(repo_id) || !is_valid_oid(oid)) {
|
||||
return {};
|
||||
}
|
||||
fs::path lock_path = fs::path(get_cache_path()) / ".locks" / repo_to_folder_name(repo_id) / (oid + ".lock");
|
||||
return lock_path.string();
|
||||
}
|
||||
|
||||
static bool is_valid_subpath(const fs::path & path, const fs::path & subpath) {
|
||||
if (subpath.is_absolute()) {
|
||||
return false; // never do a / b with b absolute
|
||||
@@ -437,7 +446,7 @@ hf_files get_cached_files(const std::string & repo_id) {
|
||||
return files;
|
||||
}
|
||||
|
||||
std::string finalize_file(const hf_file & file) {
|
||||
std::string finalize_file(const hf_file & file, const std::function<bool()> & keep_waiting) {
|
||||
static std::atomic<bool> symlinks_disabled{false};
|
||||
|
||||
std::error_code ec;
|
||||
@@ -452,6 +461,24 @@ std::string finalize_file(const hf_file & file) {
|
||||
return file.final_path;
|
||||
}
|
||||
|
||||
// the lock file is shared with huggingface_hub: another process may be
|
||||
// finalizing the same blob
|
||||
common_file_lock lock(get_lock_path(file.repo_id, file.oid));
|
||||
if (!lock.acquire(keep_waiting)) {
|
||||
// without the lock, another process could move the blob while we check it
|
||||
throw std::runtime_error("failed to lock cache for finalization: " + file.final_path);
|
||||
}
|
||||
|
||||
// acquire() only polls keep_waiting while waiting for a busy lock
|
||||
if (keep_waiting && !keep_waiting()) {
|
||||
throw std::runtime_error("download cancelled before finalization: " + file.final_path);
|
||||
}
|
||||
|
||||
// re-check under the lock: the winner may have created the link already
|
||||
if (fs::exists(final_path, ec)) {
|
||||
return file.final_path;
|
||||
}
|
||||
|
||||
fs::create_directories(final_path.parent_path(), ec);
|
||||
|
||||
if (!symlinks_disabled) {
|
||||
|
||||
+9
-2
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,8 +27,14 @@ hf_files get_repo_files(
|
||||
|
||||
hf_files get_cached_files(const std::string & repo_id = {});
|
||||
|
||||
// Create snapshot path (link or move/copy) and return it
|
||||
std::string finalize_file(const hf_file & file);
|
||||
// Lock file path for a blob, shared with the hf CLI: <cache>/.locks/<repo folder>/<oid>.lock
|
||||
// Pure path computation: creates nothing. Returns an empty string on invalid repo or oid.
|
||||
std::string get_lock_path(const std::string & repo_id, const std::string & oid);
|
||||
|
||||
// Create snapshot path (link or move/copy) and return it.
|
||||
// Throws when the lock cannot be taken or keep_waiting returns false.
|
||||
// A completed snapshot (final_path present) is returned without the lock, so a read-only cache works.
|
||||
std::string finalize_file(const hf_file & file, const std::function<bool()> & keep_waiting = {});
|
||||
|
||||
// Remove the entire cached directory for a repo, returns true if removed
|
||||
bool remove_cached_repo(const std::string & repo_id);
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
// we can remove it in the future when we figure out a better way to define inputs using jinja::value
|
||||
#include "json.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
#define FILENAME "jinja-caps"
|
||||
@@ -184,6 +186,10 @@ caps caps_get(jinja::program & prog) {
|
||||
{
|
||||
{"role", "user"},
|
||||
{"content", json::array({
|
||||
{
|
||||
{"type", "text"},
|
||||
{"text", content_marker}
|
||||
}
|
||||
})}
|
||||
}
|
||||
});
|
||||
@@ -584,6 +590,58 @@ caps caps_get(jinja::program & prog) {
|
||||
}
|
||||
);
|
||||
|
||||
if (result.supports_reasoning_effort) {
|
||||
JJ_DEBUG("%s\n", ">>> Running capability check: reasoning effort levels");
|
||||
|
||||
static const std::vector<std::string> candidates = { "minimal", "low", "medium", "high", "xhigh", "max" };
|
||||
|
||||
// renders a single user turn with the given effort, nothing when the template raises
|
||||
const auto render = [&](const std::string & effort) {
|
||||
std::optional<std::string> rendered;
|
||||
caps_try_execute(
|
||||
prog,
|
||||
[&]() {
|
||||
// messages
|
||||
return json::array({
|
||||
{
|
||||
{"role", "user"},
|
||||
{"content", "User message"}
|
||||
},
|
||||
});
|
||||
},
|
||||
[&](context & ctx) {
|
||||
ctx.set_val("enable_thinking", mk_val<value_bool>(true));
|
||||
caps_apply_reasoning_effort(ctx, effort);
|
||||
},
|
||||
nullptr, // tools_fn
|
||||
[&](context &, bool success, value &, value &, const std::string & output) {
|
||||
if (success) {
|
||||
rendered = output;
|
||||
}
|
||||
}
|
||||
);
|
||||
return rendered;
|
||||
};
|
||||
|
||||
// an unknown level shows the render of the template fallback, a level that renders the same is not honored
|
||||
// levels sharing a render are aliases and the highest one is kept
|
||||
const auto unknown = render("REASONING_EFFORT_UNKNOWN");
|
||||
std::vector<std::string> renders;
|
||||
for (const auto & level : candidates) {
|
||||
const auto rendered = render(level);
|
||||
if (!rendered || rendered == unknown) {
|
||||
continue;
|
||||
}
|
||||
const auto it = std::find(renders.begin(), renders.end(), *rendered);
|
||||
if (it != renders.end()) {
|
||||
result.reasoning_effort_levels[it - renders.begin()] = level;
|
||||
} else {
|
||||
renders.push_back(*rendered);
|
||||
result.reasoning_effort_levels.push_back(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JJ_DEBUG("%s\n", result.to_string().c_str());
|
||||
|
||||
return result;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace jinja {
|
||||
|
||||
@@ -19,6 +20,9 @@ struct caps {
|
||||
// supports reasoning effort levels
|
||||
bool supports_reasoning_effort = false;
|
||||
|
||||
// reasoning effort levels the template renders distinctly, from lowest to highest
|
||||
std::vector<std::string> reasoning_effort_levels;
|
||||
|
||||
// one of the 2 content capabilities must be true
|
||||
bool supports_string_content = true;
|
||||
bool supports_typed_content = false;
|
||||
|
||||
+21
-3
@@ -21,6 +21,17 @@ static bool is_type(const statement_ptr & ptr) {
|
||||
class parser {
|
||||
const std::vector<token> & tokens;
|
||||
size_t current = 0;
|
||||
int depth = 0;
|
||||
|
||||
struct depth_guard {
|
||||
parser & p;
|
||||
depth_guard(parser & p) : p(p) {
|
||||
if (++p.depth > JINJA_RECURSIVE_LIMIT) {
|
||||
throw parser_exception("Parser Error: template is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep", p.source, p.peek().pos);
|
||||
}
|
||||
}
|
||||
~depth_guard() { p.depth--; }
|
||||
};
|
||||
|
||||
std::string source; // for error reporting
|
||||
|
||||
@@ -94,6 +105,7 @@ private:
|
||||
}
|
||||
|
||||
statement_ptr parse_any() {
|
||||
depth_guard guard(*this);
|
||||
size_t start_pos = current;
|
||||
switch (peek().t) {
|
||||
case token::comment:
|
||||
@@ -254,6 +266,7 @@ private:
|
||||
size_t pos0 = current;
|
||||
++current; // consume {%
|
||||
++current; // consume 'elif'
|
||||
depth_guard guard(*this);
|
||||
alternate.push_back(parse_if_statement(pos0)); // nested If
|
||||
} else if (is_statement({"else"})) {
|
||||
++current; // consume {%
|
||||
@@ -330,6 +343,7 @@ private:
|
||||
}
|
||||
|
||||
statement_ptr parse_if_expression() {
|
||||
depth_guard guard(*this);
|
||||
auto a = parse_logical_or_expression();
|
||||
if (is_identifier("if")) {
|
||||
// Ternary expression
|
||||
@@ -373,6 +387,7 @@ private:
|
||||
statement_ptr parse_logical_negation_expression() {
|
||||
// Try parse unary operators
|
||||
if (is_identifier("not")) {
|
||||
depth_guard guard(*this);
|
||||
size_t start_pos = current;
|
||||
auto op = next();
|
||||
return mk_stmt<unary_expression>(start_pos, op, parse_logical_negation_expression());
|
||||
@@ -458,6 +473,7 @@ private:
|
||||
|
||||
statement_ptr parse_unary_expression() {
|
||||
if (is(token::unary_operator)) {
|
||||
depth_guard guard(*this);
|
||||
size_t start_pos = current;
|
||||
auto op = next();
|
||||
return mk_stmt<unary_expression>(start_pos, op, parse_unary_expression());
|
||||
@@ -477,9 +493,11 @@ private:
|
||||
size_t start_pos = current;
|
||||
auto expr = mk_stmt<call_expression>(start_pos, std::move(callee), parse_args());
|
||||
auto member = parse_member_expression(std::move(expr)); // foo.x().y
|
||||
return is(token::open_paren)
|
||||
? parse_call_expression(std::move(member)) // foo.x()()
|
||||
: std::move(member);
|
||||
if (!is(token::open_paren)) {
|
||||
return member;
|
||||
}
|
||||
depth_guard guard(*this);
|
||||
return parse_call_expression(std::move(member)); // foo.x()()
|
||||
}
|
||||
|
||||
statements parse_args() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "value.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -52,32 +53,39 @@ static void ensure_key_type_allowed(const value & val) {
|
||||
|
||||
// execute with error handling
|
||||
value statement::execute(context & ctx) const {
|
||||
std::exception_ptr pending;
|
||||
std::string message;
|
||||
try {
|
||||
return execute_impl(ctx);
|
||||
} catch (const continue_statement::signal & /* ex */) {
|
||||
throw;
|
||||
pending = std::current_exception();
|
||||
} catch (const break_statement::signal & /* ex */) {
|
||||
throw;
|
||||
pending = std::current_exception();
|
||||
} catch (const rethrown_exception & /* ex */) {
|
||||
throw;
|
||||
pending = std::current_exception();
|
||||
} catch (const not_implemented_exception & /* ex */) {
|
||||
throw;
|
||||
pending = std::current_exception();
|
||||
} catch (const std::exception & e) {
|
||||
const std::string & source = *ctx.src;
|
||||
if (source.empty()) {
|
||||
std::ostringstream oss;
|
||||
oss << "\nError executing " << type() << " at position " << pos << ": " << e.what();
|
||||
throw rethrown_exception(oss.str());
|
||||
message = oss.str();
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss << "\n------------\n";
|
||||
oss << "While executing " << type() << " at " << get_line_col(source, pos) << " in source:\n";
|
||||
oss << peak_source(source, pos) << "\n";
|
||||
oss << "Error: " << e.what();
|
||||
// throw as another exception to avoid repeated formatting
|
||||
throw rethrown_exception(oss.str());
|
||||
message = oss.str();
|
||||
}
|
||||
}
|
||||
// throw outside the catch: MSVC keeps the unwound frames on the stack while a catch block runs
|
||||
if (pending) {
|
||||
std::rethrow_exception(pending);
|
||||
}
|
||||
// throw as another exception to avoid repeated formatting
|
||||
throw rethrown_exception(message);
|
||||
}
|
||||
|
||||
value identifier::execute_impl(context & ctx) const {
|
||||
@@ -747,7 +755,11 @@ value macro_statement::execute_impl(context & ctx) const {
|
||||
std::string name = cast_stmt<identifier>(this->name)->val;
|
||||
|
||||
const func_handler func = [this, name](const func_args & args) -> value {
|
||||
if (args.ctx.macro_depth >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::runtime_error("macro calls are nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
context macro_ctx(args.ctx); // new scope for macro execution
|
||||
macro_ctx.macro_depth = args.ctx.macro_depth + 1;
|
||||
|
||||
bind_parameters(name, this->args, args, macro_ctx);
|
||||
|
||||
@@ -778,7 +790,11 @@ value call_statement::execute_impl(context & ctx) const {
|
||||
context caller_ctx(ctx); // new scope for caller execution
|
||||
|
||||
const func_handler func = [this, caller_ctx = std::move(caller_ctx)](const func_args & args) -> value {
|
||||
if (args.ctx.macro_depth >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::runtime_error("macro calls are nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
context block_ctx(caller_ctx); // new scope for block execution
|
||||
block_ctx.macro_depth = args.ctx.macro_depth + 1;
|
||||
|
||||
bind_parameters("caller", this->caller_args, args, block_ctx);
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ struct context {
|
||||
|
||||
bool is_get_stats = false; // whether to collect stats
|
||||
|
||||
int macro_depth = 0;
|
||||
|
||||
visitor_fn visitor;
|
||||
|
||||
// src is optional, used for error reporting
|
||||
@@ -78,6 +80,7 @@ struct context {
|
||||
src(parent.src),
|
||||
current_time(parent.current_time),
|
||||
is_get_stats(parent.is_get_stats),
|
||||
macro_depth(parent.macro_depth),
|
||||
// inherit variables (for example, when entering a new scope)
|
||||
env(mk_val<value_object>(*parent.env)) {}
|
||||
|
||||
@@ -710,7 +713,7 @@ struct rethrown_exception : public std::exception {
|
||||
|
||||
//////////////////////
|
||||
|
||||
static void gather_string_parts_recursive(const value & val, value_string & parts) {
|
||||
static void gather_string_parts_recursive(const value & val, value_string & parts, int depth = 0) {
|
||||
// TODO: probably allow print value_none as "None" string? currently this breaks some templates
|
||||
if (is_val<value_string>(val)) {
|
||||
const auto & str_val = cast_val<value_string>(val)->val_str;
|
||||
@@ -718,10 +721,12 @@ static void gather_string_parts_recursive(const value & val, value_string & part
|
||||
} else if (is_val<value_int>(val) || is_val<value_float>(val) || is_val<value_bool>(val)) {
|
||||
std::string str_val = val->as_string().str();
|
||||
parts->val_str.append(str_val);
|
||||
} else if (is_val<value_array>(val) && depth >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
} else if (is_val<value_array>(val)) {
|
||||
auto items = cast_val<value_array>(val)->as_array();
|
||||
for (const auto & item : items) {
|
||||
gather_string_parts_recursive(item, parts);
|
||||
gather_string_parts_recursive(item, parts, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#define JINJA_RECURSIVE_LIMIT 128
|
||||
|
||||
namespace jinja {
|
||||
|
||||
static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
|
||||
|
||||
+58
-70
@@ -6,6 +6,7 @@
|
||||
#include "json.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
#include <cfloat>
|
||||
@@ -299,74 +300,53 @@ static value & get_attribute(const value & val, const value & attr, value & defa
|
||||
return default_val;
|
||||
}
|
||||
|
||||
template<bool is_reject>
|
||||
static value selectattr(const func_args & args) {
|
||||
args.ensure_count(2, 4);
|
||||
args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
|
||||
template<bool is_reject, bool lookup_attr>
|
||||
static value select_or_reject(const func_args & args) {
|
||||
// example: array | select("odd") -> test_is_odd(item)
|
||||
// example: array | selectattr("status", "equalto", "active") -> test_is_equalto(item.status, "active")
|
||||
constexpr size_t test_pos = lookup_attr ? 2 : 1;
|
||||
args.ensure_count(test_pos);
|
||||
args.ensure_val<value_array>(args.get_pos(0));
|
||||
value attr;
|
||||
if constexpr (lookup_attr) {
|
||||
attr = args.get_pos(1);
|
||||
args.ensure_val<value_string>(attr);
|
||||
}
|
||||
|
||||
auto & builtins = global_builtins();
|
||||
auto test_it = builtins.end();
|
||||
if (args.count() > test_pos) {
|
||||
args.ensure_val<value_string>(args.get_pos(test_pos));
|
||||
std::string test_name = args.get_pos(test_pos)->as_string().str();
|
||||
test_it = builtins.find("test_is_" + test_name);
|
||||
if (test_it == builtins.end()) {
|
||||
throw raised_exception(args.func_name + ": unknown test '" + test_name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
auto arr = args.get_pos(0)->as_array();
|
||||
auto attribute = args.get_pos(1);
|
||||
auto out = mk_val<value_array>();
|
||||
value val_default = mk_val<value_undefined>();
|
||||
|
||||
if (args.count() == 2) {
|
||||
// example: array | selectattr("active")
|
||||
for (const auto & item : arr) {
|
||||
value attr_val = get_attribute(item, 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);
|
||||
for (const auto & item : arr) {
|
||||
value val = item;
|
||||
if constexpr (lookup_attr) {
|
||||
val = get_attribute(item, attr, val_default);
|
||||
}
|
||||
return out;
|
||||
|
||||
} else if (args.count() == 3) {
|
||||
// example: array | selectattr("equalto", "text")
|
||||
// translated to: test_is_equalto(item, "text")
|
||||
std::string test_name = args.get_pos(1)->as_string().str();
|
||||
value test_val = args.get_pos(2);
|
||||
auto & builtins = global_builtins();
|
||||
auto it = builtins.find("test_is_" + test_name);
|
||||
if (it == builtins.end()) {
|
||||
throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
||||
}
|
||||
auto test_fn = it->second;
|
||||
for (const auto & item : arr) {
|
||||
bool is_selected;
|
||||
if (test_it == builtins.end()) {
|
||||
is_selected = val->as_bool();
|
||||
} else {
|
||||
func_args test_args(args.ctx);
|
||||
test_args.push_back(item); // current object
|
||||
test_args.push_back(test_val); // extra argument
|
||||
value test_result = test_fn(test_args);
|
||||
bool is_selected = test_result->as_bool();
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
test_args.push_back(val);
|
||||
for (size_t i = test_pos + 1; i < args.count(); i++) {
|
||||
test_args.push_back(args.get_pos(i));
|
||||
}
|
||||
is_selected = test_it->second(test_args)->as_bool();
|
||||
}
|
||||
return out;
|
||||
|
||||
} else if (args.count() == 4) {
|
||||
// example: array | selectattr("status", "equalto", "active")
|
||||
// translated to: test_is_equalto(item.status, "active")
|
||||
std::string test_name = args.get_pos(2)->as_string().str();
|
||||
auto extra_arg = args.get_pos(3);
|
||||
auto & builtins = global_builtins();
|
||||
auto it = builtins.find("test_is_" + test_name);
|
||||
if (it == builtins.end()) {
|
||||
throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
||||
}
|
||||
auto test_fn = it->second;
|
||||
for (const auto & item : arr) {
|
||||
value attr_val = get_attribute(item, 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
|
||||
value test_result = test_fn(test_args);
|
||||
bool is_selected = test_result->as_bool();
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
}
|
||||
return out;
|
||||
} else {
|
||||
throw raised_exception("selectattr: invalid number of arguments");
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1095,10 +1075,10 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
auto arr = slice(val->as_array(), start, stop, step);
|
||||
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
|
||||
}},
|
||||
{"selectattr", selectattr<false>},
|
||||
{"select", selectattr<false>},
|
||||
{"rejectattr", selectattr<true>},
|
||||
{"reject", selectattr<true>},
|
||||
{"selectattr", select_or_reject<false, true>},
|
||||
{"select", select_or_reject<false, false>},
|
||||
{"rejectattr", select_or_reject<true, true>},
|
||||
{"reject", select_or_reject<true, false>},
|
||||
{"join", [](const func_args & args) -> value {
|
||||
args.ensure_count(1, 3);
|
||||
if (!is_val<value_array>(args.get_pos(0))) {
|
||||
@@ -1460,7 +1440,8 @@ const func_builtins & value_undefined_t::get_builtins() const {
|
||||
//////////////////////////////////
|
||||
|
||||
|
||||
static value from_json(const common_json & j, bool mark_input) {
|
||||
// the runtime walks values with recursion, a deeper input overflows the stack
|
||||
static value from_json(const common_json & j, bool mark_input, int depth = 0) {
|
||||
if (j.is_null()) {
|
||||
return mk_val<value_none>();
|
||||
} else if (j.is_boolean()) {
|
||||
@@ -1475,16 +1456,18 @@ static value from_json(const common_json & j, bool mark_input) {
|
||||
str->mark_input();
|
||||
}
|
||||
return str;
|
||||
} else if ((j.is_array() || j.is_object()) && depth >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::invalid_argument("JSON is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
} else if (j.is_array()) {
|
||||
auto arr = mk_val<value_array>();
|
||||
for (const auto & item : j) {
|
||||
arr->push_back(from_json(item, mark_input));
|
||||
arr->push_back(from_json(item, mark_input, depth + 1));
|
||||
}
|
||||
return arr;
|
||||
} else if (j.is_object()) {
|
||||
auto obj = mk_val<value_object>();
|
||||
for (auto it = j.begin(); it != j.end(); ++it) {
|
||||
obj->insert(it.key(), from_json(it.value(), mark_input));
|
||||
obj->insert(it.key(), from_json(it.value(), mark_input, depth + 1));
|
||||
}
|
||||
return obj;
|
||||
} else {
|
||||
@@ -1679,6 +1662,8 @@ static void value_to_json_internal(std::ostringstream & oss, const value & val,
|
||||
}
|
||||
}
|
||||
oss << "\"";
|
||||
} else if ((is_val<value_array>(val) || is_val<value_object>(val)) && curr_lvl >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
} else if (is_val<value_array>(val)) {
|
||||
const auto & arr = val->as_array();
|
||||
oss << "[";
|
||||
@@ -1744,17 +1729,20 @@ std::string value_to_string_repr(const value & val) {
|
||||
}
|
||||
|
||||
// stats utility
|
||||
void value_t::stats_t::mark_used(value & val, bool deep) {
|
||||
void value_t::stats_t::mark_used(value & val, bool deep, int depth) {
|
||||
val->stats.used = true;
|
||||
if (deep && (is_val<value_array>(val) || is_val<value_object>(val)) && depth >= JINJA_RECURSIVE_LIMIT) {
|
||||
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
if (deep) {
|
||||
if (is_val<value_array>(val)) {
|
||||
for (auto & item : val->val_arr) {
|
||||
mark_used(item, deep);
|
||||
mark_used(item, deep, depth + 1);
|
||||
}
|
||||
} else if (is_val<value_object>(val)) {
|
||||
for (auto & pair : val->val_obj) {
|
||||
mark_used(pair.first, deep);
|
||||
mark_used(pair.second, deep);
|
||||
mark_used(pair.first, deep, depth + 1);
|
||||
mark_used(pair.second, deep, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ struct value_t {
|
||||
// ops can be builtin calls or operators: "array_access", "object_access"
|
||||
std::set<std::string> ops;
|
||||
// utility to recursively mark value and its children as used
|
||||
static void mark_used(value & val, bool deep = false);
|
||||
static void mark_used(value & val, bool deep = false, int depth = 0);
|
||||
} stats;
|
||||
|
||||
value_t() = default;
|
||||
|
||||
@@ -272,15 +272,12 @@ static bool is_reserved_name(const std::string & name) {
|
||||
return RESERVED_NAMES.find(name) != RESERVED_NAMES.end();
|
||||
}
|
||||
|
||||
static std::regex INVALID_RULE_CHARS_RE("[^a-zA-Z0-9-]+");
|
||||
static std::regex GRAMMAR_LITERAL_ESCAPE_RE("[\r\n\"\\\\]");
|
||||
static std::regex GRAMMAR_RANGE_LITERAL_ESCAPE_RE("[\r\n\"\\]\\-\\\\]");
|
||||
static std::unordered_map<char, std::string> GRAMMAR_LITERAL_ESCAPES = {
|
||||
{'\r', "\\r"}, {'\n', "\\n"}, {'"', "\\\""}, {'-', "\\-"}, {']', "\\]"}, {'\\', "\\\\"}
|
||||
};
|
||||
|
||||
static const int MAX_PATTERN_DEPTH = 100;
|
||||
|
||||
static std::unordered_set<char> NON_LITERAL_SET = {'|', '.', '(', ')', '[', ']', '{', '}', '*', '+', '?', '^', '$'};
|
||||
static std::unordered_set<char> ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = {'^', '$', '.', '[', ']', '(', ')', '|', '{', '}', '*', '+', '?'};
|
||||
|
||||
@@ -312,6 +309,22 @@ static std::string format_literal(const std::string & literal) {
|
||||
|
||||
std::string gbnf_format_literal(const std::string & literal) { return format_literal(literal); }
|
||||
|
||||
// same as std::regex_replace with [^a-zA-Z0-9-]+ -> "-", no std::regex: libstdc++ recurses per char
|
||||
std::string gbnf_format_rule_name(const std::string & name) {
|
||||
std::string out;
|
||||
bool in_invalid = false;
|
||||
for (char c : name) {
|
||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') {
|
||||
out += c;
|
||||
in_invalid = false;
|
||||
} else if (!in_invalid) {
|
||||
out += '-';
|
||||
in_invalid = true;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static size_t gbnf_escape_length(const std::string & pattern, size_t pos) {
|
||||
if (pos + 1 >= pattern.length() || pattern[pos] != '\\') {
|
||||
return 0;
|
||||
@@ -347,6 +360,17 @@ private:
|
||||
std::unordered_set<std::string> _refs_being_resolved;
|
||||
std::vector<std::string> _errors;
|
||||
std::vector<std::string> _warnings;
|
||||
int _depth = 0;
|
||||
|
||||
struct depth_guard {
|
||||
int & depth;
|
||||
depth_guard(int & depth) : depth(depth) {
|
||||
if (++depth > COMMON_RECURSIVE_LIMIT) {
|
||||
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
}
|
||||
~depth_guard() { depth--; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static const T & as(const common_chat_schema & node) {
|
||||
@@ -354,7 +378,7 @@ private:
|
||||
}
|
||||
|
||||
std::string _add_rule(const std::string & name, const std::string & rule) {
|
||||
std::string esc_name = regex_replace(name, INVALID_RULE_CHARS_RE, "-");
|
||||
std::string esc_name = gbnf_format_rule_name(name);
|
||||
if (_rules.find(esc_name) == _rules.end() || _rules[esc_name] == rule) {
|
||||
_rules[esc_name] = rule;
|
||||
return esc_name;
|
||||
@@ -482,7 +506,7 @@ private:
|
||||
}
|
||||
}
|
||||
paren_depth++;
|
||||
if (paren_depth > MAX_PATTERN_DEPTH) {
|
||||
if (paren_depth > COMMON_RECURSIVE_LIMIT) {
|
||||
throw unsupported_pattern("pattern nesting too deep");
|
||||
}
|
||||
seq.emplace_back("(" + to_rule(transform()) + ")", false);
|
||||
@@ -644,7 +668,11 @@ private:
|
||||
std::string char_rule = _add_primitive("char", PRIMITIVE_RULES.at("char"));
|
||||
std::ostringstream out;
|
||||
out << "[\"] ( ";
|
||||
std::function<void(size_t)> visit = [&](size_t idx) {
|
||||
// one level per char of the longest name
|
||||
std::function<void(size_t, int)> visit = [&](size_t idx, int depth) {
|
||||
if (depth >= COMMON_RECURSIVE_LIMIT) {
|
||||
throw std::invalid_argument("JSON schema property name is longer than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " chars");
|
||||
}
|
||||
const auto & node = trie.nodes[idx];
|
||||
std::string rejects;
|
||||
auto first = true;
|
||||
@@ -659,7 +687,7 @@ private:
|
||||
out << "[" << c << "]";
|
||||
if (!trie.nodes[child].children.empty()) {
|
||||
out << " (";
|
||||
visit(child);
|
||||
visit(child, depth + 1);
|
||||
out << ")";
|
||||
} else {
|
||||
out << " " << char_rule << "+";
|
||||
@@ -669,7 +697,7 @@ private:
|
||||
out << " | [^\"" << rejects << "] " << char_rule << "*";
|
||||
}
|
||||
};
|
||||
visit(0);
|
||||
visit(0, 0);
|
||||
|
||||
out << " )";
|
||||
if (trie.nodes[0].pattern < 0) {
|
||||
@@ -682,8 +710,7 @@ private:
|
||||
std::string _resolve_ref(const common_chat_schema_ref & schema) {
|
||||
auto it = schema.ref.find('#');
|
||||
std::string ref_fragment = it != std::string::npos ? schema.ref.substr(it + 1) : schema.ref;
|
||||
static const std::regex nonalphanumeric_regex(R"([^a-zA-Z0-9-]+)");
|
||||
std::string ref_name = "ref" + std::regex_replace(ref_fragment, nonalphanumeric_regex, "-");
|
||||
std::string ref_name = "ref" + gbnf_format_rule_name(ref_fragment);
|
||||
if (_rules.find(ref_name) == _rules.end() && _refs_being_resolved.find(schema.ref) == _refs_being_resolved.end()) {
|
||||
if (!schema.target) {
|
||||
_errors.push_back("Unresolved $ref " + schema.ref);
|
||||
@@ -833,10 +860,13 @@ public:
|
||||
std::unordered_set<std::string> required;
|
||||
std::vector<std::pair<std::string, const common_chat_schema *>> properties;
|
||||
std::map<std::string, size_t> enum_values;
|
||||
std::function<void(const common_chat_schema &, bool)> add_component = [&](const common_chat_schema & comp, bool is_required) {
|
||||
std::function<void(const common_chat_schema &, bool, int)> add_component = [&](const common_chat_schema & comp, bool is_required, int depth) {
|
||||
if (depth >= COMMON_RECURSIVE_LIMIT) {
|
||||
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
if (comp.kind() == common_chat_schema::KIND_REF) {
|
||||
if (const auto * target = as<common_chat_schema_ref>(comp).target) {
|
||||
add_component(*target, is_required);
|
||||
add_component(*target, is_required, depth + 1);
|
||||
}
|
||||
} else if (comp.kind() == common_chat_schema::KIND_OBJECT) {
|
||||
for (const auto & prop : as<common_chat_schema_object>(comp).properties) {
|
||||
@@ -854,10 +884,10 @@ public:
|
||||
for (const auto & child : schema.children) {
|
||||
if (child->kind() == common_chat_schema::KIND_ANY_OF) {
|
||||
for (const auto & alt : as<common_chat_schema_any_of>(*child).children) {
|
||||
add_component(*alt, false);
|
||||
add_component(*alt, false, 0);
|
||||
}
|
||||
} else {
|
||||
add_component(*child, true);
|
||||
add_component(*child, true, 0);
|
||||
}
|
||||
}
|
||||
if (!enum_values.empty()) {
|
||||
@@ -875,6 +905,8 @@ public:
|
||||
}
|
||||
|
||||
std::string visit(const common_chat_schema & schema, const std::string & name) {
|
||||
// the $ref targets are visited too, a $ref chain is not bounded by the builder
|
||||
depth_guard guard(_depth);
|
||||
std::string rule_name = is_reserved_name(name) ? name + "-" : name.empty() ? "root" : name;
|
||||
std::string sub_name = name + (name.empty() ? "" : "-");
|
||||
|
||||
|
||||
@@ -19,5 +19,6 @@ struct common_grammar_options {
|
||||
};
|
||||
|
||||
std::string gbnf_format_literal(const std::string & literal);
|
||||
std::string gbnf_format_rule_name(const std::string & name);
|
||||
|
||||
std::string build_grammar(const std::function<void(const common_grammar_builder &)> & cb, const common_grammar_options & options = {});
|
||||
|
||||
+34
-10
@@ -9,6 +9,18 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
struct depth_guard {
|
||||
int & depth;
|
||||
depth_guard(int & depth) : depth(depth) {
|
||||
if (++depth > COMMON_RECURSIVE_LIMIT) {
|
||||
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
}
|
||||
~depth_guard() { depth--; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class common_chat_schema_builder {
|
||||
const common_json & root_;
|
||||
common_chat_schema_document & doc_;
|
||||
@@ -224,7 +236,14 @@ class common_chat_schema_builder {
|
||||
return node;
|
||||
}
|
||||
|
||||
int depth_ = 0;
|
||||
|
||||
common_chat_schema_ptr build_node(const common_json & schema, const std::string & path) {
|
||||
depth_guard guard(depth_);
|
||||
return build_node_impl(schema, path);
|
||||
}
|
||||
|
||||
common_chat_schema_ptr build_node_impl(const common_json & schema, const std::string & path) {
|
||||
if (!schema.is_object()) {
|
||||
fail(path, "schema must be an object");
|
||||
}
|
||||
@@ -365,7 +384,9 @@ static common_chat_schema::value_type json_type(const common_json & value) {
|
||||
return common_chat_schema::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
static common_chat_schema::type_set value_types_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {
|
||||
// depth counts the $ref, anyOf and allOf hops, a $ref chain is not bounded by the builder
|
||||
static common_chat_schema::type_set value_types_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited, int & depth) {
|
||||
depth_guard guard(depth);
|
||||
switch (s.kind()) {
|
||||
case common_chat_schema::KIND_ANY:
|
||||
return common_chat_schema::type_set::all();
|
||||
@@ -399,21 +420,21 @@ static common_chat_schema::type_set value_types_impl(const common_chat_schema &
|
||||
// a cycle contributes no type, to be safe
|
||||
return {};
|
||||
}
|
||||
auto types = value_types_impl(*target, visited);
|
||||
auto types = value_types_impl(*target, visited, depth);
|
||||
visited.erase(target);
|
||||
return types;
|
||||
}
|
||||
case common_chat_schema::KIND_ANY_OF: {
|
||||
common_chat_schema::type_set types;
|
||||
for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {
|
||||
types |= value_types_impl(*child, visited);
|
||||
types |= value_types_impl(*child, visited, depth);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
case common_chat_schema::KIND_ALL_OF: {
|
||||
auto types = common_chat_schema::type_set::all();
|
||||
for (const auto & child : static_cast<const common_chat_schema_all_of &>(s).children) {
|
||||
types &= value_types_impl(*child, visited);
|
||||
types &= value_types_impl(*child, visited, depth);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
@@ -423,10 +444,12 @@ static common_chat_schema::type_set value_types_impl(const common_chat_schema &
|
||||
|
||||
common_chat_schema::type_set common_chat_schema::value_types() const {
|
||||
std::unordered_set<const common_chat_schema *> visited;
|
||||
return value_types_impl(*this, visited);
|
||||
int depth = 0;
|
||||
return value_types_impl(*this, visited, depth);
|
||||
}
|
||||
|
||||
static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {
|
||||
static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited, int & depth) {
|
||||
depth_guard guard(depth);
|
||||
switch (s.kind()) {
|
||||
case common_chat_schema::KIND_STRING:
|
||||
return true;
|
||||
@@ -445,13 +468,13 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
|
||||
if (!target || !visited.insert(target).second) {
|
||||
return false;
|
||||
}
|
||||
bool result = may_be_string_impl(*target, visited);
|
||||
bool result = may_be_string_impl(*target, visited, depth);
|
||||
visited.erase(target);
|
||||
return result;
|
||||
}
|
||||
case common_chat_schema::KIND_ANY_OF:
|
||||
for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {
|
||||
if (may_be_string_impl(*child, visited)) {
|
||||
if (may_be_string_impl(*child, visited, depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -463,7 +486,7 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
|
||||
if (child->kind() == common_chat_schema::KIND_ANY) {
|
||||
continue;
|
||||
}
|
||||
if (!may_be_string_impl(*child, visited)) {
|
||||
if (!may_be_string_impl(*child, visited, depth)) {
|
||||
return false;
|
||||
}
|
||||
any_string = true;
|
||||
@@ -477,7 +500,8 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
|
||||
|
||||
bool common_chat_schema::may_be_string() const {
|
||||
std::unordered_set<const common_chat_schema *> visited;
|
||||
return may_be_string_impl(*this, visited);
|
||||
int depth = 0;
|
||||
return may_be_string_impl(*this, visited, depth);
|
||||
}
|
||||
|
||||
const char * common_chat_schema::kind_name(node_kind kind) {
|
||||
|
||||
+53
-10
@@ -1,4 +1,5 @@
|
||||
#include "parsers.h"
|
||||
#include "log.h"
|
||||
|
||||
// The DeepSeek V4 reference implementation renders consecutive tool results into a single
|
||||
// user block, ordered by the tool call order of the preceding assistant message (matched
|
||||
@@ -75,13 +76,30 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
// non-thinking generation prompt ends with a bare </think> instead of an empty
|
||||
// <think></think> pair.
|
||||
const bool is_v4 = tmpl.source().find("function_calls") == std::string::npos;
|
||||
// V4.1: DSML tag names carry a leading space, e.g. <|DSML| calls>
|
||||
const bool is_v41 = is_v4 && tmpl.source().find("|DSML| calls>") != std::string::npos;
|
||||
|
||||
std::optional<json> adjusted_messages;
|
||||
if (is_v4) {
|
||||
adjusted_messages = deepseek_v4_sort_tool_results(inputs.messages);
|
||||
}
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
// V4.1 can declare tools on a system message instead of top-level "tools": parse calls to those too.
|
||||
json tools = inputs.tools;
|
||||
if (is_v41 && !(tools.is_array() && !tools.empty()) && inputs.messages.is_array()) {
|
||||
for (const auto & msg : inputs.messages) {
|
||||
if (msg.value("role", "") == "system" && msg.contains("tools") && msg.at("tools").is_array()) {
|
||||
if (!tools.is_array()) {
|
||||
tools = json::array();
|
||||
}
|
||||
for (const auto & tool : msg.at("tools")) {
|
||||
tools.push_back(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto has_tools = tools.is_array() && !tools.empty();
|
||||
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
|
||||
@@ -90,17 +108,39 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
if (is_v4 && has_response_format) {
|
||||
additional_context = json{ { "response_format", inputs.json_schema } };
|
||||
}
|
||||
// V4.1 raises on anything but low/high/max or 1-100: map minimal -> low, xhigh -> max, medium -> default
|
||||
if (is_v41 && inputs.extra_context.is_object() && inputs.extra_context.contains("reasoning_effort") &&
|
||||
inputs.extra_context.at("reasoning_effort").is_string()) {
|
||||
const std::string effort = inputs.extra_context.at("reasoning_effort").get<std::string>();
|
||||
json mapped;
|
||||
if (effort == "minimal") {
|
||||
mapped = "low";
|
||||
} else if (effort == "xhigh") {
|
||||
mapped = "max";
|
||||
} else if (effort == "medium") {
|
||||
mapped = nullptr;
|
||||
}
|
||||
if (effort == "minimal" || effort == "xhigh" || effort == "medium") {
|
||||
LOG_DBG("%s: DeepSeek V4.1 reasoning_effort '%s' -> %s\n", __func__, effort.c_str(),
|
||||
mapped.is_null() ? "unset (template default)" : mapped.get<std::string>().c_str());
|
||||
if (!additional_context.has_value()) {
|
||||
additional_context = json::object();
|
||||
}
|
||||
(*additional_context)["reasoning_effort"] = mapped;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string DSML = "|DSML|";
|
||||
const std::string THINK_START = "<think>";
|
||||
const std::string THINK_END = "</think>";
|
||||
const std::string TC_BLOCK = is_v4 ? "tool_calls" : "function_calls";
|
||||
const std::string FC_START = "<" + DSML + TC_BLOCK + ">";
|
||||
const std::string FC_END = "</" + DSML + TC_BLOCK + ">";
|
||||
const std::string INVOKE_START = "<" + DSML + "invoke";
|
||||
const std::string INVOKE_END = "</" + DSML + "invoke>";
|
||||
const std::string PARAM_START = "<" + DSML + "parameter";
|
||||
const std::string PARAM_END = "</" + DSML + "parameter>";
|
||||
const std::string TAG_SP = is_v41 ? " " : "";
|
||||
const std::string TC_BLOCK = is_v41 ? "calls" : is_v4 ? "tool_calls" : "function_calls";
|
||||
const std::string FC_START = "<" + DSML + TAG_SP + TC_BLOCK + ">";
|
||||
const std::string FC_END = "</" + DSML + TAG_SP + TC_BLOCK + ">";
|
||||
const std::string INVOKE_START = "<" + DSML + TAG_SP + "invoke";
|
||||
const std::string INVOKE_END = "</" + DSML + TAG_SP + "invoke>";
|
||||
const std::string PARAM_START = "<" + DSML + TAG_SP + "parameter";
|
||||
const std::string PARAM_END = "</" + DSML + TAG_SP + "parameter>";
|
||||
const std::string GEN_PROMPT = "<|Assistant|>";
|
||||
const std::string TC_SEPARATOR = "\n\n";
|
||||
|
||||
@@ -152,7 +192,7 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
// build tool call section first since we might need it in reasoning
|
||||
auto tool_choice = p.choice();
|
||||
if (has_tool_calls) {
|
||||
foreach_function(inputs.tools, [&](size_t tool_index, const json & tool) {
|
||||
foreach_function(tools, [&](size_t tool_index, const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
std::string name = function.at("name");
|
||||
|
||||
@@ -223,7 +263,10 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
}
|
||||
|
||||
if (extract_reasoning && inputs.enable_thinking) {
|
||||
reasoning = p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
|
||||
// V4.1 renders a continuation without reasoning as "<|Assistant|></think>" even in thinking mode,
|
||||
// so consume the bare THINK_END too or it leaks into the content
|
||||
reasoning = is_v41 ? p.optional((THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END) | p.literal(THINK_END))
|
||||
: p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
|
||||
reasoning_with_tc = THINK_START +
|
||||
p.reasoning(p.until_one_of({ TC_SEPARATOR + FC_START, FC_START, THINK_END })) +
|
||||
p.space() + obligatory_tool_calls;
|
||||
|
||||
@@ -76,9 +76,10 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
|
||||
|
||||
auto reasoning = p.eps();
|
||||
if (supports_reasoning && extract_reasoning) {
|
||||
// The template strips the newline before </think>, so leave it out of the reasoning
|
||||
reasoning = p.optional("<think>" + p.space() +
|
||||
p.reasoning(p.until_one_of({ "</think>", "<tool_call>" })) +
|
||||
(p.literal("</think>") | p.peek(p.literal("<tool_call>"))));
|
||||
p.reasoning(p.until_one_of({ "\n</think>", "</think>", "<tool_call>" })) +
|
||||
(p.literal("\n</think>") | p.literal("</think>") | p.peek(p.literal("<tool_call>"))));
|
||||
}
|
||||
|
||||
// Response format parser
|
||||
@@ -166,8 +167,15 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
|
||||
auto calls = inputs.parallel_tool_calls ? tool_call_first + p.zero_or_more(tool_call) : tool_call_first;
|
||||
auto tool_calls = p.trigger_rule("tool-call-root", p.repeat(calls, min_calls, 1));
|
||||
|
||||
// The templates separate content from the tool calls with one or two newlines,
|
||||
// stop the content before them
|
||||
std::vector<std::string> content_ends;
|
||||
for (const auto & start : tool_call_starts) {
|
||||
content_ends.insert(content_ends.end(), { "\n\n" + start, "\n" + start, start });
|
||||
}
|
||||
|
||||
return generation_prompt +
|
||||
(reasoning << p.content(p.until_one_of(tool_call_starts)) << tool_calls);
|
||||
(reasoning << p.content(p.until_one_of(content_ends)) << tool_calls);
|
||||
}
|
||||
|
||||
// Content only parser
|
||||
|
||||
+30
-17
@@ -10,10 +10,12 @@
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
// The parser can run with a lower limit than COMMON_RECURSIVE_LIMIT
|
||||
static constexpr int RULE_RECURSIVE_LIMIT = 64;
|
||||
|
||||
// Trick to catch missing branches
|
||||
template <typename T>
|
||||
inline constexpr bool is_always_false_v = false;
|
||||
@@ -186,13 +188,17 @@ std::string common_peg_ast_node::sanitized_text() const {
|
||||
}
|
||||
|
||||
void common_peg_ast_arena::visit(common_peg_ast_id id, const common_peg_ast_visitor & visitor) const {
|
||||
if (id == COMMON_PEG_INVALID_AST_ID) {
|
||||
return;
|
||||
}
|
||||
const auto & node = get(id);
|
||||
visitor(node);
|
||||
for (const auto & child : node.children) {
|
||||
visit(child, visitor);
|
||||
// no recursion: the AST is several nodes deep per level of the input
|
||||
std::vector<common_peg_ast_id> stack = { id };
|
||||
while (!stack.empty()) {
|
||||
const auto cur = stack.back();
|
||||
stack.pop_back();
|
||||
if (cur == COMMON_PEG_INVALID_AST_ID) {
|
||||
continue;
|
||||
}
|
||||
const auto & node = get(cur);
|
||||
visitor(node);
|
||||
stack.insert(stack.end(), node.children.rbegin(), node.children.rend());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,8 +721,9 @@ struct parser_executor {
|
||||
return common_peg_parse_result(COMMON_PEG_PARSE_RESULT_SUCCESS, start_pos, pos, {}, std::move(invalid_utf8));
|
||||
}
|
||||
|
||||
if (match == common_trie::PARTIAL_MATCH) {
|
||||
// Found a partial match extending to end of input, return everything before it
|
||||
if (match == common_trie::PARTIAL_MATCH && !ctx.is_complete()) {
|
||||
// Found a partial match extending to end of input, return everything before it.
|
||||
// On a complete input the delimiter can no longer form, so the bytes belong to the text
|
||||
return common_peg_parse_result(COMMON_PEG_PARSE_RESULT_SUCCESS, start_pos, pos, {}, std::move(invalid_utf8));
|
||||
}
|
||||
|
||||
@@ -736,8 +743,15 @@ struct parser_executor {
|
||||
}
|
||||
|
||||
common_peg_parse_result operator()(const common_peg_rule_parser & p) {
|
||||
// only rules recurse, a recursive rule is entered once per input level
|
||||
if (ctx.rule_depth[p.child] >= RULE_RECURSIVE_LIMIT) {
|
||||
throw common_peg_recursion_error("input is nested more than " + std::to_string(RULE_RECURSIVE_LIMIT) + " levels deep");
|
||||
}
|
||||
|
||||
// Parse the child
|
||||
ctx.rule_depth[p.child]++;
|
||||
auto result = arena.parse(p.child, ctx, start_pos);
|
||||
ctx.rule_depth[p.child]--;
|
||||
|
||||
if (!result.fail()) {
|
||||
std::string_view text;
|
||||
@@ -819,6 +833,10 @@ common_peg_parse_result common_peg_arena::parse(common_peg_parse_context & ctx,
|
||||
if (root_ == COMMON_PEG_INVALID_PARSER_ID) {
|
||||
throw std::runtime_error("No root parser set");
|
||||
}
|
||||
|
||||
// Initialize rule depth for all parsers
|
||||
ctx.rule_depth.assign(parsers_.size(), 0);
|
||||
|
||||
return parse(root_, ctx, start);
|
||||
}
|
||||
|
||||
@@ -1069,11 +1087,6 @@ common_peg_parser operator|(const std::string & str, const common_peg_parser & p
|
||||
return operator|(str.c_str(), p);
|
||||
}
|
||||
|
||||
static std::string rule_name(const std::string & name) {
|
||||
static const std::regex invalid_rule_chars_re("[^a-zA-Z0-9-]+");
|
||||
return std::regex_replace(name, invalid_rule_chars_re, "-");
|
||||
}
|
||||
|
||||
common_peg_parser_builder::common_peg_parser_builder() {}
|
||||
|
||||
common_peg_parser common_peg_parser_builder::sequence(const std::vector<common_peg_parser_id> & parsers) {
|
||||
@@ -1155,14 +1168,14 @@ common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p,
|
||||
}
|
||||
|
||||
common_peg_parser common_peg_parser_builder::rule(const std::string & name, const common_peg_parser & p, bool trigger) {
|
||||
auto clean_name = rule_name(name);
|
||||
auto clean_name = gbnf_format_rule_name(name);
|
||||
auto rule_id = arena_.add_parser(common_peg_rule_parser{clean_name, p.id(), trigger});
|
||||
arena_.add_rule(clean_name, rule_id);
|
||||
return ref(clean_name);
|
||||
}
|
||||
|
||||
common_peg_parser common_peg_parser_builder::rule(const std::string & name, const std::function<common_peg_parser()> & builder_fn, bool trigger) {
|
||||
auto clean_name = rule_name(name);
|
||||
auto clean_name = gbnf_format_rule_name(name);
|
||||
if (arena_.has_rule(clean_name)) {
|
||||
return ref(clean_name);
|
||||
}
|
||||
|
||||
+11
-3
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -161,9 +162,10 @@ struct common_peg_parse_result {
|
||||
};
|
||||
|
||||
enum common_peg_parse_flags {
|
||||
COMMON_PEG_PARSE_FLAG_NONE = 0,
|
||||
COMMON_PEG_PARSE_FLAG_LENIENT = 1 << 0,
|
||||
COMMON_PEG_PARSE_FLAG_DEBUG = 1 << 1,
|
||||
COMMON_PEG_PARSE_FLAG_NONE = 0,
|
||||
COMMON_PEG_PARSE_FLAG_LENIENT = 1 << 0,
|
||||
COMMON_PEG_PARSE_FLAG_DEBUG = 1 << 1,
|
||||
COMMON_PEG_PARSE_FLAG_COMPLETE = 1 << 2,
|
||||
};
|
||||
|
||||
inline common_peg_parse_flags operator|(common_peg_parse_flags a, common_peg_parse_flags b) {
|
||||
@@ -182,6 +184,10 @@ inline common_peg_parse_flags operator~(common_peg_parse_flags a) {
|
||||
return static_cast<common_peg_parse_flags>(~int(a));
|
||||
}
|
||||
|
||||
struct common_peg_recursion_error : std::invalid_argument {
|
||||
using std::invalid_argument::invalid_argument;
|
||||
};
|
||||
|
||||
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]
|
||||
@@ -189,6 +195,7 @@ struct common_peg_parse_context {
|
||||
common_peg_ast_arena ast;
|
||||
|
||||
int parse_depth;
|
||||
std::vector<int> rule_depth; // index: rule child id
|
||||
|
||||
common_peg_parse_context(common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_NONE)
|
||||
: flags(flags), parse_depth(0) {}
|
||||
@@ -203,6 +210,7 @@ struct common_peg_parse_context {
|
||||
|
||||
bool is_lenient() const { return flags & COMMON_PEG_PARSE_FLAG_LENIENT; }
|
||||
bool is_debug() const { return flags & COMMON_PEG_PARSE_FLAG_DEBUG; }
|
||||
bool is_complete() const { return flags & COMMON_PEG_PARSE_FLAG_COMPLETE; }
|
||||
};
|
||||
|
||||
class common_peg_arena;
|
||||
|
||||
+60
-3
@@ -19,6 +19,7 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <cinttypes>
|
||||
|
||||
#define SPC_DBG(fmt, ...) LOG_DBG("spec %12.*s: " fmt, 12, __func__, __VA_ARGS__)
|
||||
@@ -1010,6 +1011,10 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
bool is_mrope = false;
|
||||
int32_t selector_top_k = 0;
|
||||
|
||||
// DFlash2 probabilistic drafting: one generator per sequence and the seed it was last seeded with (-1: reseed)
|
||||
std::vector<std::mt19937> dflash2_rng;
|
||||
std::vector<int64_t> dflash2_seed;
|
||||
|
||||
// draft-dspark: the draft carries a Markov head and uses an anchor-first block layout
|
||||
const bool is_dspark;
|
||||
|
||||
@@ -1060,6 +1065,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
selector_top_k = llama_model_dflash_selector_top_k(model_dft);
|
||||
is_dflash2 = selector_top_k > 0;
|
||||
dflash2_rng.resize(n_seq);
|
||||
dflash2_seed.assign(n_seq, -1);
|
||||
char lilicorr_meta[32] = {};
|
||||
lilicorr_top_k = llama_model_meta_val_str(model_dft, "lilicorr.candidate_topk", lilicorr_meta, sizeof(lilicorr_meta)) >= 0
|
||||
? std::atoi(lilicorr_meta) : 0;
|
||||
@@ -1158,6 +1165,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
return;
|
||||
}
|
||||
|
||||
// reseed per request rather than per round, or two identical requests differ
|
||||
dflash2_seed[seq_id] = -1;
|
||||
|
||||
const int32_t N = (int32_t) prompt.size();
|
||||
if (N <= 0) {
|
||||
return;
|
||||
@@ -1338,6 +1348,21 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
const float * lattice = llama_get_embeddings_nextn(ctx_dft);
|
||||
GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
|
||||
|
||||
// DFlash2 can instead sample each step and hand q to the verifier for rejection sampling
|
||||
const bool sample = is_dflash2 && params.probabilistic && dp.result_q;
|
||||
|
||||
// greedy keeps temperature 1, sampling uses the target's
|
||||
const float temp = sample ? dp.temp : 1.0f;
|
||||
|
||||
if (sample && dflash2_seed[seq_id] != dp.seed) {
|
||||
dflash2_seed[seq_id] = dp.seed;
|
||||
// the default seed means random; mixed so it differs from the target's, as in spec_retune
|
||||
dflash2_rng[seq_id].seed(dp.seed == LLAMA_DEFAULT_SEED ? std::random_device{}() : dp.seed ^ 0x85ebca6bu);
|
||||
}
|
||||
|
||||
std::vector<float> probs;
|
||||
std::vector<int32_t> sorted_idx;
|
||||
|
||||
int32_t predecessor = 0;
|
||||
for (int32_t i = 1; i < (is_dflash2 ? n_block_tokens : params.n_max + 1); ++i) {
|
||||
const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
|
||||
@@ -1346,21 +1371,53 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
predecessor = (int32_t) std::distance(scores,
|
||||
std::max_element(scores, scores + top_k));
|
||||
if (params.p_min > 0.0f) {
|
||||
// softmax(scores) at the argmax, i.e. 1 / sum(exp(s_k - s_max))
|
||||
if (params.p_min > 0.0f || sample) {
|
||||
// softmax(scores / temp) at the argmax, i.e. 1 / sum(exp((s_k - s_max) / temp))
|
||||
probs.resize(top_k);
|
||||
float sum = 0.0f;
|
||||
for (int32_t k = 0; k < top_k; ++k) {
|
||||
sum += std::exp(scores[k] - scores[predecessor]);
|
||||
probs[k] = std::exp((scores[k] - scores[predecessor]) / temp);
|
||||
sum += probs[k];
|
||||
}
|
||||
if (1.0f / sum < params.p_min) {
|
||||
break;
|
||||
}
|
||||
for (int32_t k = 0; k < top_k; ++k) {
|
||||
probs[k] /= sum;
|
||||
}
|
||||
}
|
||||
if (sample) {
|
||||
// ggml_top_k leaves candidates unordered, so walk them by token id to keep a seed's draft stable
|
||||
sorted_idx.resize(top_k);
|
||||
for (int32_t k = 0; k < top_k; ++k) {
|
||||
sorted_idx[k] = k;
|
||||
}
|
||||
std::sort(sorted_idx.begin(), sorted_idx.end(), [row](int32_t a, int32_t b) { return row[a] < row[b]; });
|
||||
|
||||
// a round-off overshoot keeps the argmax, which has q > 0
|
||||
double u = std::uniform_real_distribution<double>(0.0, 1.0)(dflash2_rng[seq_id]);
|
||||
for (const int32_t k : sorted_idx) {
|
||||
u -= probs[k];
|
||||
if (u < 0.0) {
|
||||
predecessor = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto & q = dp.result_q->emplace_back();
|
||||
q.reserve(top_k);
|
||||
for (int32_t k = 0; k < top_k; ++k) {
|
||||
q.push_back({ (llama_token) row[k], scores[k], probs[k] });
|
||||
}
|
||||
}
|
||||
result.push_back((llama_token) row[predecessor]);
|
||||
}
|
||||
|
||||
if (result.size() < (size_t) params.n_min) {
|
||||
result.clear();
|
||||
if (sample) {
|
||||
dp.result_q->clear();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ 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
|
||||
// candidate distribution per drafted token; set it to make draft-simple, draft-mtp and draft-dflash (DFlash2 only) sample
|
||||
std::vector<std::vector<llama_token_data>> * result_q = nullptr;
|
||||
|
||||
// the target's temp and seed, read only when the drafter samples probabilistically
|
||||
|
||||
@@ -70,6 +70,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"LingDSparkModel": "qwen",
|
||||
"DeepseekV4ForCausalLM": "deepseek",
|
||||
"DeepseekV4DSparkModel": "deepseek",
|
||||
"DeepseekV41ForCausalLM": "deepseek41",
|
||||
"DistilBertForMaskedLM": "bert",
|
||||
"DistilBertForSequenceClassification": "bert",
|
||||
"DistilBertModel": "bert",
|
||||
@@ -171,6 +172,10 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"D1OmniModel": "lfm2",
|
||||
"Lfm2Model": "lfm2",
|
||||
"Lfm2MoeForCausalLM": "lfm2",
|
||||
"Lfm3D1Model": "lfm3",
|
||||
"Lfm3ForCausalLM": "lfm3",
|
||||
"Lfm3OmniForConditionalGeneration": "lfm3",
|
||||
"Lfm3VlForConditionalGeneration": "lfm3",
|
||||
"Llama4ForCausalLM": "llama",
|
||||
"Llama4ForConditionalGeneration": "llama",
|
||||
"LlamaBidirectionalModel": "llama",
|
||||
@@ -196,6 +201,8 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"MiniCPMForCausalLM": "minicpm",
|
||||
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMOCRForSequenceClassification": "qwen",
|
||||
"MiniCPMOcrForConditionalGeneration": "minicpm",
|
||||
"MiniMaxText01ForCausalLM": "minimax",
|
||||
"MiniMaxM1ForCausalLM": "minimax",
|
||||
"MiniMaxM2ForCausalLM": "minimax",
|
||||
@@ -285,6 +292,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"SeedOssForCausalLM": "olmo",
|
||||
"SmallThinkerForCausalLM": "smallthinker",
|
||||
"SmolLM3ForCausalLM": "llama",
|
||||
"SopranoTextModel": "soprano",
|
||||
"Spark2_5ForCausalLM": "spark2_5",
|
||||
"SolarOpenForCausalLM": "glm",
|
||||
"StableLMEpochForCausalLM": "stablelm",
|
||||
@@ -324,6 +332,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"VllmD3Model": "vllm_d3",
|
||||
"DeepseekOCR2ForCausalLM": "deepseek",
|
||||
"DeepseekOCRForCausalLM": "deepseek",
|
||||
"DeepseekV41ForCausalLM": "deepseek41",
|
||||
"DeepseekV4ForCausalLM": "deepseek",
|
||||
"Dots3NoteForCausalLM": "dots3",
|
||||
"Dots3NoteForConditionalGeneration": "dots3",
|
||||
@@ -353,6 +362,9 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"Lfm2AudioForConditionalGeneration": "lfm2",
|
||||
"D1OmniModel": "lfm2",
|
||||
"Lfm2VlForConditionalGeneration": "lfm2",
|
||||
"Lfm3AudioForConditionalGeneration": "lfm3",
|
||||
"Lfm3OmniForConditionalGeneration": "lfm3",
|
||||
"Lfm3VlForConditionalGeneration": "lfm3",
|
||||
"LightOnOCRForConditionalGeneration": "lighton_ocr",
|
||||
"Llama4ForConditionalGeneration": "llama4",
|
||||
"LlavaForConditionalGeneration": "llava",
|
||||
@@ -361,6 +373,8 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"MiniMaxM3SparseForConditionalGeneration": "minimax",
|
||||
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMOCRForSequenceClassification": "qwen3vl",
|
||||
"MiniCPMOcrForConditionalGeneration": "minicpm",
|
||||
"Mistral3ForConditionalGeneration": "llava",
|
||||
"Mistral3Model": "llava",
|
||||
"NemotronH_Omni_Reasoning_V3": "nemotron",
|
||||
@@ -378,6 +392,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"Qwen3ASRForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3OmniMoeForConditionalGeneration": "qwen3vl",
|
||||
"PocketTTSModel": "pockettts",
|
||||
"SopranoModel": "soprano",
|
||||
"Qwen3TTSForConditionalGeneration": "qwen3tts",
|
||||
"Qwen3VLForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
|
||||
|
||||
@@ -1837,6 +1837,9 @@ class TextModel(ModelBase):
|
||||
if chkhsh == "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35":
|
||||
# ref: https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0
|
||||
res = "minerva-7b"
|
||||
if chkhsh == "6e4d72c6e5260b90606e26a0931fa15e4b42ec6e97719d12919f2b1f93478262":
|
||||
# ref: https://huggingface.co/domyn/Domyn-Small-v1.0
|
||||
res = "domyn-small"
|
||||
if chkhsh == "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664":
|
||||
# ref: https://huggingface.co/tencent/Hunyuan-A13B-Instruct
|
||||
res = "hunyuan"
|
||||
|
||||
@@ -803,9 +803,6 @@ class DeepseekV4Model(TextModel):
|
||||
self._dsv4_mxfp4_generated = True
|
||||
return ()
|
||||
|
||||
def _format_dsv4_tensor_name(self, key: gguf.MODEL_TENSOR, bid: int | None, suffix: str = ".weight") -> str:
|
||||
return self.format_tensor_name(key, bid, suffix)
|
||||
|
||||
def _map_dsv4_tensor_name(self, name: str, bid: int | None) -> tuple[gguf.MODEL_TENSOR, str]:
|
||||
root_map: dict[str, tuple[gguf.MODEL_TENSOR, str]] = {
|
||||
"embed.weight": (gguf.MODEL_TENSOR.TOKEN_EMBD, ".weight"),
|
||||
@@ -879,17 +876,17 @@ class DeepseekV4Model(TextModel):
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if re.match(r"layers\.\d+\.ffn\.experts\.\d+\.w[123]\.(weight|scale)$", name):
|
||||
return []
|
||||
return
|
||||
|
||||
# hash layers route text tokens via tid2eid and image tokens via bias_vl; gate.bias is unused
|
||||
if name.endswith(".ffn.gate.bias") and bid is not None and bid < self.hparams["num_hash_layers"]:
|
||||
return []
|
||||
return
|
||||
|
||||
tensor_key, suffix = self._map_dsv4_tensor_name(name, bid)
|
||||
if tensor_key == gguf.MODEL_TENSOR.FFN_GATE_TID2EID:
|
||||
return []
|
||||
return
|
||||
|
||||
return [(self._format_dsv4_tensor_name(tensor_key, bid, suffix), data_torch)]
|
||||
yield self.format_tensor_name(tensor_key, bid, suffix), data_torch
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
del bid # unused
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING, cast
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import LazyTorchTensor, ModelBase, TextModel, gguf
|
||||
from .deepseek import DeepseekV4FlashVisionModel, DeepseekV4Model
|
||||
|
||||
|
||||
# Engram hash constants from inference/engram.py, stored in the GGUF: the token map needs the HF tokenizers
|
||||
# normalizers and the multipliers numpy's PCG64 stream, neither of which the runtime has.
|
||||
|
||||
def _is_prime(n: int) -> bool:
|
||||
if n < 2 or n % 2 == 0:
|
||||
return n == 2
|
||||
return all(n % d for d in range(3, math.isqrt(n) + 1, 2))
|
||||
|
||||
|
||||
def engram_primes(n_layers: int, max_ngram_size: int, n_heads: int, vocab_size: int) -> list[list[list[int]]]:
|
||||
"""EngramLayout.from_args: [layer][ngram order 2..max][head] bucket moduli, never reused across layers."""
|
||||
seen: set[int] = set()
|
||||
out: list[list[list[int]]] = []
|
||||
for _ in range(n_layers):
|
||||
per_ngram = []
|
||||
for _ in range(max_ngram_size - 1):
|
||||
sizes, cur = [], vocab_size - 1
|
||||
for _ in range(n_heads):
|
||||
cand = cur + 1
|
||||
while not _is_prime(cand) or cand in seen:
|
||||
cand += 1
|
||||
cur = cand
|
||||
seen.add(cur)
|
||||
sizes.append(cur)
|
||||
per_ngram.append(sizes)
|
||||
out.append(per_ngram)
|
||||
return out
|
||||
|
||||
|
||||
def engram_multipliers(layer_ids: list[int], max_ngram_size: int, compressed_vocab_size: int) -> list[list[int]]:
|
||||
"""compute_hash_multipliers: odd, bounded so that token_id * multiplier cannot overflow int64."""
|
||||
bound = max(1, (np.iinfo(np.int64).max // compressed_vocab_size) // 2)
|
||||
rows = []
|
||||
for layer_id in layer_ids:
|
||||
rng = np.random.default_rng(10007 * layer_id)
|
||||
values = rng.integers(low=0, high=bound, size=(max_ngram_size,), dtype=np.int64)
|
||||
rows.append([int(v) * 2 + 1 for v in values])
|
||||
return rows
|
||||
|
||||
|
||||
def engram_compressed_token_map(tokenizer_dir: Path) -> tuple[list[int], int]:
|
||||
"""build_compressed_token_map: token id -> compressed id (tokens that normalize alike collapse)."""
|
||||
from tokenizers import Regex, normalizers
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(tokenizer_dir)
|
||||
sentinel = "\ue000"
|
||||
normalizer = normalizers.Sequence([
|
||||
normalizers.NFKC(),
|
||||
normalizers.NFD(),
|
||||
normalizers.StripAccents(),
|
||||
normalizers.Lowercase(),
|
||||
normalizers.Replace(Regex(r"[ \t\r\n]+"), " "),
|
||||
normalizers.Replace(Regex(r"^ $"), sentinel),
|
||||
normalizers.Strip(),
|
||||
normalizers.Replace(sentinel, " "),
|
||||
])
|
||||
backend = tokenizer.backend_tokenizer
|
||||
key_to_new: dict[str, int] = {}
|
||||
lookup = [0] * len(tokenizer)
|
||||
for token_id in range(len(tokenizer)):
|
||||
text = backend.decode([token_id], skip_special_tokens=False)
|
||||
if "\ufffd" in text:
|
||||
key = backend.id_to_token(token_id)
|
||||
else:
|
||||
normalized = normalizer.normalize_str(text)
|
||||
key = normalized if normalized else text
|
||||
lookup[token_id] = key_to_new.setdefault(key, len(key_to_new))
|
||||
return lookup, len(key_to_new)
|
||||
|
||||
|
||||
class _StreamedBytes:
|
||||
"""Raw tensor bytes produced chunk by chunk at write time (an Engram table is ~200 GB in BF16)."""
|
||||
def __init__(self, shape: tuple[int, ...], chunks: Callable[[], Iterable[np.ndarray]]):
|
||||
self.shape = shape
|
||||
self.dtype = np.dtype(np.uint8)
|
||||
self.nbytes = int(np.prod(shape))
|
||||
self._chunks = chunks
|
||||
|
||||
def tofile(self, fout) -> None:
|
||||
written = 0
|
||||
for chunk in self._chunks():
|
||||
chunk = np.ascontiguousarray(chunk, dtype=np.uint8)
|
||||
chunk.tofile(fout)
|
||||
written += chunk.nbytes
|
||||
if written != self.nbytes:
|
||||
raise ValueError(f"streamed {written} bytes, expected {self.nbytes}")
|
||||
|
||||
def byteswap(self, inplace: bool = False):
|
||||
raise NotImplementedError("streamed tensors are written little-endian only")
|
||||
|
||||
|
||||
def _e8m0_check(scale_u8: np.ndarray, name: str) -> None:
|
||||
# e4m3 (|x| in [2^-9, 448]) * 2^(e - 127) is exact in BF16 for e in [3, 246]
|
||||
if scale_u8.size and (int(scale_u8.min()) < 3 or int(scale_u8.max()) > 246):
|
||||
raise ValueError(f"{name}: E8M0 scale exponents outside [3, 246], the dequantized values would not be exact")
|
||||
|
||||
|
||||
@ModelBase.register("DeepseekV41ForCausalLM")
|
||||
class DeepseekV41Model(DeepseekV4Model):
|
||||
"""DeepSeek-V4.1 text model, lossless: FP8 (e4m3, 32x32 ue8m0 blocks) linears dequantized to BF16 (F32 with
|
||||
--outtype f32), routed experts copied bit-exact as MXFP4, Engram tables (e4m3 rows, one e8m0 scale per 32) as the
|
||||
exact BF16 lookup (F32 with --outtype f32), router and Engram gate F32. DSpark (mtp.*) is not converted here
|
||||
(--dspark, DeepseekV41DSparkModel); the vision tower goes to an mmproj (--mmproj, DeepseekV41VisionModel).
|
||||
The Engram tables of all layers are written as one per_layer_token_embd (rows concatenated in layer order) with
|
||||
the PLE keys of qwen4exp: absolute head offsets and bucket counts, so the runtime can read it lazily."""
|
||||
model_arch = gguf.MODEL_ARCH.DEEPSEEK41
|
||||
supports_mtp_export = False
|
||||
|
||||
_ENGRAM_EMBED_RE = re.compile(r"layers\.(\d+)\.engram\.embed\.(weight|scale)$")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.hparams.setdefault("num_hash_layers", 0) # no hash-routed layers in V4.1
|
||||
self._f32 = self.ftype == gguf.LlamaFileType.ALL_F32
|
||||
qc = self.hparams.get("quantization_config") or {}
|
||||
self._fp8_block = tuple(qc.get("weight_block_size") or (32, 32))
|
||||
if qc.get("scale_fmt", "ue8m0") != "ue8m0":
|
||||
raise ValueError(f"unsupported FP8 scale format {qc.get('scale_fmt')!r}")
|
||||
if self.hparams.get("gate_temp", 1.0) != 1.0:
|
||||
raise ValueError("gate_temp != 1 is not supported (the router logits would need dividing by it)")
|
||||
|
||||
# V4.1 does not use the DeepSeek-V4 template; a template shipped with the model is still added by set_vocab
|
||||
self.gguf_writer.remove_key(gguf.Keys.Tokenizer.CHAT_TEMPLATE)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
# num_hidden_layers lives in text_config, which is merged only after indexing; MTP stages are skipped anyway
|
||||
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hp = self.hparams
|
||||
w = self.gguf_writer
|
||||
w.add_attention_kv_source_layers(hp["kv_source_layer_ids"])
|
||||
w.add_indexer_source_layers(hp["index_source_layer_ids"])
|
||||
if hp.get("candidate_source_layer_id", -1) >= 0:
|
||||
w.add_indexer_candidate_source_layer(hp["candidate_source_layer_id"])
|
||||
w.add_indexer_candidate_top_k_blocks(hp["candidate_topk_blocks"])
|
||||
w.add_indexer_candidate_block_size(hp["candidate_block_size"])
|
||||
|
||||
# GGUF cannot hold empty arrays: the Engram keys are omitted when the model has no Engram layers
|
||||
layer_ids = list(hp.get("engram_layer_ids") or [])
|
||||
if layer_ids:
|
||||
max_ngram, n_heads = hp["engram_max_ngram_size"], hp["engram_n_heads"]
|
||||
primes = engram_primes(len(layer_ids), max_ngram, n_heads, hp["engram_vocab_size"])
|
||||
for li, layer_primes in enumerate(primes):
|
||||
if sum(p for per in layer_primes for p in per) != hp["engram_num_embeddings"][li]:
|
||||
raise ValueError(f"engram layer {layer_ids[li]}: prime sum != num_embeddings")
|
||||
token_map, cvocab = engram_compressed_token_map(self.dir_model)
|
||||
if cvocab != hp["engram_compressed_vocab_size"] or len(token_map) != hp["vocab_size"]:
|
||||
raise ValueError(f"compressed token map ({len(token_map)} -> {cvocab}) does not match the config")
|
||||
# absolute row offsets into the concatenated table
|
||||
sizes = [p for layer in primes for per in layer for p in per]
|
||||
offsets = [sum(sizes[:i]) for i in range(len(sizes))]
|
||||
w.add_key_value(gguf.Keys.PerLayerEmbedding.LAYERS.format(arch=w.arch), layer_ids,
|
||||
gguf.GGUFValueType.ARRAY, gguf.GGUFValueType.UINT32)
|
||||
w.add_ple_ngram_size(max_ngram)
|
||||
w.add_ple_heads_per_ngram(n_heads)
|
||||
w.add_embedding_length_per_layer_input(hp["engram_head_dim"])
|
||||
w.add_ple_layer_multipliers([m for row in engram_multipliers(layer_ids, max_ngram, cvocab) for m in row])
|
||||
w.add_ple_head_offsets(offsets)
|
||||
w.add_ple_head_vocab_sizes(sizes)
|
||||
w.add_engram_pad_token_id(hp["engram_pad_token_id"])
|
||||
w.add_engram_token_map(token_map)
|
||||
|
||||
def dequant_model(self):
|
||||
bo, bi = self._fp8_block
|
||||
|
||||
def dequant(weight: Tensor, scale: Tensor) -> Tensor:
|
||||
out_f, in_f = weight.shape
|
||||
s = self._e8m0_to_float(scale).repeat_interleave(bo, 0)[:out_f].repeat_interleave(bi, 1)[:, :in_f]
|
||||
return weight.float() * s
|
||||
|
||||
for name in [n for n in self.model_tensors if n.endswith(".scale") and not self._ENGRAM_EMBED_RE.match(n)]:
|
||||
weight_name = name.removesuffix(".scale") + ".weight"
|
||||
if weight_name not in self.model_tensors or self.model_tensors[weight_name]().dtype not in self._float8_dtypes():
|
||||
continue
|
||||
w, s = self.model_tensors[weight_name], self.model_tensors.pop(name)
|
||||
out_f, in_f = w().shape
|
||||
if tuple(s().shape) != (-(-out_f // bo), -(-in_f // bi)):
|
||||
raise ValueError(f"{weight_name}: scale shape {tuple(s().shape)} does not match {(bo, bi)} blocks")
|
||||
_e8m0_check(LazyTorchTensor.to_eager(s()).view(torch.uint8).numpy(), name)
|
||||
self.model_tensors[weight_name] = lambda w=w, s=s: dequant(w(), s())
|
||||
self._dsv4_fp8_dequantized.add(weight_name)
|
||||
|
||||
def _tensor_part(self, name: str) -> Path:
|
||||
index = self.dir_model / "model.safetensors.index.json"
|
||||
if not index.is_file():
|
||||
return self.dir_model / "model.safetensors"
|
||||
with open(index, "r", encoding="utf-8") as f:
|
||||
return self.dir_model / json.load(f)["weight_map"][name]
|
||||
|
||||
def _write_mxfp4_experts(self, bid: int, proj: str, key: gguf.MODEL_TENSOR, n_expert: int | None = None) -> None:
|
||||
n_expert = self.hparams["n_routed_experts"] if n_expert is None else n_expert
|
||||
names = [f"layers.{bid}.ffn.experts.{e}.{proj}" for e in range(n_expert)]
|
||||
pairs = [(self.model_tensors.pop(n + ".weight"), self.model_tensors.pop(n + ".scale")) for n in names]
|
||||
rows, packed_cols = pairs[0][0]().shape
|
||||
|
||||
def chunks() -> Iterable[np.ndarray]:
|
||||
for w, s in pairs:
|
||||
yield self.repack_mxfp4_blocks(LazyTorchTensor.to_eager(w()), LazyTorchTensor.to_eager(s()))
|
||||
data = cast(np.ndarray, _StreamedBytes((len(pairs), rows, packed_cols * 2 // 32 * 17), chunks))
|
||||
self.gguf_writer.add_tensor(self.format_tensor_name(key, bid), data, raw_dtype=gguf.GGMLQuantizationType.MXFP4)
|
||||
|
||||
def _write_engram_tables(self, layer_ids: list[int]) -> None:
|
||||
# official lookup e4m3 * 2^(e8m0 - 127), exact in BF16
|
||||
parts, dim = [], None
|
||||
for bid in layer_ids:
|
||||
wn, sn = f"layers.{bid}.engram.embed.weight", f"layers.{bid}.engram.embed.scale"
|
||||
rows, d = self.model_tensors.pop(wn)().shape
|
||||
if d % 32 != 0 or tuple(self.model_tensors.pop(sn)().shape) != (rows, d // 32) or d != (dim or d):
|
||||
raise ValueError(f"{wn}: expected one e8m0 scale per 32 elements and the same width in every layer")
|
||||
if rows != self.hparams["engram_num_embeddings"][len(parts)]:
|
||||
raise ValueError(f"{wn}: {rows} rows, config says {self.hparams['engram_num_embeddings'][len(parts)]}")
|
||||
dim = d
|
||||
parts.append((wn, sn, rows, self._tensor_part(wn), self._tensor_part(sn)))
|
||||
assert dim is not None
|
||||
name = self.format_tensor_name(gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD)
|
||||
n_rows = sum(p[2] for p in parts)
|
||||
|
||||
def chunks() -> Iterable[np.ndarray]:
|
||||
from safetensors import safe_open
|
||||
for wn, sn, rows, wpart, spart in parts:
|
||||
with safe_open(wpart, framework="pt") as fw, safe_open(spart, framework="pt") as fs:
|
||||
ws, ss = fw.get_slice(wn), fs.get_slice(sn)
|
||||
for r0 in range(0, rows, 1 << 18):
|
||||
q = ws[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 32)
|
||||
e = ss[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 1)
|
||||
_e8m0_check(e.numpy(), sn)
|
||||
v = (q.view(torch.float8_e4m3fn).float() * torch.exp2(e.float() - 127)).reshape(-1, dim)
|
||||
yield (v if self._f32 else v.bfloat16()).view(torch.uint8).numpy()
|
||||
qt = gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
|
||||
data = cast(np.ndarray, _StreamedBytes((n_rows, dim * (4 if self._f32 else 2)), chunks))
|
||||
self.gguf_writer.add_tensor(name, data, raw_dtype=qt)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
if not self._dsv4_mxfp4_generated:
|
||||
for bid in range(self.block_count):
|
||||
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP)
|
||||
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP)
|
||||
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP)
|
||||
if self.hparams.get("engram_layer_ids"):
|
||||
self._write_engram_tables(list(self.hparams["engram_layer_ids"]))
|
||||
self._dsv4_mxfp4_generated = True
|
||||
return ()
|
||||
|
||||
_LAYER_MAP: dict[str, tuple[gguf.MODEL_TENSOR, str]] = {
|
||||
"attn.indexer.wk.weight": (gguf.MODEL_TENSOR.INDEXER_ATTN_K, ".weight"),
|
||||
"attn.indexer.k_norm.weight": (gguf.MODEL_TENSOR.INDEXER_K_NORM, ".weight"),
|
||||
"engram.wkv.weight": (gguf.MODEL_TENSOR.ENGRAM_KV, ".weight"),
|
||||
}
|
||||
|
||||
def _map_dsv4_tensor_name(self, name: str, bid: int | None) -> tuple[gguf.MODEL_TENSOR, str]:
|
||||
m = re.match(r"layers\.\d+\.(.+)$", name)
|
||||
if m is not None and m.group(1) in self._LAYER_MAP:
|
||||
return self._LAYER_MAP[m.group(1)]
|
||||
return super()._map_dsv4_tensor_name(name, bid)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(".engram.k_weight"):
|
||||
return # folded into engram_gate
|
||||
if name.endswith(".engram.q_weight"):
|
||||
# Engram gate weight = q_weight * k_weight (bf16 * bf16 is exact in f32)
|
||||
k = self.model_tensors[name.removesuffix("q_weight") + "k_weight"]
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.ENGRAM_GATE, bid), data_torch.float() * k().float()
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
if new_name.endswith((".engram_gate.weight", ".ffn_gate_inp.weight")) or name in self._dsv4_f32_tensors:
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
if n_dims >= 2 and (name in self._dsv4_fp8_dequantized or name in self._dsv4_bf16_tensors):
|
||||
return gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
|
||||
return False
|
||||
|
||||
|
||||
class DeepseekV41DSparkModel(DeepseekV41Model):
|
||||
"""DeepSeek-V4.1 DSpark speculative draft (mtp.0..2 of the checkpoint) as a DFLASH GGUF (`--dspark`).
|
||||
|
||||
Layout = the V4 DSpark draft (conversion/deepseek.py DeepseekV4DSparkModel) with V4.1's differences flagged by
|
||||
`dflash.hyper_connection.single_pass`: stages blk.0..2 (MXFP4 experts bit-exact, dense BF16), mtp.0.main_proj /
|
||||
main_norm -> fc / enc.output_norm, mtp.2.norm -> output_norm, markov_head.embed / head -> markov_w1 / markov_w2,
|
||||
confidence_head.proj -> conf_proj (F32), no hc_head. Token embeddings and the lm head are the target's (shared at
|
||||
runtime through ctx_other). target_layers are the V4.1 ids as-is: the draft reads the attention input of those
|
||||
layers, which is what llama_set_embeddings_layer_inp extracts.
|
||||
"""
|
||||
model_arch = gguf.MODEL_ARCH.DFLASH
|
||||
mtp_only = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hp = self.hparams
|
||||
self._n_main = hp["num_hidden_layers"]
|
||||
self._n_stage = hp["num_nextn_predict_layers"]
|
||||
if self._n_stage <= 0 or "dspark_block_size" not in hp:
|
||||
raise ValueError("DeepSeek-V4.1 DSpark export needs num_nextn_predict_layers > 0 and the dspark_* config")
|
||||
# stages keep main-model block ids (mtp.M -> layers.n_main+M); format_tensor_name() maps them back
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self._n_stage)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
hp = {**self.hparams, **self.hparams.get("text_config", {})} # text_config is merged only after indexing
|
||||
type(self)._dsv4_main_layers = hp["num_hidden_layers"]
|
||||
type(self)._dsv4_nextn_layers = hp.get("num_nextn_predict_layers", 0)
|
||||
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
m = re.match(r"mtp\.(\d+)\.(.+)$", name)
|
||||
if m is None:
|
||||
return None # shared with the target at runtime
|
||||
stage, rest = int(m.group(1)), m.group(2)
|
||||
assert cls._dsv4_main_layers is not None
|
||||
if stage >= cls._dsv4_nextn_layers:
|
||||
raise ValueError(f"Unexpected DSpark stage {stage}")
|
||||
if rest.startswith(("markov_head.", "confidence_head.")):
|
||||
return f"dspark.{rest}", gen
|
||||
return f"layers.{cls._dsv4_main_layers + stage}.{rest}", gen
|
||||
|
||||
def format_tensor_name(self, key: gguf.MODEL_TENSOR, bid: int | None = None, suffix: str = ".weight") -> str:
|
||||
if bid is not None and bid >= self._n_main:
|
||||
bid -= self._n_main
|
||||
return super().format_tensor_name(key, bid, suffix)
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
self.gguf_writer.add_mask_token_id(self.hparams["dspark_noise_token_id"])
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
hp = self.hparams
|
||||
n = self._n_stage
|
||||
w = self.gguf_writer
|
||||
w.add_block_count(n)
|
||||
w.add_context_length(hp["max_position_embeddings"])
|
||||
w.add_embedding_length(hp["hidden_size"])
|
||||
w.add_feed_forward_length(hp["moe_intermediate_size"])
|
||||
w.add_head_count(hp["num_attention_heads"])
|
||||
w.add_head_count_kv(1)
|
||||
w.add_key_length(hp["head_dim"])
|
||||
w.add_value_length(hp["head_dim"])
|
||||
w.add_rope_dimension_count(hp["qk_rope_head_dim"])
|
||||
# compress-ratio-0 layers: plain rope at rope_theta, no YaRN
|
||||
w.add_rope_freq_base(float(hp["rope_theta"]))
|
||||
w.add_layer_norm_rms_eps(hp["rms_norm_eps"])
|
||||
w.add_q_lora_rank(hp["q_lora_rank"])
|
||||
w.add_sliding_window(hp["sliding_window"])
|
||||
w.add_expert_count(hp["dspark_n_routed_experts"])
|
||||
w.add_expert_used_count(hp["dspark_num_experts_per_tok"])
|
||||
w.add_expert_feed_forward_length(hp["moe_intermediate_size"])
|
||||
w.add_expert_shared_count(hp["n_shared_experts"])
|
||||
w.add_expert_weights_scale(hp["routed_scaling_factor"])
|
||||
w.add_expert_weights_norm(hp["norm_topk_prob"])
|
||||
w.add_expert_gating_func(gguf.ExpertGatingFuncType.SQRTSOFTPLUS)
|
||||
w.add_swiglu_clamp_exp([hp["swiglu_limit"]] * n)
|
||||
w.add_swiglu_clamp_shexp([hp["swiglu_limit"]] * n)
|
||||
w.add_attention_output_group_count(hp["o_groups"])
|
||||
w.add_attention_output_lora_rank(hp["o_lora_rank"])
|
||||
w.add_attention_compress_ratios([0] * n)
|
||||
w.add_hyper_connection_count(hp["hc_mult"])
|
||||
w.add_hyper_connection_sinkhorn_iterations(hp["hc_sinkhorn_iters"])
|
||||
w.add_hyper_connection_epsilon(hp["hc_eps"])
|
||||
w.add_hyper_connection_single_pass(True)
|
||||
w.add_block_size(hp["dspark_block_size"])
|
||||
w.add_target_layers(hp["dspark_target_layer_ids"])
|
||||
w.add_sample_from_anchor(True)
|
||||
w.add_has_confidence_head(True)
|
||||
w.add_file_type(self.ftype)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
# no Engram tables in the draft
|
||||
if not self._dsv4_mxfp4_generated:
|
||||
n_expert = self.hparams["dspark_n_routed_experts"]
|
||||
for bid in range(self._n_main, self._n_main + self._n_stage):
|
||||
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP, n_expert)
|
||||
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP, n_expert)
|
||||
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP, n_expert)
|
||||
self._dsv4_mxfp4_generated = True
|
||||
return ()
|
||||
|
||||
_DSPARK_ROOT: dict[str, gguf.MODEL_TENSOR] = {
|
||||
"main_proj.weight": gguf.MODEL_TENSOR.FC,
|
||||
"main_norm.weight": gguf.MODEL_TENSOR.ENC_OUTPUT_NORM,
|
||||
"norm.weight": gguf.MODEL_TENSOR.OUTPUT_NORM,
|
||||
"dspark.markov_head.embed.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W1,
|
||||
"dspark.markov_head.head.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W2,
|
||||
"dspark.confidence_head.proj.weight": gguf.MODEL_TENSOR.DSPARK_CONF_PROJ,
|
||||
}
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(".ffn.gate.bias_vl"):
|
||||
return
|
||||
if name in self._DSPARK_ROOT:
|
||||
yield self.format_tensor_name(self._DSPARK_ROOT[name], None), data_torch
|
||||
return
|
||||
m = re.match(r"layers\.\d+\.(main_proj\.weight|main_norm\.weight|norm\.weight)$", name)
|
||||
if m is not None:
|
||||
yield self.format_tensor_name(self._DSPARK_ROOT[m.group(1)], None), data_torch
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
if new_name == "conf_proj.weight":
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
|
||||
@ModelBase.register("DeepseekV41ForCausalLM")
|
||||
@ModelBase.example("deepseek-ai/DeepSeek-V4.1-Flash-1017")
|
||||
class DeepseekV41VisionModel(DeepseekV4FlashVisionModel):
|
||||
"""DeepSeek-V4.1 vision tower (projector deepseek41v): the V4-Flash-Vision ViT + 3x3 aligner, with the V4.1 token
|
||||
block ([START] ([IMAGE]*w [NEWLINE])*h [END], no pads) and resize plan. ref: inference/vision.py,
|
||||
inference/image_processor.py of deepseek-ai/DeepSeek-V4.1-Flash-1017. The checkpoint config is HF-style
|
||||
(vision_config); image_start / image_end / image_newline are written like V4's sentinels, there is no image_pad."""
|
||||
|
||||
def get_vision_config(self) -> dict[str, Any] | None:
|
||||
vc = self.global_config.get("vision_config")
|
||||
if not vc or vc.get("num_hidden_layers", 0) == 0:
|
||||
raise ValueError("DeepseekV41VisionModel requires vision_config.num_hidden_layers > 0")
|
||||
return {
|
||||
"num_hidden_layers": vc["num_hidden_layers"],
|
||||
"hidden_size": vc["hidden_size"],
|
||||
"num_attention_heads": vc["num_attention_heads"],
|
||||
"intermediate_size": vc["intermediate_size"],
|
||||
"patch_size": vc["patch_size"],
|
||||
# dynamic resolution; only used for compat / warmup
|
||||
"image_size": vc["patch_size"] * vc["downsample_ratio"] * 16,
|
||||
"rope_theta": vc.get("rope_theta", 10000.0),
|
||||
"downsample_ratio": vc["downsample_ratio"],
|
||||
"min_pixels": vc["min_pixels"],
|
||||
"max_image_tokens": vc["max_image_tokens"],
|
||||
"max_wh_ratio": vc.get("max_wh_ratio"),
|
||||
}
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
assert self.hparams_vision is not None
|
||||
super(DeepseekV4FlashVisionModel, self).set_gguf_parameters()
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.DEEPSEEK41V)
|
||||
# pytorch default eps (vision.py), not the LLM rms_norm_eps (1e-20)
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(1e-6)
|
||||
self.gguf_writer.add_vision_use_silu(True)
|
||||
self.gguf_writer.add_vision_projector_scale_factor(self.hparams_vision["downsample_ratio"])
|
||||
self.gguf_writer.add_vision_min_pixels(self.hparams_vision["min_pixels"])
|
||||
# hardcoded in clip.cpp (PROJECTOR_TYPE_DEEPSEEK41V); add GGUF keys if a later model differs
|
||||
assert self.hparams_vision["max_image_tokens"] == 1024, self.hparams_vision["max_image_tokens"]
|
||||
assert self.hparams_vision["max_wh_ratio"] is None, self.hparams_vision["max_wh_ratio"]
|
||||
assert self.hparams_vision["rope_theta"] == 10000, self.hparams_vision["rope_theta"]
|
||||
+21
-14
@@ -15,25 +15,30 @@ from .base import MmprojModel, ModelBase, TextModel, gguf, jinja_str_or_json, lo
|
||||
from .gemma import ConformerAudioModel
|
||||
|
||||
|
||||
def lfm2_feed_forward_length(hparams: dict[str, Any]) -> int:
|
||||
ff_dim = hparams.get("block_ff_dim", hparams.get("intermediate_size"))
|
||||
assert ff_dim is not None
|
||||
auto_adjust_ff_dim = hparams["block_auto_adjust_ff_dim"]
|
||||
ffn_dim_multiplier = hparams["block_ffn_dim_multiplier"]
|
||||
multiple_of = hparams["block_multiple_of"]
|
||||
|
||||
if auto_adjust_ff_dim:
|
||||
ff_dim = int(2 * ff_dim / 3)
|
||||
# custom dim factor multiplier
|
||||
if ffn_dim_multiplier is not None:
|
||||
ff_dim = int(ffn_dim_multiplier * ff_dim)
|
||||
ff_dim = multiple_of * ((ff_dim + multiple_of - 1) // multiple_of)
|
||||
|
||||
return ff_dim
|
||||
|
||||
|
||||
@ModelBase.register("Lfm2ForCausalLM", "LFM2ForCausalLM")
|
||||
@ModelBase.example("LiquidAI/LFM2-1.2B", "LiquidAI/LFM2.5-350M")
|
||||
class LFM2Model(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.LFM2
|
||||
|
||||
def _add_feed_forward_length(self):
|
||||
ff_dim = self.find_hparam(["block_ff_dim", "intermediate_size"])
|
||||
auto_adjust_ff_dim = self.hparams["block_auto_adjust_ff_dim"]
|
||||
ffn_dim_multiplier = self.hparams["block_ffn_dim_multiplier"]
|
||||
multiple_of = self.hparams["block_multiple_of"]
|
||||
|
||||
if auto_adjust_ff_dim:
|
||||
ff_dim = int(2 * ff_dim / 3)
|
||||
# custom dim factor multiplier
|
||||
if ffn_dim_multiplier is not None:
|
||||
ff_dim = int(ffn_dim_multiplier * ff_dim)
|
||||
ff_dim = multiple_of * ((ff_dim + multiple_of - 1) // multiple_of)
|
||||
|
||||
self.gguf_writer.add_feed_forward_length(ff_dim)
|
||||
self.gguf_writer.add_feed_forward_length(lfm2_feed_forward_length(self.hparams))
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
# set num_key_value_heads only for attention layers
|
||||
@@ -72,7 +77,9 @@ def _is_d1_checkpoint(dir_model: Path) -> bool:
|
||||
if not (dir_model / "config.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
return json.load(f).get("auto_map", {}).get("AutoModel", "").endswith(".D1Model")
|
||||
config = json.load(f)
|
||||
# d1 on LFM3 is converted by conversion/lfm3.py
|
||||
return config.get("auto_map", {}).get("AutoModel", "").endswith(".D1Model") and not config.get("model_type", "").startswith("lfm3")
|
||||
|
||||
|
||||
@ModelBase.register_hparams_loader(_is_d1_checkpoint)
|
||||
|
||||
@@ -0,0 +1,632 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import MmprojModel, ModelBase, TextModel, gguf, logger
|
||||
from .lfm2 import D1Model, lfm2_feed_forward_length
|
||||
|
||||
|
||||
@ModelBase.register("Lfm3ForCausalLM", "Lfm3VlForConditionalGeneration", "Lfm3OmniForConditionalGeneration")
|
||||
@ModelBase.example("LiquidAI/LFM3-1.7B", "LiquidAI/LFM3-1.7B-Base")
|
||||
class LFM3Model(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.LFM3
|
||||
|
||||
# LFM3-Audio/Omni: encoder, projector and audio-output heads live next to the text model
|
||||
_media_prefixes = ("encoder.", "mlp.", "codebook_embedding.", "depth_embeddings.", "depth_linear.", "depthformer.",
|
||||
"mask_embedding", "model.codebook_embedding.", "model.depth_decoder.")
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
if name.startswith(cls._media_prefixes):
|
||||
return None
|
||||
|
||||
name = name.removeprefix("lfm.")
|
||||
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
# LFM3-Audio live ASR prompt of the model card, the audio follows in the assistant turn
|
||||
_asr_template = (
|
||||
"{{- bos_token -}}"
|
||||
"{{- '<|im_start|>system\\nPerform live ASR.<|im_end|>\\n' -}}"
|
||||
"{%- if add_generation_prompt -%}{{- '<|im_start|>assistant\\n' -}}{%- endif -%}"
|
||||
)
|
||||
|
||||
# TTS prompt of the model card: a language code is looked up, anything else is taken as the language name
|
||||
_tts_langs = {
|
||||
"ar": "Arabic", "da": "Danish", "de": "German", "el": "Greek", "en": "American English", "en-us": "American English",
|
||||
"en-gb": "British English", "fi": "Finnish", "fr": "French", "he": "Hebrew", "it": "Italian", "ja": "Japanese",
|
||||
"ko": "Korean", "nl": "Dutch", "pl": "Polish", "pt": "Portuguese", "es": "Spanish", "sv": "Swedish", "tr": "Turkish",
|
||||
"zh": "Chinese",
|
||||
}
|
||||
|
||||
# "language" and "voice" are optional, the text to speak is the first message
|
||||
def _tts_template(self, think: bool) -> str:
|
||||
return (
|
||||
"{%- set names = " + json.dumps(self._tts_langs) + " -%}"
|
||||
"{%- set code = (language | lower) if language is defined and language else 'en' -%}"
|
||||
"{%- set lang = names[code] if code in names else language -%}"
|
||||
"{%- set v = (voice | lower) if voice is defined and voice and (voice | lower) in ['female', 'male'] else 'female' -%}"
|
||||
"{{- bos_token -}}"
|
||||
"{{- '<|im_start|>system\\nPerform TTS in ' + lang + ' using a ' + v + ' voice.<|im_end|>\\n' -}}"
|
||||
"{{- '<|im_start|>user\\n' + messages[0]['content'] + '<|im_end|>\\n' -}}"
|
||||
"{{- '<|im_start|>assistant\\n" + ("<think></think>" if think else "") + "<|audio_start|>' -}}"
|
||||
)
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
# hparams hold the merged text config, the checkpoint's own model_type is in config.json
|
||||
with open(self.dir_model / "config.json", encoding="utf-8") as f:
|
||||
model_type = json.load(f).get("model_type")
|
||||
is_audio = model_type == "lfm3_audio"
|
||||
is_omni = model_type == "lfm3_omni"
|
||||
templates = []
|
||||
if is_audio:
|
||||
templates.append({"name": "asr", "template": self._asr_template})
|
||||
if is_audio or is_omni:
|
||||
# LFM3-Omni opens every assistant turn with an empty think block
|
||||
templates.append({"name": "tts", "template": self._tts_template(think=is_omni)})
|
||||
if templates:
|
||||
self.gguf_writer.add_chat_template(templates)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
layer_types = self.hparams["layer_types"]
|
||||
n_kv_heads = self.hparams["num_key_value_heads"]
|
||||
|
||||
# set num_key_value_heads only for attention layers
|
||||
self.hparams["num_key_value_heads"] = [
|
||||
n_kv_heads if lt != "conv" else 0
|
||||
for lt in layer_types
|
||||
]
|
||||
|
||||
super().set_gguf_parameters()
|
||||
|
||||
self.gguf_writer.add_rope_dimension_count(self.hparams["head_dim"])
|
||||
|
||||
self.gguf_writer.add_sliding_window_pattern([lt == "sliding_attention" for lt in layer_types])
|
||||
self.gguf_writer.add_sliding_window(self.hparams["sliding_window"])
|
||||
|
||||
self.gguf_writer.add_shortconv_l_cache(self.hparams["conv_L_cache"])
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
# Partial RoPE is represented by masking trailing frequency pairs.
|
||||
# rope_parameters is keyed by layer type; only full_attention layers use partial RoPE.
|
||||
rope_parameters = self.hparams["rope_parameters"]["full_attention"]
|
||||
partial = rope_parameters.get("partial_rotary_factor", 1.0)
|
||||
head_dim = self.hparams["head_dim"]
|
||||
rope_angles = int(partial * head_dim) // 2
|
||||
n_pairs = head_dim // 2
|
||||
assert 0 < rope_angles <= n_pairs
|
||||
values = [1.0] * rope_angles + [1e30] * (n_pairs - rope_angles)
|
||||
rope_freqs = torch.tensor(values, dtype=torch.float32)
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.ROPE_FREQS), rope_freqs)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
# conv op requires 2d tensor
|
||||
if "conv.conv" in name:
|
||||
data_torch = data_torch.squeeze(1)
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
# d1 on LFM3-VL / LFM3-Omni: the stock weights, the d1 prompt read at its last token
|
||||
_D1_TYPES = {"lfm3_vl": gguf.DecisionType.LFM3_D1, "lfm3_omni": gguf.DecisionType.LFM3_D1_OMNI}
|
||||
|
||||
|
||||
def _is_lfm3_d1_checkpoint(dir_model: Path) -> bool:
|
||||
if not (dir_model / "config.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get("architectures") == ["D1Model"] and config.get("model_type") in _D1_TYPES
|
||||
|
||||
|
||||
@ModelBase.register_hparams_loader(_is_lfm3_d1_checkpoint)
|
||||
def _load_lfm3_d1_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
logger.info("gguf: detected LFM3 d1 checkpoint")
|
||||
hparams = ModelBase.load_hparams(dir_model, False, guess=False)
|
||||
hparams["d1_family"] = hparams["model_type"]
|
||||
# the mmproj is the base model's, the text model writes the decision
|
||||
hparams["architectures"] = ["Lfm3VlForConditionalGeneration" if hparams["model_type"] == "lfm3_vl" else "Lfm3OmniForConditionalGeneration"]
|
||||
hparams["text_config"]["architectures"] = ["Lfm3D1Model"]
|
||||
return hparams
|
||||
|
||||
|
||||
@ModelBase.register("Lfm3D1Model")
|
||||
@ModelBase.example("LiquidAI/d1-2B", "LiquidAI/d1-omni-2B")
|
||||
class LFM3D1Model(LFM3Model):
|
||||
model_arch = gguf.MODEL_ARCH.LFM3
|
||||
|
||||
# how the checkpoint serves, hard-coded in the server: assert the checkpoint records the same
|
||||
_served = {"d1_codes": 2, "d1_attention": "set"}
|
||||
|
||||
def set_vocab(self):
|
||||
# the vocab without the speech task templates of LFM3-Audio/Omni, d1 only decides
|
||||
TextModel.set_vocab(self)
|
||||
# the d1 prompt of LFM2, the LFM3 template writes an empty thought before the answer
|
||||
self.gguf_writer.add_chat_template([{"name": "systemone", "template": D1Model._systemone_template() + "<think></think>"}])
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
for key, value in self._served.items():
|
||||
if self.hparams.get(key) != value:
|
||||
raise ValueError(f"unsupported d1 checkpoint: {key} = {self.hparams.get(key)!r}, expected {value!r}")
|
||||
|
||||
# a lone question reads the whole prompt both ways on the full attention layers
|
||||
self.gguf_writer.add_causal_attention(False)
|
||||
self.gguf_writer.add_decision_type(_D1_TYPES[self.hparams["d1_family"]])
|
||||
|
||||
# the option answers share one temperature
|
||||
with open(self.dir_model / "calibration.json", encoding="utf-8") as f:
|
||||
calibration = json.load(f)
|
||||
if calibration.get("temperatures"):
|
||||
raise ValueError("unsupported d1 calibration: per-bucket temperatures")
|
||||
for name in ("noul", "choice", "score"):
|
||||
self.gguf_writer.add_decision_temperature(name, calibration["default"])
|
||||
|
||||
|
||||
class LFM3VLVisionMixin(MmprojModel):
|
||||
"""LFM3-VL vision tower and projector, shared with LFM3-Omni."""
|
||||
|
||||
def _set_lfm3vl_gguf_parameters(self):
|
||||
assert self.hparams_vision is not None
|
||||
# clip.cpp hardcodes the GELU projector and its input norm eps
|
||||
if (self.global_config["projector_hidden_act"] != "gelu" or not self.global_config["projector_bias"]
|
||||
or self.global_config["projector_layer_norm_eps"] != 1e-6):
|
||||
raise ValueError("unsupported projector_hidden_act / projector_bias / projector_layer_norm_eps")
|
||||
# clip.cpp hardcodes the vision rope base, the RGB patchifier and the 3x3 spatial conv
|
||||
if (self.hparams_vision["rope_parameters"]["rope_theta"] != 1e6 or self.hparams_vision["in_channels"] != 3
|
||||
or self.hparams_vision["conv_kernel_size"] != 3):
|
||||
raise ValueError("unsupported vision rope_theta / in_channels / conv_kernel_size")
|
||||
self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.LFM3VL)
|
||||
self.gguf_writer.add_vision_head_count_kv(self.hparams_vision["num_key_value_heads"])
|
||||
self.gguf_writer.add_vision_spatial_merge_size(self.global_config["downsample_factor"])
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision["norm_eps"])
|
||||
self.gguf_writer.add_vision_use_silu(True)
|
||||
size = self.preprocessor_config["size"]
|
||||
self.gguf_writer.add_vision_min_pixels(size["shortest_edge"])
|
||||
self.gguf_writer.add_vision_max_pixels(size["longest_edge"])
|
||||
# PIL resample enum, the torchvision backend has no lanczos
|
||||
resample = self.preprocessor_config["resample"]
|
||||
resize_algo = {2: "bilinear", 3: "bicubic"}.get(resample)
|
||||
if resize_algo is None:
|
||||
raise ValueError(f"unsupported resample: {resample}")
|
||||
self.gguf_writer.add_vision_image_resize_algo(resize_algo)
|
||||
self._check_video_config()
|
||||
|
||||
# mtmd_get_video_ts_style and the lfm3vl video frame sizing hardcode these, conversion fails on a mismatch
|
||||
_video = {"fps": 2.0, "min_frames": 4, "temporal_patch_size": 2, "size": {"shortest_edge": 4096, "longest_edge": 25165824}}
|
||||
_video_max_frames = 768
|
||||
|
||||
def _check_video_config(self):
|
||||
import json
|
||||
video = self.preprocessor_config.get("video_processor")
|
||||
path = self.dir_model / "video_preprocessor_config.json"
|
||||
if video is None and path.is_file():
|
||||
with open(path, encoding="utf-8") as f:
|
||||
video = json.load(f)
|
||||
if video is None:
|
||||
return
|
||||
for k, v in {**self._video, "max_frames": self._video_max_frames}.items():
|
||||
if video.get(k) != v:
|
||||
raise ValueError(f"unsupported video {k}={video.get(k)!r}, expected {v!r}")
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if "patch_embd" in new_name:
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
if "conv2d_spatial" in new_name:
|
||||
# CUDA CONV_2D_DW requires an f32 kernel; the tensor is tiny
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
def _lfm3vl_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name == "model.vision_tower.patchifier.weight":
|
||||
# qwen2vl-style layout [channel, temporal, ph, pw], one conv kernel per temporal slice
|
||||
assert self.hparams_vision is not None
|
||||
t = self.hparams_vision["temporal_patch_size"]
|
||||
p = self.hparams_vision["patch_size"]
|
||||
n_embd = data_torch.shape[0]
|
||||
w = data_torch.view(n_embd, 3, t, p, p)
|
||||
yield ("v.patch_embd.weight", w[:, :, 0].contiguous())
|
||||
if t == 2:
|
||||
yield ("v.patch_embd.weight.1", w[:, :, 1].contiguous())
|
||||
else:
|
||||
assert t == 1, f"unsupported vision_temporal_patch_size {t}"
|
||||
return
|
||||
if name == "model.vision_tower.embedding_norm.weight":
|
||||
yield ("v.embedding_norm.weight", data_torch)
|
||||
return
|
||||
if "conv.in_proj.weight" in name:
|
||||
yield (f"v.blk.{bid}.conv2d_in_proj.weight", data_torch)
|
||||
return
|
||||
if "conv.spatial_conv.weight" in name:
|
||||
yield (f"v.blk.{bid}.conv2d_spatial.weight", data_torch)
|
||||
return
|
||||
if "conv.out_proj.weight" in name:
|
||||
yield (f"v.blk.{bid}.conv2d_out_proj.weight", data_torch)
|
||||
return
|
||||
|
||||
# projector: input LayerNorm -> linear_1 -> GELU -> linear_2
|
||||
if name.startswith("model.multi_modal_projector."):
|
||||
layer, suffix = name.removeprefix("model.multi_modal_projector.").split(".")
|
||||
yield ({"layer_norm": "mm.input_norm", "linear_1": "mm.0", "linear_2": "mm.2"}[layer] + "." + suffix, data_torch)
|
||||
return
|
||||
|
||||
yield (self.map_tensor_name(name), data_torch)
|
||||
|
||||
|
||||
@ModelBase.register("Lfm3VlForConditionalGeneration")
|
||||
@ModelBase.example("LiquidAI/LFM3-VL-2B")
|
||||
class LFM3VLMmprojModel(LFM3VLVisionMixin):
|
||||
has_vision_encoder = True
|
||||
has_audio_encoder = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
assert self.hparams_vision is not None
|
||||
# dynamic resolution, image_size is only set for MmprojModel compatibility
|
||||
self.hparams_vision["image_size"] = 512
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self._set_lfm3vl_gguf_parameters()
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.startswith(("model.vision_tower.", "model.multi_modal_projector.")):
|
||||
yield from self._lfm3vl_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
def _is_lfm3_audio_checkpoint(dir_model: Path) -> bool:
|
||||
if not (dir_model / "config.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get("model_type") == "lfm3_audio" and "lfm" in config
|
||||
|
||||
|
||||
# read the release config.json as is: a transformers that knows lfm3_audio rewrites it into another layout
|
||||
@ModelBase.register_hparams_loader(_is_lfm3_audio_checkpoint)
|
||||
def _load_lfm3_audio_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
config["text_config"] = config["lfm"]
|
||||
return config
|
||||
|
||||
|
||||
@ModelBase.register("Lfm3AudioForConditionalGeneration")
|
||||
@ModelBase.example("LiquidAI/LFM3-Audio-2B")
|
||||
class LFM3AudioMmprojModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = True
|
||||
|
||||
# the downsampler has the LFM2-Audio pre-encoder layout: conv, dw conv, pw conv, dw conv, pw conv
|
||||
_downsample_idx = {"conv_in": 0, "conv_mid": 2, "lin_mid": 3, "conv_end": 5, "lin_end": 6}
|
||||
|
||||
# audio output: MaskGIT depthformer (a.gen.code.*) and the codec decoder (a.gen.wav.*)
|
||||
_depth_layer_map = {
|
||||
"operator_norm": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_NORM,
|
||||
"self_attn.q_proj": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_Q,
|
||||
"self_attn.q_norm": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_Q_NORM,
|
||||
"self_attn.k_proj": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_K,
|
||||
"self_attn.k_norm": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_K_NORM,
|
||||
"self_attn.v_proj": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_V,
|
||||
"self_attn.o_proj": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_OUT,
|
||||
"self_attn.gate_proj": gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_GATE,
|
||||
"ffn_norm": gguf.MODEL_TENSOR.A_GEN_CODE_FFN_NORM,
|
||||
"feed_forward.w1": gguf.MODEL_TENSOR.A_GEN_CODE_FFN_GATE,
|
||||
"feed_forward.w3": gguf.MODEL_TENSOR.A_GEN_CODE_FFN_UP,
|
||||
"feed_forward.w2": gguf.MODEL_TENSOR.A_GEN_CODE_FFN_DOWN,
|
||||
}
|
||||
_codec_layer_map = {
|
||||
"operator_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_NORM,
|
||||
"post_operator_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_POST_NORM,
|
||||
"self_attn.q_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_Q,
|
||||
"self_attn.q_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_Q_NORM,
|
||||
"self_attn.k_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_K,
|
||||
"self_attn.k_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_K_NORM,
|
||||
"self_attn.v_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_V,
|
||||
"self_attn.o_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_OUT,
|
||||
"self_attn.gate_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_ATTN_GATE,
|
||||
"conv.in_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_CONV_PW1,
|
||||
"conv.conv": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_CONV_DW,
|
||||
"conv.out_proj": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_CONV_PW2,
|
||||
"ffn_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_FFN_NORM,
|
||||
"post_ffn_norm": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_FFN_POST_NORM,
|
||||
"feed_forward.w1": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_FFN_GATE,
|
||||
"feed_forward.w3": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_FFN_UP,
|
||||
"feed_forward.w2": gguf.MODEL_TENSOR.A_GEN_WAV_TFM_FFN_DOWN,
|
||||
}
|
||||
# the codec graph in clip.cpp hardcodes the liquid-codec FSQ front end and decoder shape, conversion fails on a mismatch
|
||||
_codec = {
|
||||
"model_type": "liquid_codec", "fsq_levels": [8, 7, 6, 6], "n_q": 12, "quantizer_dim": 256,
|
||||
"encoder_dim": 512, "stft_n_fft": 1280, "stft_hop_length": 320, "stft_win_length": 1280, "stft_frame_stack": 6,
|
||||
"decoder_window": 30, "sampling_rate": 24000,
|
||||
}
|
||||
# the lfm3a pipeline in mtmd-helper-gen.cpp hardcodes these
|
||||
_generation = {"codebooks": 12, "maskgit_schedule": [4, 4, 4], "interleaved_n_text": 6, "interleaved_n_audio": 12}
|
||||
|
||||
# speech-code sampling of the model card, generation_config.json overrides it
|
||||
_audio_sampling = {
|
||||
"audio_temperature": 1.0, "audio_top_k": 128, "audio_repetition_penalty": 1.1, "audio_repetition_window": 4096,
|
||||
}
|
||||
|
||||
# mtmd_audio_preprocessor_lfm3a hardcodes this front end, conversion fails on a mismatch
|
||||
_feature_extractor = {
|
||||
"sampling_rate": 16000, "feature_size": 128, "n_fft": 400, "win_length": 400, "hop_length": 160,
|
||||
"log_floor": -12.0, "normalization_offset": 4.0, "normalization_divisor": 4.0, "encoder_stride": 8,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# audio output is exported when the checkpoint ships the codec
|
||||
self._audio_output = (self.dir_model / "audio_tokenizer").is_dir()
|
||||
gen = self.get_generation_config()
|
||||
self._n_codebooks: int = gen["codebooks"]
|
||||
self._audio_vocab: int = gen["audio_vocab_size"]
|
||||
self._depth_embd: dict[int, Tensor] = {}
|
||||
self._depth_norm: dict[int, Tensor] = {}
|
||||
|
||||
def get_audio_config(self) -> dict[str, Any] | None:
|
||||
enc = self.global_config["encoder"]
|
||||
cfg = dict(enc["lfm"])
|
||||
cfg["num_mel_bins"] = enc["dim_in"]
|
||||
cfg["sliding_window"] = enc["sliding_window"]
|
||||
return cfg
|
||||
|
||||
def get_generation_config(self) -> dict[str, Any]:
|
||||
c = self.global_config
|
||||
steps = c["maskgit_steps"]
|
||||
return {
|
||||
"codebooks": c["codebooks"], "audio_vocab_size": c["audio_vocab_size"],
|
||||
"maskgit_schedule": [c["codebooks"] // steps] * steps,
|
||||
"interleaved_n_text": c["interleaved_n_text"], "interleaved_n_audio": c["interleaved_n_audio"],
|
||||
}
|
||||
|
||||
def get_depth_config(self) -> dict[str, Any]:
|
||||
return self.global_config["depthformer"]
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
assert self.hparams_audio is not None
|
||||
fe = self.preprocessor_config["feature_extractor"]
|
||||
if fe["feature_extractor_type"] not in ("Lfm3AudioFeatureExtractor", "Lfm3OmniFeatureExtractor"):
|
||||
raise ValueError(f"unsupported feature_extractor_type {fe['feature_extractor_type']!r}")
|
||||
for k, v in self._feature_extractor.items():
|
||||
if fe[k] != v:
|
||||
raise ValueError(f"unsupported feature_extractor.{k}={fe[k]!r}, expected {v!r}")
|
||||
|
||||
# clip.cpp hardcodes the encoder rope base
|
||||
if self.hparams_audio["rope_parameters"]["rope_theta"] != 1e6:
|
||||
raise ValueError("unsupported audio encoder rope_theta")
|
||||
|
||||
self.hparams_audio["intermediate_size"] = lfm2_feed_forward_length(self.hparams_audio)
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_clip_audio_projector_type(gguf.VisionProjectorType.LFM3A)
|
||||
self.gguf_writer.add_audio_head_count_kv(self.hparams_audio["num_key_value_heads"])
|
||||
self.gguf_writer.add_audio_attention_layernorm_eps(self.hparams_audio["norm_eps"])
|
||||
self.gguf_writer.add_audio_num_mel_bins(self.hparams_audio["num_mel_bins"])
|
||||
self.gguf_writer.add_audio_window_size(self.hparams_audio["sliding_window"])
|
||||
|
||||
if not self._audio_output:
|
||||
return
|
||||
|
||||
# audio generation: depthformer hparams, the codec ones are constant and live in clip.cpp
|
||||
gen = self.get_generation_config()
|
||||
for k, v in self._generation.items():
|
||||
if gen[k] != v:
|
||||
raise ValueError(f"unsupported {k}={gen[k]!r}, expected {v!r}")
|
||||
depth = self.get_depth_config()
|
||||
if depth.get("is_causal", False) or depth["use_sandwich_norm"] or set(depth["layer_types"]) != {"full_attention"}:
|
||||
raise ValueError("unsupported depthformer, expected a bidirectional all-attention stack")
|
||||
if depth["rope_parameters"]["full_attention"]["rope_theta"] != 1e6:
|
||||
raise ValueError("unsupported depthformer rope_theta")
|
||||
self.gguf_writer.add_clip_has_gen_audio_encoder(True)
|
||||
self.gguf_writer.add_clip_gen_audio_projector_type(gguf.VisionProjectorType.LFM3A_GEN)
|
||||
self.gguf_writer.add_gen_audio_projection_dim(self.n_embd_text)
|
||||
self.gguf_writer.add_gen_audio_embedding_length(depth["hidden_size"])
|
||||
self.gguf_writer.add_gen_audio_feed_forward_length(depth["intermediate_size"])
|
||||
self.gguf_writer.add_gen_audio_block_count(depth["num_hidden_layers"])
|
||||
self.gguf_writer.add_gen_audio_head_count(depth["num_attention_heads"])
|
||||
self.gguf_writer.add_gen_audio_head_count_kv(depth["num_key_value_heads"])
|
||||
self.gguf_writer.add_gen_audio_attention_layernorm_eps(depth["norm_eps"])
|
||||
|
||||
# sampling defaults of the speech codes, next to the text ones that become general.sampling.*
|
||||
gen = gguf.Metadata.load_generation_config(self.dir_model)
|
||||
for key, add in (("audio_temperature", self.gguf_writer.add_gen_audio_sampling_temp),
|
||||
("audio_top_k", self.gguf_writer.add_gen_audio_sampling_top_k),
|
||||
("audio_repetition_penalty", self.gguf_writer.add_gen_audio_sampling_penalty_repeat),
|
||||
("audio_repetition_window", self.gguf_writer.add_gen_audio_sampling_penalty_last_n)):
|
||||
value = gen.get(key, self._audio_sampling.get(key))
|
||||
if value is not None:
|
||||
add(value)
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if new_name.startswith("a.conv1d.") or "conv_dw" in new_name or new_name.startswith("a.gen.wav.quant."):
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
# the codec decoder is small and its log-magnitude/phase output does not survive quantization
|
||||
if new_name.startswith("a.gen.wav."):
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
if self._audio_output:
|
||||
yield from self._codec_tensors()
|
||||
|
||||
def _codec_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
import json
|
||||
from safetensors.torch import load_file
|
||||
|
||||
T = gguf.MODEL_TENSOR
|
||||
codec_dir = self.dir_model / "audio_tokenizer"
|
||||
with open(codec_dir / "config.json", encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
for k, v in self._codec.items():
|
||||
if cfg.get(k) != v:
|
||||
raise ValueError(f"unsupported audio_tokenizer {k}={cfg.get(k)!r}, expected {v!r}")
|
||||
if math.prod(cfg["fsq_levels"]) != self._audio_vocab - 1:
|
||||
raise ValueError(f"audio_vocab_size {self._audio_vocab} does not match the codec, expected codebook size + 1")
|
||||
|
||||
state_dict = load_file(codec_dir / "model.safetensors")
|
||||
|
||||
# FSQ code table: index -> the len(levels) quantized values, see FiniteScalarQuantization._indices_to_codes
|
||||
levels = torch.tensor(cfg["fsq_levels"], dtype=torch.int64)
|
||||
basis = torch.cumprod(torch.cat([torch.ones(1, dtype=torch.int64), levels[:-1]]), dim=0)
|
||||
half = levels // 2
|
||||
idx = torch.arange(int(levels.prod()), dtype=torch.int64)[:, None]
|
||||
codes = ((idx // basis) % levels - half) / half
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_QUANT_FIRST_CB), codes.to(torch.float32))
|
||||
|
||||
# one projection over the concatenated code vectors of all codebooks, under quantizer.fsq. in newer exports
|
||||
quantizer = "quantizer.fsq." if "quantizer.fsq.project_out.weight" in state_dict else "quantizer."
|
||||
for suffix in ("weight", "bias"):
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_QUANT_FIRST_OUT, suffix=f".{suffix}"), state_dict[f"{quantizer}project_out.{suffix}"])
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_IN_PROJ, suffix=f".{suffix}"), state_dict[f"from_quantizer.{suffix}"])
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_OUT_PROJ, suffix=f".{suffix}"), state_dict[f"decoder.lin.{suffix}"])
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_OUTPUT_NORM), state_dict["decoder.lfm.embedding_norm.weight"])
|
||||
|
||||
for name, data_torch in state_dict.items():
|
||||
if not name.startswith("decoder.lfm.layers."):
|
||||
continue
|
||||
bid_str, key = name.removeprefix("decoder.lfm.layers.").rsplit(".weight", 1)[0].split(".", 1)
|
||||
if key == "conv.conv":
|
||||
data_torch = data_torch.squeeze(1)
|
||||
yield (self.format_tensor_name(self._codec_layer_map[key], int(bid_str)), data_torch)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.startswith("a.gen.wav."):
|
||||
yield (name, data_torch)
|
||||
return
|
||||
|
||||
if name.startswith(("depthformer.", "depth_", "codebook_embedding.", "mask_embedding")):
|
||||
if self._audio_output:
|
||||
yield from self._depthformer_tensor(data_torch, name, bid)
|
||||
return
|
||||
|
||||
if not name.startswith(("encoder.", "mlp.")):
|
||||
return
|
||||
|
||||
if name.startswith("encoder.downsample."):
|
||||
layer, suffix = name.removeprefix("encoder.downsample.").split(".")
|
||||
if layer == "lin_out":
|
||||
if suffix == "weight":
|
||||
data_torch = data_torch.squeeze(-1)
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_ENC_OUT, suffix=f".{suffix}"), data_torch)
|
||||
return
|
||||
if suffix == "bias":
|
||||
data_torch = data_torch[:, None, None]
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.A_ENC_CONV1D, self._downsample_idx[layer], f".{suffix}"), data_torch)
|
||||
return
|
||||
|
||||
# conv op requires 2d tensor
|
||||
if name.endswith("conv.conv.weight"):
|
||||
data_torch = data_torch.squeeze(1)
|
||||
|
||||
yield (self.map_tensor_name(name), data_torch)
|
||||
|
||||
def _depthformer_tensor(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
T = gguf.MODEL_TENSOR
|
||||
|
||||
if name.startswith("depthformer.model.layers."):
|
||||
key = name.removeprefix("depthformer.model.layers.").split(".", 1)[1].rsplit(".weight", 1)[0]
|
||||
yield (self.format_tensor_name(self._depth_layer_map[key], bid), data_torch)
|
||||
return
|
||||
if name == "depthformer.model.embedding_norm.weight":
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_OUTPUT_NORM), data_torch)
|
||||
return
|
||||
if name.startswith("depth_linear."):
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_PROJ_IN, suffix="." + name.rsplit(".", 1)[1]), data_torch)
|
||||
return
|
||||
if name == "mask_embedding":
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_MASK_EMBD, suffix=""), data_torch)
|
||||
return
|
||||
if name == "codebook_embedding.weight":
|
||||
assert data_torch.shape[0] == self._n_codebooks * self._audio_vocab
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_OUT_EMBD), data_torch)
|
||||
return
|
||||
|
||||
# per-codebook heads: the embedding table is tied to to_logits, merge the 12 tables into one tensor
|
||||
assert name.startswith("depth_embeddings.")
|
||||
idx = int(name.split(".")[1])
|
||||
key = name.split(".", 2)[2]
|
||||
if key == "embedding.weight":
|
||||
self._depth_embd[idx] = data_torch
|
||||
if len(self._depth_embd) == self._n_codebooks:
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_EMBD), torch.stack([self._depth_embd[i] for i in range(self._n_codebooks)], dim=0))
|
||||
elif key == "embedding_norm.weight":
|
||||
self._depth_norm[idx] = data_torch
|
||||
if len(self._depth_norm) == self._n_codebooks:
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_HEAD_NORM), torch.stack([self._depth_norm[i] for i in range(self._n_codebooks)], dim=0))
|
||||
elif key == "to_logits.weight":
|
||||
if not torch.equal(data_torch, self._depth_embd[idx]):
|
||||
raise ValueError(f"depth_embeddings.{idx} is not tied")
|
||||
else:
|
||||
raise ValueError(f"unexpected tensor {name}")
|
||||
|
||||
|
||||
@ModelBase.register("Lfm3OmniForConditionalGeneration")
|
||||
@ModelBase.example("LiquidAI/LFM3-Omni-2B")
|
||||
class LFM3OmniMmprojModel(LFM3VLVisionMixin, LFM3AudioMmprojModel):
|
||||
has_vision_encoder = True
|
||||
has_audio_encoder = True
|
||||
|
||||
_audio_sampling = {"audio_temperature": 0.4, "audio_repetition_penalty": 1.2, "audio_repetition_window": 64}
|
||||
_video_max_frames = 384
|
||||
|
||||
# the audio side is the LFM3-Audio model under the HF-native names
|
||||
_audio_renames = (
|
||||
("model.audio_encoder.lfm.", "encoder.lfm.lfm."),
|
||||
("model.audio_encoder.", "encoder."),
|
||||
("model.audio_projector.layer_norm.", "mlp.model.0."),
|
||||
("model.audio_projector.linear_1.", "mlp.model.1."),
|
||||
("model.audio_projector.linear_2.", "mlp.model.3."),
|
||||
("model.depth_decoder.model.", "depthformer.model."),
|
||||
("model.depth_decoder.", ""),
|
||||
("model.codebook_embedding.", "codebook_embedding."),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
assert self.hparams_vision is not None
|
||||
# dynamic resolution, image_size is only set for MmprojModel compatibility
|
||||
self.hparams_vision["image_size"] = 512
|
||||
|
||||
def get_audio_config(self) -> dict[str, Any] | None:
|
||||
enc = self.global_config["audio_encoder_config"]
|
||||
cfg = dict(enc["text_config"])
|
||||
cfg["num_mel_bins"] = enc["num_mel_bins"]
|
||||
cfg["sliding_window"] = enc["sliding_window"]
|
||||
return cfg
|
||||
|
||||
def get_generation_config(self) -> dict[str, Any]:
|
||||
c = self.global_config
|
||||
return {
|
||||
"codebooks": c["num_codebooks"], "audio_vocab_size": c["codebook_size"], "maskgit_schedule": c["maskgit_schedule"],
|
||||
"interleaved_n_text": c["interleaved_n_text"], "interleaved_n_audio": c["interleaved_n_audio"],
|
||||
}
|
||||
|
||||
def get_depth_config(self) -> dict[str, Any]:
|
||||
return self.global_config["depth_config"]
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self._set_lfm3vl_gguf_parameters()
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.startswith(("model.vision_tower.", "model.multi_modal_projector.")):
|
||||
yield from self._lfm3vl_tensors(data_torch, name, bid)
|
||||
return
|
||||
for old, new in self._audio_renames:
|
||||
if name.startswith(old):
|
||||
yield from super().modify_tensors(data_torch, new + name.removeprefix(old), bid)
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
@@ -272,3 +272,18 @@ class MiniCPMV4_7VisionModel(MiniCPMV4_6VisionModel):
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.V_TOK_EMBD_SEP, suffix=""), data_torch[ids]
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
# MiniCPM-OCR 1.0: the 4.6 stack with model_type minicpm_ocr.
|
||||
|
||||
@ModelBase.register("MiniCPMOcrForConditionalGeneration")
|
||||
@ModelBase.example("openbmb/MiniCPM-OCR-1.0")
|
||||
class MiniCPMOCR1_0TextModel(MiniCPMV4_6TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
no_mtp = True
|
||||
|
||||
|
||||
@ModelBase.register("MiniCPMOcrForConditionalGeneration")
|
||||
@ModelBase.example("openbmb/MiniCPM-OCR-1.0")
|
||||
class MiniCPMOCR1_0VisionModel(MiniCPMV4_6VisionModel):
|
||||
projector_type = gguf.VisionProjectorType.MINICPM_OCR_1_0
|
||||
|
||||
@@ -39,6 +39,11 @@ class Mistral3Model(TextModel):
|
||||
self.gguf_writer.add_add_bos_token(False)
|
||||
self.gguf_writer.add_add_eos_token(False)
|
||||
|
||||
def get_vocab_base_pre(self, tokenizer) -> str:
|
||||
if self.origin_hf_arch == "Mistral3Model":
|
||||
return "nemotron-3.5-embed"
|
||||
return super().get_vocab_base_pre(tokenizer)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self._try_set_pooling_type()
|
||||
|
||||
+20
-4
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, Callable, Iterable, Sequence, TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
@@ -367,9 +368,21 @@ class NemotronModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.NEMOTRON
|
||||
|
||||
def set_vocab(self):
|
||||
self._set_vocab_sentencepiece()
|
||||
self.gguf_writer.add_pad_token_id(0)
|
||||
self.gguf_writer.add_unk_token_id(1)
|
||||
if (self.dir_model / "tokenizer.model").exists():
|
||||
self._set_vocab_sentencepiece()
|
||||
self.gguf_writer.add_pad_token_id(0)
|
||||
self.gguf_writer.add_unk_token_id(1)
|
||||
else:
|
||||
# SPM-style BPE (Metaspace + byte fallback): mark <0xXX> tokens as BYTE so they decode to raw bytes
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
toktypes = [gguf.TokenType.BYTE if re.fullmatch(r"<0x[0-9A-F]{2}>", tok) else typ for tok, typ in zip(tokens, toktypes)]
|
||||
self.gguf_writer.add_tokenizer_model("gpt2")
|
||||
self.gguf_writer.add_tokenizer_pre(tokpre)
|
||||
self.gguf_writer.add_token_list(tokens)
|
||||
self.gguf_writer.add_token_types(toktypes)
|
||||
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
@@ -383,7 +396,10 @@ class NemotronModel(TextModel):
|
||||
rot_pct = self.rope_parameters["partial_rotary_factor"]
|
||||
n_embd = self.find_hparam(["hidden_size", "n_embd"])
|
||||
n_head = self.find_hparam(["num_attention_heads", "n_head"])
|
||||
self.gguf_writer.add_rope_dimension_count(int(rot_pct * n_embd) // n_head)
|
||||
# Use explicit head_dim when available; fallback keeps standard Nemotron working.
|
||||
# Without this, models where n_head * head_dim != n_embd (e.g. 48 heads, head_dim=128) produce the wrong rope dimension count.
|
||||
head_dim = self.hparams.get("head_dim") or n_embd // n_head
|
||||
self.gguf_writer.add_rope_dimension_count(int(rot_pct * head_dim))
|
||||
|
||||
# * RopeScaling for Nemotron
|
||||
factor = self.hparams.get("factor") or self.rope_parameters.get("factor")
|
||||
|
||||
@@ -690,6 +690,52 @@ class Qwen3_5TextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
self._try_set_pooling_type()
|
||||
|
||||
|
||||
@ModelBase.register("MiniCPMOCRForSequenceClassification")
|
||||
class MiniCPMOCRSeqClsModel(Qwen3_5TextModel):
|
||||
# OCR consistency judge: Qwen3.5 VL trunk with a value head read at the last token
|
||||
# the class scores are [inconsistent, consistent], the softmax temperature is 1
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
no_mtp = True # the checkpoint has no MTP head
|
||||
|
||||
CLASS_LABELS = ["inconsistent", "consistent"]
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
self.gguf_writer.add_chat_template([{"name": "systemone", "template": self._systemone_template()}])
|
||||
|
||||
def _systemone_template(self) -> str:
|
||||
# the prompt of the training data, the two images are the original page and the one
|
||||
# rendered from the OCR text; the task is fixed, there is nothing to fill in
|
||||
# the trailing newlines are in an expression, so the template does not drop them
|
||||
return (
|
||||
"<|im_start|>user\n{{ images[0] }}\nOCR文本:\n{{ state }}\n{{ images[1] }}\n"
|
||||
"第一张图是原图,第二张图是上述 OCR 文本渲染得到的图片。\n"
|
||||
"请判断该 OCR 文本及其对应渲染图是否与原图内容一致。\n"
|
||||
"这是一个二分类任务:一致标为 1,不一致标为 0。"
|
||||
"{{ '<|im_end|>\\n<|im_start|>assistant\\n<think>\\n\\n</think>\\n\\n' }}"
|
||||
)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
# the config.json of the checkpoint keeps the one of the base model, it has no num_labels / id2label
|
||||
if "score.weight" not in self.model_tensors:
|
||||
raise ValueError("the checkpoint has no score.weight classification head")
|
||||
|
||||
self.gguf_writer.add_pooling_type(gguf.PoolingType.LAST)
|
||||
self.gguf_writer.add_embedding_length_out(len(self.CLASS_LABELS))
|
||||
self.gguf_writer.add_classifier_output_labels(self.CLASS_LABELS)
|
||||
self.gguf_writer.add_decision_type(gguf.DecisionType.MINICPM_OCR_JEV)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name == "score.weight":
|
||||
if data_torch.shape[0] != len(self.CLASS_LABELS):
|
||||
raise ValueError(f"score.weight must have {len(self.CLASS_LABELS)} rows, got {data_torch.shape[0]}")
|
||||
yield (gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.CLS_OUT] + ".weight", data_torch)
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
def _is_openjev_checkpoint(dir_model: Path) -> bool:
|
||||
return (dir_model / "helper" / "shim.py").is_file() and (dir_model / "config.json").is_file()
|
||||
|
||||
|
||||
@@ -144,6 +144,16 @@ class Qwen3VLVisionModel(MmprojModel):
|
||||
yield from MmprojModel.modify_tensors(self, data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("MiniCPMOCRForSequenceClassification")
|
||||
class MiniCPMOCRSeqClsVisionModel(Qwen3VLVisionModel):
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
# trained with the image budget of its processor, wider than the mtmd default of Qwen-VL
|
||||
size = self.preprocessor_config["size"]
|
||||
self.gguf_writer.add_vision_min_pixels(int(size["shortest_edge"]))
|
||||
self.gguf_writer.add_vision_max_pixels(int(size["longest_edge"]))
|
||||
|
||||
|
||||
@ModelBase.register("Qwen3OmniMoeForConditionalGeneration")
|
||||
@ModelBase.example("Qwen/Qwen3-Omni-30B-A3B-Instruct")
|
||||
class Qwen3OmniMmprojModel(Qwen3VLVisionModel, Qwen25AudioModel):
|
||||
|
||||
+54
-15
@@ -41,6 +41,8 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
self._ple_shards: dict[int, str] = {}
|
||||
self._ple_row_dim: int | None = None
|
||||
self._mtp_fc: dict[str, Tensor] = {}
|
||||
# halves of the merged weights, keyed by (bid, merged tensor), see _merge_shared_input
|
||||
self._merge_parts: dict[tuple[int, gguf.MODEL_TENSOR], dict[int, Tensor]] = {}
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item):
|
||||
@@ -131,44 +133,79 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
# int64 hash constants must stay exact; 1-D tensors force F32, so use KV
|
||||
if name.endswith("ple_embedding.layer_multipliers"):
|
||||
self._ple_multipliers = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
return
|
||||
if name.endswith("ple_embedding.ngram_heads_offsets"):
|
||||
self._ple_head_offsets = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
return
|
||||
if name.endswith("ple_embedding.ngram_heads_vocab_sizes"):
|
||||
self._ple_head_vocab_sizes = [int(x) for x in data_torch.tolist()]
|
||||
return []
|
||||
return
|
||||
|
||||
if ".ngram_embedding.shard_" in name:
|
||||
return self._place_ple_shard(data_torch, name)
|
||||
yield from self._place_ple_shard(data_torch, name)
|
||||
return
|
||||
|
||||
# eh_proj([e ; h_s]) = fc_embedding(e) + fc_hidden(h_s) for every hc stream s
|
||||
if name.endswith((".nextn_fc_embedding.weight", ".nextn_fc_hidden.weight")):
|
||||
self._mtp_fc[name.rsplit(".", 2)[1]] = data_torch
|
||||
if len(self._mtp_fc) < 2:
|
||||
return []
|
||||
return
|
||||
eh = torch.cat([self._mtp_fc.pop("nextn_fc_embedding"), self._mtp_fc.pop("nextn_fc_hidden")], dim=1)
|
||||
return [(self.format_tensor_name(gguf.MODEL_TENSOR.NEXTN_EH_PROJ, bid, ".weight"), eh)]
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.NEXTN_EH_PROJ, bid, ".weight"), eh)
|
||||
return
|
||||
|
||||
# one projection feeds indexer q and k; split it, as minimax-m3 does
|
||||
if ".indexer.index_qk_proj.weight" in name:
|
||||
n_q = self.hparams["indexer_n_heads"] * self.hparams["indexer_head_dim"]
|
||||
q = data_torch[:n_q]
|
||||
k = data_torch[n_q:]
|
||||
return [
|
||||
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), q),
|
||||
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), k),
|
||||
]
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), data_torch[:n_q])
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), data_torch[n_q:])
|
||||
return
|
||||
|
||||
# Gemma zero-centred gammas the inherited norm.weight rule misses
|
||||
if name.endswith((".ple.norm_key.weight", ".ple.norm_query.weight", ".ple.norm_conv.weight",
|
||||
".indexer.q_layernorm.weight", ".indexer.k_layernorm.weight")):
|
||||
return [(self.map_tensor_name(name), data_torch + 1)]
|
||||
yield (self.map_tensor_name(name), data_torch + 1)
|
||||
return
|
||||
|
||||
if name.endswith(".ple.conv1d.weight"):
|
||||
return [(self.map_tensor_name(name), data_torch.squeeze())]
|
||||
yield (self.map_tensor_name(name), data_torch.squeeze())
|
||||
return
|
||||
|
||||
return super().modify_tensors(data_torch, name, bid)
|
||||
for new_name, data in super().modify_tensors(data_torch, name, bid):
|
||||
yield from self._merge_shared_input(new_name, data, bid)
|
||||
|
||||
# matmuls that read the same input, written as one weight [a | b] so the runtime does one matmul:
|
||||
# (merged tensor, (a, b), linear-attention layers only)
|
||||
_SHARED_INPUT_MERGES = (
|
||||
(gguf.MODEL_TENSOR.HC_ATTN_DOWN_INJECT, (gguf.MODEL_TENSOR.HC_ATTN_DOWN, gguf.MODEL_TENSOR.HC_ATTN_INJECT), False),
|
||||
(gguf.MODEL_TENSOR.HC_FFN_DOWN_INJECT, (gguf.MODEL_TENSOR.HC_FFN_DOWN, gguf.MODEL_TENSOR.HC_FFN_INJECT), False),
|
||||
(gguf.MODEL_TENSOR.ATTN_QKV_GATE, (gguf.MODEL_TENSOR.ATTN_QKV, gguf.MODEL_TENSOR.ATTN_GATE), True),
|
||||
(gguf.MODEL_TENSOR.SSM_BETA_ALPHA_CAT, (gguf.MODEL_TENSOR.SSM_BETA, gguf.MODEL_TENSOR.SSM_ALPHA), True),
|
||||
)
|
||||
|
||||
# caches one half of a merged weight until the other half arrives, then yields [a | b]
|
||||
def _merge_shared_input(self, new_name: str, data: Tensor, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
# a pre-quantized checkpoint carries per-tensor scales, which one merged weight cannot hold
|
||||
if bid is None or self.hparams.get("quantization_config"):
|
||||
yield (new_name, data)
|
||||
return
|
||||
|
||||
layer_types = self.hparams.get("layer_types") or []
|
||||
is_linear = bid < len(layer_types) and layer_types[bid] == "linear_attention"
|
||||
|
||||
match = next(((merged, parts.index(part)) for merged, parts, linear_only in self._SHARED_INPUT_MERGES
|
||||
for part in parts
|
||||
if (is_linear or not linear_only) and self.match_model_tensor_name(new_name, part, bid)), None)
|
||||
if match is None:
|
||||
yield (new_name, data)
|
||||
return
|
||||
|
||||
merged, idx = match
|
||||
halves = self._merge_parts.setdefault((bid, merged), {})
|
||||
halves[idx] = data
|
||||
if len(halves) == 2:
|
||||
del self._merge_parts[(bid, merged)]
|
||||
yield (self.format_tensor_name(merged, bid), torch.cat([halves[0], halves[1]], dim=0))
|
||||
|
||||
# the shards concatenate into a tensor of well over 100 GB
|
||||
# use LazyChunkedTensor here, a single shard resident at a time
|
||||
@@ -214,6 +251,8 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
|
||||
super().prepare_tensors()
|
||||
if self._mtp_fc:
|
||||
raise ValueError(f"MTP projection missing its other half: {sorted(self._mtp_fc)}")
|
||||
if self._merge_parts:
|
||||
raise ValueError(f"merged weights missing a half: {sorted((bid, t.name) for bid, t in self._merge_parts)}")
|
||||
n_parts = self.hparams.get("split_ngram_parts", 0)
|
||||
if self._ple_shards and len(self._ple_shards) != n_parts:
|
||||
raise ValueError(
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
from .base import ModelBase, MmprojModel, gguf, logger
|
||||
from .qwen import Qwen3Model
|
||||
|
||||
|
||||
def _is_soprano_checkpoint(dir_model: Path) -> bool:
|
||||
if not (dir_model / "decoder.pth").is_file() or not (dir_model / "config.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
if config.get("model_type") != "qwen3" or config.get("bos_token_id") != 3 or config.get("eos_token_id") != 3:
|
||||
return False
|
||||
if not (dir_model / "tokenizer.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "tokenizer.json", encoding="utf-8") as f:
|
||||
added = {t["id"]: t["content"] for t in json.load(f).get("added_tokens", [])}
|
||||
return [added.get(i) for i in range(4)] == ["[UNK]", "[TEXT]", "[START]", "[STOP]"]
|
||||
|
||||
|
||||
@ModelBase.register_hparams_loader(_is_soprano_checkpoint)
|
||||
def _load_soprano_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
logger.info("gguf: detected Soprano checkpoint")
|
||||
hparams = ModelBase.load_hparams(dir_model, False, guess=False)
|
||||
# the text model stays Qwen3, the mmproj holds the decoder
|
||||
hparams["text_config"] = {"architectures": ["SopranoTextModel"]}
|
||||
hparams["architectures"] = ["SopranoModel"]
|
||||
return hparams
|
||||
|
||||
|
||||
@ModelBase.register("SopranoTextModel")
|
||||
class SopranoTextModel(Qwen3Model):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN3
|
||||
|
||||
def get_vocab_base_pre(self, tokenizer) -> str:
|
||||
return "soprano"
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
self.gguf_writer.add_unk_token_id(0)
|
||||
|
||||
|
||||
@ModelBase.register("SopranoModel")
|
||||
@ModelBase.example("ekwek/Soprano-80M", "ekwek/Soprano-1.1-80M")
|
||||
class SopranoModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = False
|
||||
|
||||
def _load_decoder(self) -> dict[str, torch.Tensor]:
|
||||
state = torch.load(self.dir_model / "decoder.pth", map_location="cpu", mmap=True, weights_only=True)
|
||||
return state.get("state_dict", state)
|
||||
|
||||
def get_audio_config(self):
|
||||
state = self._load_decoder()
|
||||
n_embd, n_inp, _ = state["decoder.embed.weight"].shape
|
||||
n_ff = state["decoder.convnext.0.pwconv1.weight"].shape[0]
|
||||
n_layer = sum(1 for name in state if name.endswith(".pwconv1.weight"))
|
||||
if n_inp != self.n_embd_text:
|
||||
raise ValueError(f"Soprano decoder input dim {n_inp} does not match the backbone dim {self.n_embd_text}")
|
||||
return {"num_hidden_layers": n_layer, "hidden_size": n_embd, "intermediate_size": n_ff}
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
self.gguf_writer.add_file_type(self.ftype)
|
||||
self.gguf_writer.add_clip_has_gen_audio_encoder(True)
|
||||
self.gguf_writer.add_clip_gen_audio_projector_type(gguf.VisionProjectorType.SOPRANO)
|
||||
self.gguf_writer.add_gen_audio_projection_dim(self.n_embd_text)
|
||||
self.gguf_writer.add_gen_audio_embedding_length(self.hparams["hidden_size"])
|
||||
self.gguf_writer.add_gen_audio_feed_forward_length(self.hparams["intermediate_size"])
|
||||
self.gguf_writer.add_gen_audio_block_count(self.hparams["num_hidden_layers"])
|
||||
self.gguf_writer.add_gen_audio_head_count(1)
|
||||
self.gguf_writer.add_gen_audio_attention_layernorm_eps(1e-6)
|
||||
|
||||
def get_tensors(self):
|
||||
yield from self._load_decoder().items()
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if "dwconv.weight" in new_name or (name == "decoder.embed.weight" and n_dims == 3):
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
def modify_tensors(self, data_torch, name, bid):
|
||||
if name == "head.istft.window":
|
||||
# the ISTFT in mtmd builds its own window
|
||||
if not torch.equal(data_torch.float(), torch.hann_window(data_torch.shape[0])):
|
||||
raise ValueError("Soprano requires a periodic Hann window")
|
||||
return
|
||||
if name == "decoder.embed.weight" and data_torch.shape[-1] == 1:
|
||||
data_torch = data_torch.squeeze(-1)
|
||||
|
||||
T = gguf.MODEL_TENSOR
|
||||
if name.startswith("decoder.convnext."):
|
||||
_, _, blk, key, *suffix = name.split(".")
|
||||
block_map = {
|
||||
"dwconv": T.A_GEN_WAV_UP_DWCONV,
|
||||
"norm": T.A_GEN_WAV_UP_NORM,
|
||||
"pwconv1": T.A_GEN_WAV_UP_PW1,
|
||||
"pwconv2": T.A_GEN_WAV_UP_PW2,
|
||||
"gamma": T.A_GEN_WAV_UP_GAMMA,
|
||||
}
|
||||
yield self.format_tensor_name(block_map[key], int(blk), suffix="".join("." + s for s in suffix)), data_torch
|
||||
return
|
||||
|
||||
tensor_map = {
|
||||
"decoder.embed": T.A_GEN_WAV_INPUT,
|
||||
"decoder.norm": T.A_GEN_WAV_NORM,
|
||||
"decoder.final_layer_norm": T.A_GEN_WAV_OUTPUT_NORM,
|
||||
"head.out": T.A_GEN_WAV_OUTPUT,
|
||||
}
|
||||
key, suffix = name.rsplit(".", 1)
|
||||
yield self.format_tensor_name(tensor_map[key], suffix="." + suffix), data_torch
|
||||
@@ -267,11 +267,15 @@ def main() -> None:
|
||||
sys.exit(1)
|
||||
|
||||
if args.dspark:
|
||||
if is_mistral_format or model_architecture != "DeepseekV4ForCausalLM":
|
||||
logger.error("--dspark is only supported for DeepseekV4ForCausalLM")
|
||||
if is_mistral_format or model_architecture not in ("DeepseekV4ForCausalLM", "DeepseekV41ForCausalLM"):
|
||||
logger.error("--dspark is only supported for DeepseekV4ForCausalLM and DeepseekV41ForCausalLM")
|
||||
sys.exit(1)
|
||||
from conversion.deepseek import DeepseekV4DSparkModel
|
||||
model_class = DeepseekV4DSparkModel
|
||||
if model_architecture == "DeepseekV41ForCausalLM":
|
||||
from conversion.deepseek41 import DeepseekV41DSparkModel
|
||||
model_class = DeepseekV41DSparkModel
|
||||
else:
|
||||
from conversion.deepseek import DeepseekV4DSparkModel
|
||||
model_class = DeepseekV4DSparkModel
|
||||
|
||||
if args.mtp or args.no_mtp:
|
||||
if not model_class.supports_mtp_export:
|
||||
|
||||
@@ -178,6 +178,7 @@ pre_computed_hashes = [
|
||||
{"name": "glm4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/zai-org/GLM-4.5-Air", "chkhsh": "9ca2dd618e8afaf09731a7cf6e2105b373ba6a1821559f258b272fe83e6eb902"},
|
||||
{"name": "glm4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/zai-org/GLM-4.7-Flash", "chkhsh": "cdf5f35325780597efd76153d4d1c16778f766173908894c04afc20108536267"},
|
||||
{"name": "minerva-7b", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0", "chkhsh": "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35"},
|
||||
{"name": "domyn-small", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/domyn/Domyn-Small-v1.0", "chkhsh": "6e4d72c6e5260b90606e26a0931fa15e4b42ec6e97719d12919f2b1f93478262"},
|
||||
{"name": "hunyuan", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-A13B-Instruct", "chkhsh": "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664"},
|
||||
{"name": "hunyuan-dense", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-4B-Instruct", "chkhsh": "bba3b3366b646dbdded5dbc42d59598b849371afc42f7beafa914afaa5b70aa6"},
|
||||
{"name": "hy_v4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hy4-preview", "chkhsh": "e6ddf9c6686791c12d698d34c31ab9be1fea9af5a3d9a6909783ab382198ae1c"},
|
||||
|
||||
@@ -72,6 +72,8 @@ However, A6x GPUs in phones are likely not supported due to the outdated driver
|
||||
| MXFP4 | Support |
|
||||
| IQ4_NL | Support |
|
||||
|
||||
`MUL_MAT_ID` accepts an expert id of `-1` and writes zeros to that slot. `ADD_ID` adds no bias for `-1`, including in fused MoE operations.
|
||||
|
||||
## Model Preparation
|
||||
|
||||
Since common quantizations are supported now, it is recommanded to download GGUF models directly from Huggingface.
|
||||
|
||||
+18
-3
@@ -783,7 +783,7 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
|
||||
| GGML_SYCL | ON (mandatory) | Enable build with SYCL code path. |
|
||||
| GGML_SYCL_TARGET | INTEL *(default)* | Set the SYCL target device type. |
|
||||
| GGML_SYCL_DEVICE_ARCH | Optional | Set the SYCL device architecture. Setting the device architecture can improve the performance. See the table [--offload-arch](https://github.com/intel/llvm/blob/sycl/sycl/doc/design/OffloadDesign.md#--offload-arch) for a list of valid architectures. |
|
||||
| GGML_SYCL_F16 | OFF *(default)* \|ON *(optional)* | Enable FP16 build with SYCL code path. (1.) |
|
||||
| GGML_SYCL_F16 | OFF *(default)* \|ON *(optional)* | Enable FP16 build with SYCL code path. [1.](#sycl-f16-footnote) |
|
||||
| GGML_SYCL_GRAPH | ON *(default)* \|OFF *(Optional)* | Enable build with [SYCL Graph extension](https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_graph.asciidoc). |
|
||||
| GGML_SYCL_DNN | ON *(default)* \|OFF *(Optional)* | Enable build with oneDNN. |
|
||||
| GGML_SYCL_HOST_MEM_FALLBACK | ON *(default)* \|OFF *(Optional)* | Allow host memory fallback when device memory is full during quantized weight reorder. Enables inference to continue at reduced speed (reading over PCIe) instead of failing. Requires Linux kernel 6.8+. |
|
||||
@@ -791,7 +791,7 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
|
||||
| CMAKE_C_COMPILER | `icx` *(Linux)*, `icx/cl` *(Windows)* | Set `icx` compiler for SYCL code path. |
|
||||
| CMAKE_CXX_COMPILER | `icpx` *(Linux)*, `icx` *(Windows)* | Set `icpx/icx` compiler for SYCL code path. |
|
||||
|
||||
1. FP32 or FP16 have different performance impact to LLM. Recommended to test them for better prompt processing performance on your models. You need to rebuild the code after change `GGML_SYCL_F16=OFF/ON`.
|
||||
1. <a id="sycl-f16-footnote"></a>FP32 or FP16 have different performance impact to LLM. Recommended to test them for better prompt processing performance on your models. You need to rebuild the code after change `GGML_SYCL_F16=OFF/ON`.
|
||||
|
||||
### Runtime
|
||||
|
||||
@@ -802,7 +802,8 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
|
||||
| GGML_SYCL_DEV2DEV_MEMCPY | 0 (default), 1, 2 | Choose the method of dev2dev memory copy.<br>Value: <br>* 0: SYCL API (default), only support dGPUs.<br>* 1: L0 API -- Better performance, only support dGPUs, found to lead to abnormal crash in some case. <br>* 2: Host Forward -- Most stable method for all cases (including iGPU + dGPU*N), but with lower performance (-2% to -5%).<br>SYCL & L0 API are easy to be impacted by Intel GPU driver issue. When you meet the garbled output or crash issues in multiple GPUs case, try with this debug flag to work around or check the issue.|
|
||||
| GGML_SYCL_ENABLE_FLASH_ATTN | 1 (default) or 0| Enable Flash-Attention. It can reduce memory usage. The performance impact depends on the LLM.|
|
||||
| GGML_SYCL_ENABLE_OPT | 0 or 1 (default)| Enable optimize features for Intel GPUs. (Recommended to 0 for Intel devices older than Gen 10) |
|
||||
| GGML_SYCL_ENABLE_GRAPH | 0 (default) or 1 | Enable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still on development, no better performance. |
|
||||
| GGML_SYCL_ENABLE_GRAPH | 0 (default) or 1 | Enable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still in development, performance depends on model and device. [1.](#sycl-graph-footnote) |
|
||||
| GGML_SYCL_GRAPH_EVICTION_TIMEOUT | 10 (default) or positive integer | Seconds a cached SYCL graph can go unused before it is evicted. Checked on a sweep that runs every half of this timeout. |
|
||||
| GGML_SYCL_ENABLE_HOST_PINNED_MEM | 0 or 1 (default) | Enable host pinned memory to speed up copy data from host to device. When disable it, host memory will common malloc() on CPU. Disable it when use `--load-model mlock`.|
|
||||
| GGML_SYCL_HOST_PINNED_MEM_2G | 0 (default) or 1 | Limit the max memory allocation to be no more than 2GB when enable host pinned memory. USM allocations above 2 GiB take the relaxed/large-allocation path, which serializes H2D copies with compute and prevents copy/compute overlap. It will impact the startup time. Need more test. Depend on `GGML_SYCL_ENABLE_HOST_PINNED_MEM=1`.|
|
||||
| GGML_SYCL_UPLOAD_STAGING_SLOTS | 4 (default) or non-negative integer | Number of 8 MiB pinned host slots used to stage tensor uploads (model loading), so the host copy of one slot overlaps the transfer of the previous one. Set to 0 to use the old path: a malloc'd bounce buffer and a blocking copy per tensor. |
|
||||
@@ -819,6 +820,9 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
|
||||
| GGML_SYCL_MKL_FA_DIAG | 0 (default) or 1 | Enable output fingerprinting for MKL flash attention. Dumps the first 64 float output values for the first 6 FA calls with n_kv ≥ 1024, labeled with kernel type (MKL/TILE/VEC) for cross-kernel comparison. |
|
||||
| GGML_SYCL_ENABLE_FUSION | 0 or 1 (default) | Enable fused-kernel dispatch in graph compute. Unsupported types and layouts fall back to the standalone op kernels. See `ggml_sycl_can_fuse()`. |
|
||||
| GGML_SYCL_ENABLE_ESIMD | 0 or 1 (default)| Enable ESIMD kernels when available. |
|
||||
| GGML_SYCL_ENABLE_XMX_ESIMD | 0 or 1 (default) | Use the XMX (DPAS) multi-column mat-vec kernels for reordered Q2_K to Q6_K and Q8_0 weights on Xe2 GPUs. Speeds up speculative decoding and short prompt batches. |
|
||||
| GGML_SYCL_XMX_MAX_COLS | non-negative integer, 80 (default) | Most activation columns (batch size) the `GGML_SYCL_ENABLE_XMX_ESIMD` kernels take; wider batches use the other mat-mul kernels. A larger value keeps XMX for bigger batches (as repeated launches), `0` never uses it. |
|
||||
| GGML_SYCL_XMX_GLU_MAX_COLS | 0 to 16, 16 (default) | Most columns the fused gate and up XMX kernel takes; wider batches run gate and up as separate mat-vecs. 16 is the most the kernel is built for, larger values are clamped to 16 and `0` turns the fused kernel off. |
|
||||
| GGML_SYCL_XMX_GATHER_TYPES | decimal bitmask, all bits set (default) | Weight formats that may use the XMX dequant-GEMM paths, which dequantize weights straight into the XMX tiles. This speeds up prompt processing of MoE models on GPUs with XMX units (Arc A- and B-series, Arc Pro, Data Center GPU Max), for example pp512 of Qwen3-30B-A3B UD-IQ3_XXS by about 50% on an Arc Pro B60. Bits:<br>* 1: IQ4_NL, 2: IQ3_S, 4: IQ4_XS, 8: IQ3_XXS, 16: IQ2_XXS, 32: IQ2_XS, 64: IQ2_S, 128: IQ1_S, 256: IQ1_M<br>* 512: Q8_0, 1024: Q4_K, 2048: Q5_K, 4096: Q6_K (MoE `MUL_MAT_ID` only)<br>Add values to combine them, for example `3` for IQ4_NL and IQ3_S; `0` disables the paths. A set bit does not force the path: batches of more than 64 tokens per expert or row lengths that are not a multiple of 256 (32 for IQ4_NL and Q8_0) use the library GEMM. |
|
||||
| GGML_SYCL_XMX_GATHER_SHAPES | decimal bitmask, 255 (default) | XMX `joint_matrix` combinations the paths of `GGML_SYCL_XMX_GATHER_TYPES` may use; the operand type comes from `GGML_SYCL_DYNAMIC_PRECISION` and the best supported combination is picked automatically (logged as `fg_pick_combo`). Bits:<br>* Xe2, Xe3, Xe-HPC: 1: f16 8x16x16, 2: f16 16x16x16, 4: f16 32x64x16, 8: f16 32x64x32, 32: tf32 8x16x8, 64: bf16 8x16x16<br>* Xe-HPG (Arc A770, ARL-H): 16: f16 8x8x16, 128: bf16 8x8x16<br>Clear a bit to exclude a combination, or set a single bit to force one for testing. |
|
||||
| GGML_SYCL_DYNAMIC_PRECISION | `F16` (default with `GGML_SYCL_F16=ON`), `BF16`, `TF32` or `F32` (default otherwise) | Operand type of the XMX dequant-GEMM paths (`GGML_SYCL_XMX_GATHER_TYPES`); accumulation is always f32. `F16` is the fastest, but activations above 65504 overflow. `BF16` keeps the f32 range at a 7-bit mantissa, `TF32` keeps the range and the f16 mantissa but is about 30% slower and needs Xe2, Xe3 or Xe-HPC, and `F32` turns the XMX paths off. Ops that request a higher src1 precision ([TAG_GGML_PREC]) get it regardless of this setting. |
|
||||
@@ -831,6 +835,17 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
|
||||
| UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS | 0 (default) or 1 | Allow SYCL/Unified Runtime Level Zero device allocations larger than 4 GiB. llama.cpp's direct Level Zero allocation path requests the relaxed maximum-size limit itself when GGML_SYCL_ENABLE_LEVEL_ZERO=1. |
|
||||
| GGML_SYCL_USM_SYSTEM | 0 (default) or 1 | Enable experimental support for [USM system allocations](https://github.khronos.org/SYCL_Reference/iface/usm_basic_concept.html#system-allocations) for large GPU buffers. This requires enough host memory for model weights and caches, an Intel Xe2+ GPU such as BMG or newer and supported on Linux only, with CONFIG_DRM_XE_GPUSVM enabled. |
|
||||
|
||||
1. <a id="sycl-graph-footnote"></a>SYCL Graph support (`GGML_SYCL_ENABLE_GRAPH=1`) is limited to single-GPU workloads and may also depend on the model architecture and kernels in use.
|
||||
Performance depends on how often a recorded graph can be reused, which will vary by workload.
|
||||
For this reason, it is recommended to measure performance both with and without graphs enabled on your workloads.
|
||||
|
||||
To check whether graphs are supported for your workload, enable INFO logging (`-lv 4`) and look for `enabling SYCL graphs` or `disabling SYCL graphs`.
|
||||
Support may differ between prefill and decode, so check the status for the phases that matter to you.
|
||||
DEBUG logging (`-lv 5`) reports why graphs are disabled.
|
||||
|
||||
Then benchmark with your preferred tool, such as `llama-bench`.
|
||||
For graph activity details (including reuse), set `GGML_SYCL_DEBUG=1` and look for `[SYCL-GRAPH]` messages.
|
||||
|
||||
## Compile-time Flags
|
||||
|
||||
Pass these via `CXXFLAGS` or add a one-off `#define` to enable a flag on the spot.
|
||||
|
||||
@@ -324,6 +324,9 @@ on 4 physical NPUs, or `--devices 'HTP0[0-1:0],HTP1[0-1:1]'` on 2 physical NPUs
|
||||
- `GGML_HEXAGON_NHVX=0`
|
||||
Controls the number of HVX hardware threads to use. The default is all (actual number varies depending on the hardware version).
|
||||
|
||||
- `GGML_HEXAGON_VTCM_SIZE=0`
|
||||
Overrides the VTCM size used by each session. Accepts `K`, `M` and `G` suffixes; a plain number is in MB. The default (0) uses the size the hardware reports. Values above the hardware size will fail to start the session.
|
||||
|
||||
- `GGML_HEXAGON_HOSTBUF=1` (default: 0, disabled)
|
||||
Enables allocating host buffers for debugging. By default, host buffers are disabled.
|
||||
|
||||
|
||||
+14
-14
@@ -14,7 +14,7 @@ Legend:
|
||||
|
||||
| Operation | BLAS | CANN | CPU | CUDA | ET | HTP | MTL | OpenCL | SYCL | Vulkan | WebGPU | ZenDNN | zDNN |
|
||||
|-----------|------|------|------|------|------|------|------|------|------|------|------|------|------|
|
||||
| ABS | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| ABS | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| ACC | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| ADD | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| ADD1 | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
@@ -22,12 +22,12 @@ Legend:
|
||||
| ARANGE | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| ARGMAX | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| ARGSORT | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| CEIL | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| CEIL | ❌ | ❌ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| CLAMP | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| COL2IM_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| CONCAT | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
|
||||
| CONT | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
|
||||
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
|
||||
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
|
||||
| CONV_2D | ❌ | ❌ | 🟡 | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ❌ | ❌ |
|
||||
| CONV_2D_DW | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| CONV_3D | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
@@ -45,7 +45,7 @@ Legend:
|
||||
| DSV4_HC_COMB | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| DSV4_HC_POST | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| DSV4_HC_PRE | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| DUP | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| DUP | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| ELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| EXP | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| EXPM1 | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
@@ -55,10 +55,10 @@ Legend:
|
||||
| GATED_DELTA_NET | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | 🟡 | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GATED_LINEAR_ATTN | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| GEGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GEGLU_ERF | ❌ | ✅ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GEGLU_ERF | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GEGLU_QUICK | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GELU_ERF | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GELU_ERF | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GELU_QUICK | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| GET_ROWS | ❌ | 🟡 | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ❌ | ❌ |
|
||||
| GET_ROWS_BACK | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ |
|
||||
@@ -66,7 +66,7 @@ Legend:
|
||||
| HARDSIGMOID | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| HARDSWISH | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| IM2COL | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| IM2COL_3D | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| IM2COL_3D | ❌ | ❌ | ✅ | ✅ | ❌ | 🟡 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| L2_NORM | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | 🟡 | ❌ | ❌ |
|
||||
| LEAKY_RELU | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 | 🟡 | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| LIGHTNING_INDEXER | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
@@ -74,7 +74,7 @@ Legend:
|
||||
| MEAN | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | 🟡 | ✅ | ❌ | ❌ | ❌ |
|
||||
| MUL | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| MUL_MAT | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
|
||||
| MUL_MAT_HADAMARD | ❌ | ❌ | ✅ | ❌ | ✅ | 🟡 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 |
|
||||
| MUL_MAT_HADAMARD | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 |
|
||||
| MUL_MAT_ID | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | 🟡 | ❌ |
|
||||
| MUL_MAT_ID_W4A4 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| MUL_MAT_ID_W4A8 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
@@ -87,9 +87,9 @@ Legend:
|
||||
| OUT_PROD | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ |
|
||||
| PAD | ❌ | 🟡 | ✅ | 🟡 | ❌ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ |
|
||||
| PAD_REFLECT_1D | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| POOL_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| POOL_2D | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| REGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| POOL_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| POOL_2D | ❌ | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| REGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| RELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| REPEAT | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
|
||||
| REPEAT_BACK | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
@@ -101,7 +101,7 @@ Legend:
|
||||
| ROUND | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| RWKV_WKV6 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| RWKV_WKV7 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| SCALE | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SCALE | ❌ | 🟡 | ✅ | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SET | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ |
|
||||
| SET_ROWS | ❌ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
|
||||
| SGN | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
@@ -117,12 +117,12 @@ Legend:
|
||||
| SQRT | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SSM_CONV | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SSM_SCAN | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | 🟡 | 🟡 | ✅ | ❌ | ❌ |
|
||||
| STEP | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| STEP | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SUB | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SUM | ❌ | 🟡 | ✅ | 🟡 | ❌ | 🟡 | 🟡 | ❌ | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
|
||||
| SUM_ROWS | ❌ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ |
|
||||
| SWIGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| SWIGLU_CLAMP | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| SWIGLU_CLAMP | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| SWIGLU_OAI | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| TANH | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| TIMESTEP_EMBEDDING | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
+6792
-5051
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -100,8 +100,9 @@ llama-server -m Qwen3-4B.gguf -md Qwen3-4B-DSpark.gguf \
|
||||
|
||||
`--spec-draft-n-max` is clamped to the draft model's trained block size.
|
||||
|
||||
`--spec-draft-conf-min P` truncates each drafted block at the first position whose predicted
|
||||
acceptance (from the draft's confidence head, if present) falls below `P` (default 0 = disabled).
|
||||
`--spec-draft-p-min P` truncates each drafted block at the first position whose predicted
|
||||
acceptance (from the draft's confidence head) falls below `P` (default 0 = disabled).
|
||||
Values above 0 require a draft with a confidence head.
|
||||
|
||||
Currently only drafts with a Qwen3 backbone are supported; support for other backbones
|
||||
(e.g. Gemma4) is planned.
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Example client for the llama-server /v1/realtime websocket, with the openai SDK.
|
||||
|
||||
Start a server with an audio model, e.g. LFM3-Audio:
|
||||
|
||||
llama-server -hf LiquidAI/LFM3-Audio-2B-GGUF
|
||||
|
||||
Spoken conversation, the audio of each wav file is one user turn. It is sent in chunks as it would come from a
|
||||
microphone, the server prefills it as it arrives so the response starts right after the last chunk:
|
||||
|
||||
python examples/realtime_client.py --instructions "Respond with interleaved text and audio." question.wav
|
||||
|
||||
Output modalities: left out, the model picks; "text", "audio" or "text,audio" (interleaved) force the response.
|
||||
Without --instructions, a forced mode comes with the system prompt of the LFM3-Audio or LFM3-Omni model card:
|
||||
|
||||
python examples/realtime_client.py --output-modalities text,audio --out answer.wav question.wav
|
||||
|
||||
Tool calls: --tools gives the conversation a demo tool, the client runs its calls and sends the results back:
|
||||
|
||||
python examples/realtime_client.py --tools --text "Set the volume to 30."
|
||||
|
||||
Text turns go in with --text, transcription sessions transcribe each file:
|
||||
|
||||
python examples/realtime_client.py --text "What is the capital of France?"
|
||||
python examples/realtime_client.py --type transcription --language en speech.wav
|
||||
|
||||
Live transcription models (Voxtral Realtime, LFM3-Audio live ASR) transcribe while the audio is sent, a transcription
|
||||
session opens with ?intent=transcription:
|
||||
|
||||
python examples/realtime_client.py --type transcription speech.wav
|
||||
|
||||
Requirements: pip install "openai[realtime]" numpy
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import wave
|
||||
|
||||
import numpy as np
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
|
||||
# system prompts of the LiquidAI/LFM3-Audio-2B and LiquidAI/LFM3-Omni-2B model cards for a forced output mode
|
||||
MODE_INSTRUCTIONS = {
|
||||
"audio": {"text,audio": "Respond with interleaved text and audio."},
|
||||
"omni": {"text": "Respond in text.", "text,audio": "Respond in interleaved text and audio."},
|
||||
}
|
||||
# and with tools: calls, or an answer in interleaved text and speech
|
||||
TOOLS_INSTRUCTIONS = "Respond with function calls, and interleaved text and audio.\n\nList of tools:"
|
||||
|
||||
# the demo tool of --tools
|
||||
DEMO_TOOLS = [{
|
||||
"type": "function",
|
||||
"name": "set_volume",
|
||||
"description": "Set the device speaker volume.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"volume": {"type": "integer", "description": "Volume from 0 to 100."}},
|
||||
"required": ["volume"],
|
||||
},
|
||||
}]
|
||||
|
||||
|
||||
def run_tool(name, arguments):
|
||||
if name == "set_volume":
|
||||
return {"volume": json.loads(arguments)["volume"]}
|
||||
return {"error": f"unknown tool {name}"}
|
||||
|
||||
|
||||
def read_wav(path, rate):
|
||||
# PCM16 wav -> mono int16 at the given rate
|
||||
with wave.open(path, "rb") as f:
|
||||
assert f.getsampwidth() == 2, "the example reads PCM16 wav files"
|
||||
x = np.frombuffer(f.readframes(f.getnframes()), dtype=np.int16).astype(np.float32)
|
||||
x = x.reshape(-1, f.getnchannels()).mean(axis=1)
|
||||
sr = f.getframerate()
|
||||
if sr != rate:
|
||||
n = int(round(len(x) * rate / sr))
|
||||
x = np.interp(np.arange(n) * sr / rate, np.arange(len(x)), x)
|
||||
return np.clip(x, -32768, 32767).astype(np.int16)
|
||||
|
||||
|
||||
def write_wav(path, pcm, rate):
|
||||
with wave.open(path, "wb") as f:
|
||||
f.setnchannels(1)
|
||||
f.setsampwidth(2)
|
||||
f.setframerate(rate)
|
||||
f.writeframes(pcm.tobytes())
|
||||
|
||||
|
||||
async def main(args):
|
||||
# the SDK derives a wss:// URL from base_url, a plain http server needs ws://
|
||||
client = AsyncOpenAI(base_url=args.url, api_key=args.api_key, websocket_base_url=args.url.replace("http", "ws", 1))
|
||||
query = {"intent": "transcription"} if args.type == "transcription" else {}
|
||||
async with client.realtime.connect(model=args.model, extra_query=query) as conn:
|
||||
ev = await conn.recv()
|
||||
if ev.type == "error":
|
||||
print("error:", ev.error.message)
|
||||
return
|
||||
# the audio goes in at the rate of the model, PCM16 mono
|
||||
session_info = ev.model_dump(warnings=False)["session"]
|
||||
audio_fmt = session_info["audio"]
|
||||
card = "omni" if "omni" in str(session_info.get("model", "")).lower() else "audio"
|
||||
in_rate = audio_fmt["input"]["format"]["rate"]
|
||||
out_rate = (audio_fmt.get("output") or {}).get("format", {}).get("rate", 24000)
|
||||
|
||||
session = {"type": args.type}
|
||||
if args.type == "realtime":
|
||||
instructions = args.instructions
|
||||
if instructions is None:
|
||||
instructions = TOOLS_INSTRUCTIONS if args.tools else MODE_INSTRUCTIONS[card].get(args.output_modalities)
|
||||
if args.tools:
|
||||
session["tools"] = DEMO_TOOLS
|
||||
if instructions is not None:
|
||||
session["instructions"] = instructions
|
||||
if args.output_modalities:
|
||||
session["output_modalities"] = args.output_modalities.split(",")
|
||||
elif args.language:
|
||||
session["audio"] = {"input": {"transcription": {"language": args.language}}}
|
||||
await conn.session.update(session=session) # ty: ignore[invalid-argument-type]
|
||||
ev = await conn.recv()
|
||||
if ev.type == "error":
|
||||
print("error:", ev.error.message)
|
||||
return
|
||||
|
||||
turns = [("audio", path) for path in args.wav] + [("text", text) for text in args.text]
|
||||
for i, (kind, src) in enumerate(turns):
|
||||
t_last = 0.0
|
||||
|
||||
# the input is sent while the events are read: live transcription arrives while the audio is sent
|
||||
async def send_turn():
|
||||
nonlocal t_last
|
||||
if kind == "audio":
|
||||
pcm = read_wav(src, in_rate)
|
||||
step = in_rate * args.chunk_ms // 1000
|
||||
for j in range(0, len(pcm), step):
|
||||
chunk = pcm[j:j + step]
|
||||
await conn.input_audio_buffer.append(audio=base64.b64encode(chunk.tobytes()).decode())
|
||||
if args.realtime:
|
||||
await asyncio.sleep(len(chunk) / in_rate)
|
||||
if args.type == "transcription":
|
||||
# "final" ends the input, models that transcribe with a delay then complete the last item
|
||||
await conn.send({"type": "input_audio_buffer.commit", "final": i == len(args.wav) - 1}) # ty: ignore[invalid-argument-type]
|
||||
else:
|
||||
await conn.conversation.item.create(item={
|
||||
"type": "message", "role": "user", "content": [{"type": "input_text", "text": src}]})
|
||||
if args.type == "realtime":
|
||||
# the audio not committed yet ends the user turn
|
||||
await conn.response.create()
|
||||
t_last = time.perf_counter()
|
||||
|
||||
print(f"--- turn {i} ({kind}: {src})")
|
||||
sender = asyncio.create_task(send_turn())
|
||||
t_first = None
|
||||
audio = []
|
||||
async for ev in conn:
|
||||
if ev.type == "response.output_text.delta" or ev.type == "conversation.item.input_audio_transcription.delta":
|
||||
t_first = t_first or time.perf_counter()
|
||||
print(ev.delta, end="", flush=True)
|
||||
elif ev.type == "response.output_audio.delta":
|
||||
t_first = t_first or time.perf_counter()
|
||||
audio.append(np.frombuffer(base64.b64decode(ev.delta), dtype=np.int16))
|
||||
elif ev.type == "response.done":
|
||||
# the calls of the response are run, their results go back and the model answers them
|
||||
calls = [o for o in ev.model_dump()["response"]["output"] if o["type"] == "function_call"]
|
||||
for call in calls:
|
||||
result = json.dumps(run_tool(call["name"], call["arguments"]))
|
||||
print(f"\n[call {call['name']}({call['arguments']}) -> {result}]", flush=True)
|
||||
await conn.conversation.item.create(item={
|
||||
"type": "function_call_output", "call_id": call["call_id"], "output": result})
|
||||
if not calls:
|
||||
break
|
||||
await conn.response.create()
|
||||
elif ev.type == "conversation.item.input_audio_transcription.completed":
|
||||
break
|
||||
elif ev.type == "error":
|
||||
print("\nerror:", ev.error.message)
|
||||
return
|
||||
await sender
|
||||
print()
|
||||
if t_first is not None and t_first > t_last:
|
||||
print(f"first output {1000 * (t_first - t_last):.0f} ms after the end of the input")
|
||||
elif t_first is not None:
|
||||
print(f"output started {1000 * (t_last - t_first):.0f} ms before the end of the input, "
|
||||
f"done {1000 * (time.perf_counter() - t_last):.0f} ms after it")
|
||||
if audio:
|
||||
pcm_out = np.concatenate(audio)
|
||||
path = f"{args.out.removesuffix('.wav')}_{i}.wav" if len(turns) > 1 else args.out
|
||||
write_wav(path, pcm_out, out_rate)
|
||||
print(f"audio: {len(pcm_out) / out_rate:.2f} s -> {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("wav", nargs="*", help="PCM16 wav files, one user turn each")
|
||||
p.add_argument("--text", action="append", default=[], help="a user turn as text, after the audio turns, repeatable")
|
||||
p.add_argument("--type", choices=["realtime", "transcription"], default="realtime", help="session type")
|
||||
p.add_argument("--instructions", help="system prompt of the conversation")
|
||||
p.add_argument("--tools", action="store_true", help="give the conversation a demo tool (set_volume), its calls are run")
|
||||
p.add_argument("--output-modalities", choices=["text", "audio", "text,audio"], help="default: the model picks")
|
||||
p.add_argument("--language", help="transcription language, e.g. en")
|
||||
p.add_argument("--chunk-ms", type=int, default=100, help="audio chunk size")
|
||||
p.add_argument("--realtime", action=argparse.BooleanOptionalAction, default=True, help="send the audio in real time")
|
||||
p.add_argument("--out", default="response.wav", help="where the audio of the responses goes")
|
||||
p.add_argument("--url", default="http://127.0.0.1:9931/v1")
|
||||
p.add_argument("--api-key", default="none")
|
||||
p.add_argument("--model", default="default", help="model name, used by the router")
|
||||
asyncio.run(main(p.parse_args()))
|
||||
+1
-1
@@ -4,7 +4,7 @@ project("ggml" C CXX ASM)
|
||||
|
||||
### GGML Version
|
||||
set(GGML_VERSION_MAJOR 0)
|
||||
set(GGML_VERSION_MINOR 27)
|
||||
set(GGML_VERSION_MINOR 28)
|
||||
set(GGML_VERSION_PATCH 0)
|
||||
set(GGML_VERSION_BASE "${GGML_VERSION_MAJOR}.${GGML_VERSION_MINOR}.${GGML_VERSION_PATCH}")
|
||||
|
||||
|
||||
@@ -926,6 +926,7 @@ extern "C" {
|
||||
enum ggml_type type);
|
||||
|
||||
// dst[i0, i1, i2] = a[i0, i1, i2] + b[i0, ids[i1, i2]]
|
||||
// an id of -1 adds nothing: dst[i0, i1, i2] = a[i0, i1, i2]
|
||||
GGML_API struct ggml_tensor * ggml_add_id(
|
||||
struct ggml_context * ctx,
|
||||
struct ggml_tensor * a,
|
||||
@@ -1477,6 +1478,16 @@ extern "C" {
|
||||
enum ggml_prec prec,
|
||||
int idx);
|
||||
|
||||
// [TAG_GGML_PREC]
|
||||
// get the accumulator / src[idx] precision set by the functions above
|
||||
// return GGML_PREC_UNDEFINED when the op does not carry the requested precision
|
||||
GGML_API enum ggml_prec ggml_prec_get_acc(
|
||||
const struct ggml_tensor * a);
|
||||
|
||||
GGML_API enum ggml_prec ggml_prec_get_src(
|
||||
const struct ggml_tensor * a,
|
||||
int idx);
|
||||
|
||||
// A: k columns, n rows => [ne03, ne02, n, k]
|
||||
// B: k columns, m rows (i.e. we transpose it internally) => [ne03 * x, ne02 * y, m, k]
|
||||
// result is n columns, m rows => [ne03 * x, ne02 * y, m, n]
|
||||
@@ -1498,6 +1509,7 @@ extern "C" {
|
||||
enum ggml_op_hint hint);
|
||||
|
||||
// indirect matrix multiplication
|
||||
// an id of -1 skips the slot and zeroes the matching row of the result
|
||||
//
|
||||
// [TAG_MUL_MAT_ID_DUPLICATE_ID]
|
||||
// note: this op has undefined behaviour if a given `a` is referenced more than once for a given `b`
|
||||
|
||||
@@ -774,13 +774,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
|
||||
return src_ss[0];
|
||||
};
|
||||
|
||||
auto handle_pool_1d = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
|
||||
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
|
||||
return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
|
||||
}
|
||||
return src_ss[0];
|
||||
};
|
||||
|
||||
auto handle_flash_attn_ext = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
|
||||
GGML_ASSERT(tensor->src[3] == nullptr || src_ss[3].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
||||
|
||||
@@ -997,14 +990,12 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
|
||||
case GGML_OP_CONV_3D:
|
||||
case GGML_OP_CONV_2D_DW:
|
||||
case GGML_OP_CONV_TRANSPOSE_2D:
|
||||
case GGML_OP_POOL_1D:
|
||||
case GGML_OP_POOL_2D:
|
||||
case GGML_OP_POOL_2D_BACK:
|
||||
case GGML_OP_UPSCALE: {
|
||||
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
|
||||
} break;
|
||||
case GGML_OP_POOL_1D: {
|
||||
split_state = handle_pool_1d(src_ss);
|
||||
} break;
|
||||
case GGML_OP_PAD: {
|
||||
split_state = handle_pad(src_ss);
|
||||
} break;
|
||||
|
||||
@@ -920,7 +920,7 @@ static void * ggml_backend_cann_buffer_get_base(ggml_backend_buffer_t buffer) {
|
||||
* @param dst Pointer to the destination buffer where transformed data will be
|
||||
* stored.
|
||||
*/
|
||||
static void ggml_backend_cann_transform_q4_0(ggml_tensor * tensor, const void * src, void * dst) {
|
||||
static void ggml_backend_cann_transform_q4_0(const ggml_tensor * tensor, const void * src, void * dst) {
|
||||
int64_t n_elems = ggml_nelements(tensor);
|
||||
int64_t groups = n_elems / QK4_0;
|
||||
size_t quant_bytes = n_elems * sizeof(uint8_t) / 2;
|
||||
@@ -1014,7 +1014,7 @@ static void ggml_backend_cann_transform_back_q4_0(const ggml_tensor * tensor, vo
|
||||
* @param dst Pointer to the destination buffer where transformed data will be
|
||||
* stored.
|
||||
*/
|
||||
static void ggml_backend_cann_transform_q8_0(ggml_tensor * tensor, const void * src, void * dst) {
|
||||
static void ggml_backend_cann_transform_q8_0(const ggml_tensor * tensor, const void * src, void * dst) {
|
||||
int64_t n_elems = ggml_nelements(tensor);
|
||||
int64_t groups = n_elems / QK8_0;
|
||||
size_t quant_bytes = n_elems * sizeof(uint8_t);
|
||||
@@ -1075,7 +1075,7 @@ static void ggml_backend_cann_transform_back_q8_0(const ggml_tensor * tensor, co
|
||||
* @param dst Pointer to the destination buffer where transformed data will be
|
||||
* stored.
|
||||
*/
|
||||
static void ggml_backend_cann_transform(ggml_tensor * tensor, const void * src, void * dst) {
|
||||
static void ggml_backend_cann_transform(const ggml_tensor * tensor, const void * src, void * dst) {
|
||||
switch (tensor->type) {
|
||||
case GGML_TYPE_Q4_0:
|
||||
ggml_backend_cann_transform_q4_0(tensor, src, dst);
|
||||
|
||||
@@ -287,6 +287,9 @@ void ggml_vec_dot_q2_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
|
||||
sumf = vaddvq_f32(sumv);
|
||||
#else
|
||||
UNUSED(nb);
|
||||
UNUSED(x);
|
||||
UNUSED(y);
|
||||
ggml_vec_dot_q2_0_q8_0_generic(n, s, bs, vx, bx, vy, by, nrc);
|
||||
return;
|
||||
#endif
|
||||
|
||||
@@ -535,6 +535,60 @@ void ggml_gemv_mxfp4_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const v
|
||||
UNUSED(ncols_interleaved);
|
||||
UNUSED(blocklen);
|
||||
|
||||
#if defined(__aarch64__) && defined(__ARM_FEATURE_SVE)
|
||||
if (svcntb() * 8 == 256) {
|
||||
const svbool_t pg16 = svptrue_pat_b8(SV_VL16);
|
||||
const svbool_t pg4 = svptrue_pat_b32(SV_VL4);
|
||||
const svbool_t pgb = svptrue_b8();
|
||||
const svbool_t pgw = svptrue_b32();
|
||||
const svbool_t pg_hi4 = svnot_b_z(pgw, pg4);
|
||||
const svuint8_t m4b = svdup_n_u8(0x0f);
|
||||
const svuint8_t values = svld1_u8(pg16, (const uint8_t *) kvalues_mxfp4);
|
||||
|
||||
// Reorder Q8 four-byte groups as [0,2,4,6,1,3,5,7]
|
||||
const svuint32_t q8_group_index = svsub_n_u32_m(pg_hi4, svindex_u32(0, 2), 7);
|
||||
|
||||
const block_q8_0 * a_ptr = (const block_q8_0 *) vy;
|
||||
float * res_ptr = s;
|
||||
|
||||
for (int x = 0; x < nc / ncols_interleaved; x++) {
|
||||
const block_mxfp4x4 * b_ptr = (const block_mxfp4x4 *) vx + (x * nb);
|
||||
svfloat32_t sumf = svdup_n_f32(0.0f);
|
||||
|
||||
for (int l = 0; l < nb; l++) {
|
||||
const svint8_t q8 = svld1_s8(pgb, a_ptr[l].qs);
|
||||
const svint8_t q8_even_odd = svreinterpret_s8_u32(svtbl_u32(svreinterpret_u32_s8(q8), q8_group_index));
|
||||
const svuint8_t qmx01 = svld1_u8(pgb, b_ptr[l].qs + 0);
|
||||
const svuint8_t qmx_lo01 = svtbl_u8(values, svand_u8_x(pgb, qmx01, m4b));
|
||||
const svuint8_t qmx_hi01 = svtbl_u8(values, svlsr_n_u8_x(pgb, qmx01, 4));
|
||||
const svuint8_t qmx23 = svld1_u8(pgb, b_ptr[l].qs + 32);
|
||||
const svuint8_t qmx_lo23 = svtbl_u8(values, svand_u8_x(pgb, qmx23, m4b));
|
||||
const svuint8_t qmx_hi23 = svtbl_u8(values, svlsr_n_u8_x(pgb, qmx23, 4));
|
||||
|
||||
svint32_t sumi = svdup_n_s32(0);
|
||||
sumi = svdot_lane_s32(sumi, svreinterpret_s8_u8(qmx_lo01), q8_even_odd, 0);
|
||||
sumi = svdot_lane_s32(sumi, svreinterpret_s8_u8(qmx_lo23), q8_even_odd, 1);
|
||||
sumi = svdot_lane_s32(sumi, svreinterpret_s8_u8(qmx_hi01), q8_even_odd, 2);
|
||||
sumi = svdot_lane_s32(sumi, svreinterpret_s8_u8(qmx_hi23), q8_even_odd, 3);
|
||||
const svint32_t sumi_cols = svadd_s32_z(pg4, sumi, svext_s32(sumi, sumi, 4));
|
||||
|
||||
// Vector form of GGML_CPU_E8M0_TO_FP32_HALF
|
||||
const svuint32_t e_u32 = svld1ub_u32(pg4, b_ptr[l].e);
|
||||
const svbool_t e_is_denormal = svcmplt_n_u32(pg4, e_u32, 2);
|
||||
const svuint32_t e_denormal_bits = svlsl_u32_x(pg4, svdup_n_u32(0x00200000), e_u32);
|
||||
const svuint32_t e_normal_bits = svlsl_n_u32_x(pg4, svsub_n_u32_x(pg4, e_u32, 1), 23);
|
||||
const svuint32_t e_bits = svsel_u32(e_is_denormal, e_denormal_bits, e_normal_bits);
|
||||
const svfloat16_t d_f16 = svreinterpret_f16_u16(svdup_n_u16(a_ptr[l].d));
|
||||
const svfloat32_t d_f32 = svcvt_f32_f16_x(pg4, d_f16);
|
||||
const svfloat32_t scale = svmul_f32_x(pg4, svreinterpret_f32_u32(e_bits), d_f32);
|
||||
sumf = svmla_f32_m(pg4, sumf,svcvt_f32_s32_m(svdup_n_f32(0.0f), pg4, sumi_cols), scale);
|
||||
}
|
||||
svst1_f32(pg4, res_ptr + x * 4, sumf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // defined(__aarch64__) && defined(__ARM_FEATURE_SVE)
|
||||
|
||||
#if ! ((defined(_MSC_VER)) && ! defined(__clang__)) && defined(__aarch64__) && defined(__ARM_NEON) && defined(__ARM_FEATURE_DOTPROD)
|
||||
const int8x16_t kvalues = vld1q_s8(kvalues_mxfp4);
|
||||
const block_q8_0 * a_ptr = (const block_q8_0 *) vy;
|
||||
@@ -1791,6 +1845,71 @@ void ggml_gemv_q8_0_4x8_q8_0(int n,
|
||||
UNUSED(ncols_interleaved);
|
||||
UNUSED(blocklen);
|
||||
|
||||
#if defined(__aarch64__) && defined(__ARM_FEATURE_SVE)
|
||||
// 256-bit SVE: 32 x int8 -> 8 x int32
|
||||
if (ggml_cpu_get_sve_cnt() == QK8_0) {
|
||||
const block_q8_0x4 * GGML_RESTRICT b_ptr = (const block_q8_0x4 *) vx;
|
||||
|
||||
const svbool_t pg = svptrue_b8();
|
||||
const svbool_t pg4w = svptrue_pat_b32(SV_VL4);
|
||||
|
||||
// after svtbl + svcvt the scale lanes are [d0,d0,d1,d1,d2,d2,d3,d3]: lane pair j = row j
|
||||
const svuint16_t d_idx = svreinterpret_u16_u32(svlsr_n_u32_x(pg, svindex_u32(0, 1), 1));
|
||||
|
||||
for (int c = 0; c < nc; c += ncols_interleaved) {
|
||||
const block_q8_0 * GGML_RESTRICT a_ptr = (const block_q8_0 *) vy;
|
||||
|
||||
svfloat32_t acc = svdup_n_f32(0.0f);
|
||||
|
||||
for (int b = 0; b < nb; ++b) {
|
||||
const int8_t * GGML_RESTRICT bq = (const int8_t *) b_ptr->qs;
|
||||
|
||||
uint64_t a_chunk0;
|
||||
uint64_t a_chunk1;
|
||||
uint64_t a_chunk2;
|
||||
uint64_t a_chunk3;
|
||||
uint64_t b_d;
|
||||
|
||||
memcpy(&a_chunk0, a_ptr->qs + 0, sizeof(a_chunk0));
|
||||
memcpy(&a_chunk1, a_ptr->qs + 8, sizeof(a_chunk1));
|
||||
memcpy(&a_chunk2, a_ptr->qs + 16, sizeof(a_chunk2));
|
||||
memcpy(&a_chunk3, a_ptr->qs + 24, sizeof(a_chunk3));
|
||||
memcpy(&b_d, b_ptr->d, sizeof(b_d));
|
||||
|
||||
const svint8_t aq0 = svreinterpret_s8_u64(svdup_n_u64(a_chunk0));
|
||||
const svint8_t aq1 = svreinterpret_s8_u64(svdup_n_u64(a_chunk1));
|
||||
const svint8_t aq2 = svreinterpret_s8_u64(svdup_n_u64(a_chunk2));
|
||||
const svint8_t aq3 = svreinterpret_s8_u64(svdup_n_u64(a_chunk3));
|
||||
|
||||
const svint8_t bq0 = svld1_s8(pg, bq + 0);
|
||||
const svint8_t bq1 = svld1_s8(pg, bq + 32);
|
||||
const svint8_t bq2 = svld1_s8(pg, bq + 64);
|
||||
const svint8_t bq3 = svld1_s8(pg, bq + 96);
|
||||
|
||||
const svint32_t iacc0 = svdot_s32(svdup_n_s32(0), bq0, aq0);
|
||||
const svint32_t iacc1 = svdot_s32(svdup_n_s32(0), bq1, aq1);
|
||||
const svint32_t iacc2 = svdot_s32(svdup_n_s32(0), bq2, aq2);
|
||||
const svint32_t iacc3 = svdot_s32(svdup_n_s32(0), bq3, aq3);
|
||||
|
||||
const svint32_t isum = svadd_s32_x(pg, svadd_s32_x(pg, iacc0, iacc1), svadd_s32_x(pg, iacc2, iacc3));
|
||||
|
||||
const svfloat16_t bd_h = svtbl_f16(svreinterpret_f16_u64(svdup_n_u64(b_d)), d_idx);
|
||||
const svfloat32_t scale = svmul_n_f32_x(pg, svcvt_f32_f16_x(pg, bd_h), GGML_CPU_FP16_TO_FP32(a_ptr->d));
|
||||
|
||||
acc = svmla_f32_x(pg, acc, svcvt_f32_s32_x(pg, isum), scale);
|
||||
|
||||
++a_ptr;
|
||||
++b_ptr;
|
||||
}
|
||||
|
||||
svst1_f32(pg4w, s, svadd_f32_x(pg, svuzp1_f32(acc, acc), svuzp2_f32(acc, acc)));
|
||||
s += ncols_interleaved;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif // defined(__aarch64__) && defined(__ARM_FEATURE_SVE)
|
||||
|
||||
#if defined(__aarch64__) && defined(__ARM_NEON) && defined(__ARM_FEATURE_DOTPROD)
|
||||
const block_q8_0x4 * b_ptr = (const block_q8_0x4 *) vx;
|
||||
|
||||
@@ -4874,6 +4993,7 @@ void ggml_gemm_q6_K_8x8_q8_K(int n,
|
||||
|
||||
constexpr int ncols_interleaved = 8;
|
||||
constexpr int blocklen = 8;
|
||||
constexpr int q8_k_blocklen = 4;
|
||||
|
||||
assert(n % qk == 0);
|
||||
assert(nr % 4 == 0);
|
||||
@@ -4882,9 +5002,195 @@ void ggml_gemm_q6_K_8x8_q8_K(int n,
|
||||
UNUSED(nb);
|
||||
UNUSED(ncols_interleaved);
|
||||
UNUSED(blocklen);
|
||||
UNUSED(q8_k_blocklen);
|
||||
|
||||
#if defined(__aarch64__) && defined(__ARM_FEATURE_SVE) && defined(__ARM_FEATURE_MATMUL_INT8)
|
||||
if (svcntb() * 8 == 256) {
|
||||
const svbool_t pg8 = svptrue_b8();
|
||||
const svbool_t pg32 = svptrue_b32();
|
||||
|
||||
const svuint8_t low_nibble = svdup_n_u8(0x0f);
|
||||
const svuint8_t high_lo2 = svdup_n_u8(0x03);
|
||||
const svuint8_t high_hi2 = svdup_n_u8(0x30);
|
||||
const svint8_t q6_bias = svdup_n_s8(32);
|
||||
const svint32_t zero_s32 = svdup_n_s32(0);
|
||||
|
||||
for (int y = 0; y < nr / q8_k_blocklen; ++y) {
|
||||
const block_q8_Kx4 * GGML_RESTRICT q8_ptr = (const block_q8_Kx4 *) vy + y * nb;
|
||||
|
||||
for (int x = 0; x < nc / ncols_interleaved; ++x) {
|
||||
const block_q6_Kx8 * GGML_RESTRICT q6_ptr = (const block_q6_Kx8 *) vx + x * nb;
|
||||
|
||||
// one accumlator per output row
|
||||
svfloat32_t acc_f32_r0 = svdup_n_f32(0);
|
||||
svfloat32_t acc_f32_r1 = svdup_n_f32(0);
|
||||
svfloat32_t acc_f32_r2 = svdup_n_f32(0);
|
||||
svfloat32_t acc_f32_r3 = svdup_n_f32(0);
|
||||
|
||||
for (int b = 0; b < nb; ++b) {
|
||||
svint32_t acc_c03_r01 = zero_s32;
|
||||
svint32_t acc_c03_r23 = zero_s32;
|
||||
svint32_t acc_c47_r01 = zero_s32;
|
||||
svint32_t acc_c47_r23 = zero_s32;
|
||||
|
||||
for (int half = 0; half < 2; ++half) {
|
||||
const uint8_t * ql_base = q6_ptr[b].ql + half * 512;
|
||||
const uint8_t * qh_base = q6_ptr[b].qh + half * 256;
|
||||
|
||||
for (int sb = 0; sb < QK_K / 64; ++sb) {
|
||||
const int8_t * q8_base_l = q8_ptr[b].qs + half * 512 + sb * 64;
|
||||
const int8_t * q8_base_h = q8_ptr[b].qs + half * 512 + 256 + sb * 64;
|
||||
|
||||
// load 16 bytes (8B of R0, 8B of R1) and repeat in upper half
|
||||
// low lanes will multiply with 16 bytes of C0 and high lanes will multiply with 16 bytes of C1
|
||||
const svint8_t q8_l_r01_k0 = svld1rq_s8(pg8, q8_base_l + 0);
|
||||
const svint8_t q8_l_r01_k1 = svld1rq_s8(pg8, q8_base_l + 32);
|
||||
const svint8_t q8_l_r23_k0 = svld1rq_s8(pg8, q8_base_l + 16);
|
||||
const svint8_t q8_l_r23_k1 = svld1rq_s8(pg8, q8_base_l + 48);
|
||||
|
||||
const svint8_t q8_h_r01_k0 = svld1rq_s8(pg8, q8_base_h + 0);
|
||||
const svint8_t q8_h_r01_k1 = svld1rq_s8(pg8, q8_base_h + 32);
|
||||
const svint8_t q8_h_r23_k0 = svld1rq_s8(pg8, q8_base_h + 16);
|
||||
const svint8_t q8_h_r23_k1 = svld1rq_s8(pg8, q8_base_h + 48);
|
||||
|
||||
const int ql_off = sb * QK_K / 2;
|
||||
const int qh_off = ql_off & 255;
|
||||
|
||||
const int scale_idx_l = half * 8 + sb;
|
||||
const int scale_idx_h = scale_idx_l + 4;
|
||||
|
||||
const svint32_t scales_l_all = svld1sb_s32(pg32, q6_ptr[b].scales + scale_idx_l * 8);
|
||||
const svint32_t scales_h_all = svld1sb_s32(pg32, q6_ptr[b].scales + scale_idx_h * 8);
|
||||
|
||||
const svint32_t scales_l_c03 = svzip1_s32(scales_l_all, scales_l_all);
|
||||
const svint32_t scales_h_c03 = svzip1_s32(scales_h_all, scales_h_all);
|
||||
|
||||
{
|
||||
svuint8_t q6_ql0_01 = svld1_u8(pg8, ql_base + ql_off);
|
||||
svuint8_t q6_ql1_01 = svld1_u8(pg8, ql_base + ql_off + 64);
|
||||
svuint8_t q6_qh0_01 = svld1_u8(pg8, qh_base + qh_off);
|
||||
svuint8_t q6_qh1_01 = svld1_u8(pg8, qh_base + qh_off + 64);
|
||||
|
||||
if (sb > 1) {
|
||||
q6_qh0_01 = svlsr_n_u8_x(pg8, q6_qh0_01, 2);
|
||||
q6_qh1_01 = svlsr_n_u8_x(pg8, q6_qh1_01, 2);
|
||||
}
|
||||
|
||||
const svuint8_t q6_qh0_01_hi = svand_u8_x(pg8, q6_qh0_01, high_hi2);
|
||||
const svuint8_t q6_qh1_01_hi = svand_u8_x(pg8, q6_qh1_01, high_hi2);
|
||||
|
||||
const svuint8_t q6_l0_01_bits = svorr_u8_x(pg8, svand_u8_x(pg8, q6_ql0_01, low_nibble), svlsl_n_u8_x(pg8, svand_u8_x(pg8, q6_qh0_01, high_lo2), 4));
|
||||
const svuint8_t q6_l1_01_bits = svorr_u8_x(pg8, svand_u8_x(pg8, q6_ql1_01, low_nibble), svlsl_n_u8_x(pg8, svand_u8_x(pg8, q6_qh1_01, high_lo2), 4));
|
||||
const svuint8_t q6_h0_01_bits = svorr_u8_x(pg8, svlsr_n_u8_x(pg8, q6_ql0_01, 4), q6_qh0_01_hi);
|
||||
const svuint8_t q6_h1_01_bits = svorr_u8_x(pg8, svlsr_n_u8_x(pg8, q6_ql1_01, 4), q6_qh1_01_hi);
|
||||
|
||||
const svint8_t q6_l0_01 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_l0_01_bits), q6_bias);
|
||||
const svint8_t q6_l1_01 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_l1_01_bits), q6_bias);
|
||||
const svint8_t q6_h0_01 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_h0_01_bits), q6_bias);
|
||||
const svint8_t q6_h1_01 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_h1_01_bits), q6_bias);
|
||||
|
||||
svint32_t dot_l_r01 = svmmla_s32(zero_s32, q6_l0_01, q8_l_r01_k0);
|
||||
dot_l_r01 = svmmla_s32(dot_l_r01, q6_l1_01, q8_l_r01_k1);
|
||||
|
||||
svint32_t dot_h_r01 = svmmla_s32(zero_s32, q6_h0_01, q8_h_r01_k0);
|
||||
dot_h_r01 = svmmla_s32(dot_h_r01, q6_h1_01, q8_h_r01_k1);
|
||||
|
||||
svint32_t dot_l_r23 = svmmla_s32(zero_s32, q6_l0_01, q8_l_r23_k0);
|
||||
dot_l_r23 = svmmla_s32(dot_l_r23, q6_l1_01, q8_l_r23_k1);
|
||||
|
||||
svint32_t dot_h_r23 = svmmla_s32(zero_s32, q6_h0_01, q8_h_r23_k0);
|
||||
dot_h_r23 = svmmla_s32(dot_h_r23, q6_h1_01, q8_h_r23_k1);
|
||||
|
||||
acc_c03_r01 = svmla_s32_x(pg32, acc_c03_r01, dot_l_r01, scales_l_c03);
|
||||
acc_c03_r01 = svmla_s32_x(pg32, acc_c03_r01, dot_h_r01, scales_h_c03);
|
||||
acc_c03_r23 = svmla_s32_x(pg32, acc_c03_r23, dot_l_r23, scales_l_c03);
|
||||
acc_c03_r23 = svmla_s32_x(pg32, acc_c03_r23, dot_h_r23, scales_h_c03);
|
||||
}
|
||||
|
||||
const svint32_t scales_l_c47 = svzip2_s32(scales_l_all, scales_l_all);
|
||||
const svint32_t scales_h_c47 = svzip2_s32(scales_h_all, scales_h_all);
|
||||
|
||||
{
|
||||
svuint8_t q6_ql0_23 = svld1_u8(pg8, ql_base + ql_off + 32);
|
||||
svuint8_t q6_ql1_23 = svld1_u8(pg8, ql_base + ql_off + 96);
|
||||
svuint8_t q6_qh0_23 = svld1_u8(pg8, qh_base + qh_off + 32);
|
||||
svuint8_t q6_qh1_23 = svld1_u8(pg8, qh_base + qh_off + 96);
|
||||
|
||||
if (sb > 1) {
|
||||
q6_qh0_23 = svlsr_n_u8_x(pg8, q6_qh0_23, 2);
|
||||
q6_qh1_23 = svlsr_n_u8_x(pg8, q6_qh1_23, 2);
|
||||
}
|
||||
|
||||
const svuint8_t q6_qh0_23_hi = svand_u8_x(pg8, q6_qh0_23, high_hi2);
|
||||
const svuint8_t q6_qh1_23_hi = svand_u8_x(pg8, q6_qh1_23, high_hi2);
|
||||
|
||||
const svuint8_t q6_l0_23_bits = svorr_u8_x(pg8, svand_u8_x(pg8, q6_ql0_23, low_nibble), svlsl_n_u8_x(pg8, svand_u8_x(pg8, q6_qh0_23, high_lo2), 4));
|
||||
const svuint8_t q6_l1_23_bits = svorr_u8_x(pg8, svand_u8_x(pg8, q6_ql1_23, low_nibble), svlsl_n_u8_x(pg8, svand_u8_x(pg8, q6_qh1_23, high_lo2), 4));
|
||||
const svuint8_t q6_h0_23_bits = svorr_u8_x(pg8, svlsr_n_u8_x(pg8, q6_ql0_23, 4), q6_qh0_23_hi);
|
||||
const svuint8_t q6_h1_23_bits = svorr_u8_x(pg8, svlsr_n_u8_x(pg8, q6_ql1_23, 4), q6_qh1_23_hi);
|
||||
|
||||
const svint8_t q6_l0_23 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_l0_23_bits), q6_bias);
|
||||
const svint8_t q6_l1_23 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_l1_23_bits), q6_bias);
|
||||
const svint8_t q6_h0_23 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_h0_23_bits), q6_bias);
|
||||
const svint8_t q6_h1_23 = svsub_s8_x(pg8, svreinterpret_s8_u8(q6_h1_23_bits), q6_bias);
|
||||
|
||||
svint32_t dot_l_r01 = svmmla_s32(zero_s32, q6_l0_23, q8_l_r01_k0);
|
||||
dot_l_r01 = svmmla_s32(dot_l_r01, q6_l1_23, q8_l_r01_k1);
|
||||
|
||||
svint32_t dot_h_r01 = svmmla_s32(zero_s32, q6_h0_23, q8_h_r01_k0);
|
||||
dot_h_r01 = svmmla_s32(dot_h_r01, q6_h1_23, q8_h_r01_k1);
|
||||
|
||||
svint32_t dot_l_r23 = svmmla_s32(zero_s32, q6_l0_23, q8_l_r23_k0);
|
||||
dot_l_r23 = svmmla_s32(dot_l_r23, q6_l1_23, q8_l_r23_k1);
|
||||
|
||||
svint32_t dot_h_r23 = svmmla_s32(zero_s32, q6_h0_23, q8_h_r23_k0);
|
||||
dot_h_r23 = svmmla_s32(dot_h_r23, q6_h1_23, q8_h_r23_k1);
|
||||
|
||||
acc_c47_r01 = svmla_s32_x(pg32, acc_c47_r01, dot_l_r01, scales_l_c47);
|
||||
acc_c47_r01 = svmla_s32_x(pg32, acc_c47_r01, dot_h_r01, scales_h_c47);
|
||||
acc_c47_r23 = svmla_s32_x(pg32, acc_c47_r23, dot_l_r23, scales_l_c47);
|
||||
acc_c47_r23 = svmla_s32_x(pg32, acc_c47_r23, dot_h_r23, scales_h_c47);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// acc_c03_r01 -> c0-r0,c0-r1,c1-r0,c1-r1,c2-r0,c2-r1,c3-r0,c3-r1
|
||||
// acc_c47_r01 -> c4-r0,c4-r1,c5-r0,c5-r1,c6-r0,c6-r1,c7-r0,c7-r1
|
||||
// svuzp1_s32 picks even lanes -> c0-r0,c1-r0,c2-r0,c3-r0,c4-r0,c5-r0,c6-r0,c7-r0
|
||||
// similarly svuzp2_s32 picks remaining values from odd lanes
|
||||
const svint32_t dot_r0 = svuzp1_s32(acc_c03_r01, acc_c47_r01);
|
||||
const svint32_t dot_r1 = svuzp2_s32(acc_c03_r01, acc_c47_r01);
|
||||
const svint32_t dot_r2 = svuzp1_s32(acc_c03_r23, acc_c47_r23);
|
||||
const svint32_t dot_r3 = svuzp2_s32(acc_c03_r23, acc_c47_r23);
|
||||
|
||||
const svuint32_t q6_d_bits = svld1uh_u32(pg32, (const uint16_t *) q6_ptr[b].d);
|
||||
const svfloat32_t q6_d_f32 = svcvt_f32_f16_x(pg32, svreinterpret_f16_u32(q6_d_bits));
|
||||
|
||||
const svfloat32_t scale_r0 = svmul_n_f32_x(pg32, q6_d_f32, q8_ptr[b].d[0]);
|
||||
const svfloat32_t scale_r1 = svmul_n_f32_x(pg32, q6_d_f32, q8_ptr[b].d[1]);
|
||||
const svfloat32_t scale_r2 = svmul_n_f32_x(pg32, q6_d_f32, q8_ptr[b].d[2]);
|
||||
const svfloat32_t scale_r3 = svmul_n_f32_x(pg32, q6_d_f32, q8_ptr[b].d[3]);
|
||||
|
||||
acc_f32_r0 = svmla_f32_x(pg32, acc_f32_r0, svcvt_f32_s32_x(pg32, dot_r0), scale_r0);
|
||||
acc_f32_r1 = svmla_f32_x(pg32, acc_f32_r1, svcvt_f32_s32_x(pg32, dot_r1), scale_r1);
|
||||
acc_f32_r2 = svmla_f32_x(pg32, acc_f32_r2, svcvt_f32_s32_x(pg32, dot_r2), scale_r2);
|
||||
acc_f32_r3 = svmla_f32_x(pg32, acc_f32_r3, svcvt_f32_s32_x(pg32, dot_r3), scale_r3);
|
||||
}
|
||||
|
||||
const int row = y * q8_k_blocklen;
|
||||
const int col = x * ncols_interleaved;
|
||||
|
||||
svst1_f32(pg32, s + (row + 0) * bs + col, acc_f32_r0);
|
||||
svst1_f32(pg32, s + (row + 1) * bs + col, acc_f32_r1);
|
||||
svst1_f32(pg32, s + (row + 2) * bs + col, acc_f32_r2);
|
||||
svst1_f32(pg32, s + (row + 3) * bs + col, acc_f32_r3);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // SVE compile-time end
|
||||
|
||||
#if defined(__aarch64__) && defined(__ARM_NEON) && defined(__ARM_FEATURE_MATMUL_INT8)
|
||||
constexpr int q8_k_blocklen = 4;
|
||||
const uint8x16_t m4b = vdupq_n_u8(0x0f);
|
||||
const uint8x16_t mask_lo = vdupq_n_u8(0x03);
|
||||
const uint8x16_t mask_hi = vdupq_n_u8(0x30);
|
||||
|
||||
@@ -1652,7 +1652,12 @@ static void ggml_compute_forward_mul_mat_id(
|
||||
for (int id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = *(const int32_t *) ((const char *) ids->data + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
|
||||
assert(i02 >= 0 && i02 < n_as);
|
||||
assert(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = (struct mmid_row_mapping) {id, iid1};
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
+100
-215
@@ -743,12 +743,17 @@ static void ggml_compute_forward_add_id_f32(
|
||||
// src1 indices
|
||||
const int i11 = *(int32_t *) ((char *) src2->data + i1*nb20 + i2*nb21);
|
||||
|
||||
GGML_ASSERT(i11 >= 0 && i11 < ne11);
|
||||
GGML_ASSERT(i11 == -1 || (i11 >= 0 && i11 < ne11));
|
||||
|
||||
ggml_vec_add_f32(ne0,
|
||||
(float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1 ),
|
||||
(float *) ((char *) src0->data + i3*nb03 + i2*nb02 + i1*nb01),
|
||||
(float *) ((char *) src1->data + i11*nb11));
|
||||
float * dst_row = (float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1 );
|
||||
const float * src0_row = (float *) ((char *) src0->data + i3*nb03 + i2*nb02 + i1*nb01);
|
||||
|
||||
if (i11 == -1) {
|
||||
memcpy(dst_row, src0_row, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
ggml_vec_add_f32(ne0, dst_row, src0_row, (float *) ((char *) src1->data + i11*nb11));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1909,213 +1914,87 @@ void ggml_compute_forward_repeat_back(
|
||||
}
|
||||
|
||||
// ggml_compute_forward_concat
|
||||
static void ggml_compute_forward_concat_any(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
// rows of dst copied together, so that a strided source is read along neighboring rows
|
||||
static constexpr int64_t GGML_CONCAT_TILE = 32;
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
||||
|
||||
GGML_ASSERT(dim >= 0 && dim < 4);
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src1));
|
||||
|
||||
int64_t o[4] = {0, 0, 0, 0};
|
||||
|
||||
if (dim == 0) {
|
||||
GGML_ASSERT(src0->ne[0] % ggml_blck_size(src0->type) == 0);
|
||||
GGML_ASSERT(src1->ne[0] % ggml_blck_size(src1->type) == 0);
|
||||
|
||||
o[dim] = src0->ne[dim]/ggml_blck_size(src0->type);
|
||||
} else {
|
||||
o[dim] = src0->ne[dim];
|
||||
}
|
||||
|
||||
// Region 1: copy rows from src0
|
||||
for (int i3 = 0; i3 < ne03; i3++) {
|
||||
for (int i2 = ith; i2 < ne02; i2 += nth) {
|
||||
for (int i1 = 0; i1 < ne01; i1++) {
|
||||
const char * x = (const char *) src0->data + i1*nb01 + i2*nb02 + i3*nb03;
|
||||
char * y = ( char *) dst->data + i1*nb1 + i2*nb2 + i3*nb3;
|
||||
memcpy(y, x, ggml_row_size(src0->type, ne00));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Region 2: copy rows from src1, offset into dst by o[]
|
||||
for (int i3 = 0; i3 < ne13; i3++) {
|
||||
for (int i2 = ith; i2 < ne12; i2 += nth) {
|
||||
for (int i1 = 0; i1 < ne11; i1++) {
|
||||
const char * x = (const char *) src1->data + i1*nb11 + i2*nb12 + i3*nb13;
|
||||
char * y = ( char *) dst->data + (i1 + o[1])*nb1 + (i2 + o[2])*nb2 + (i3 + o[3])*nb3 + o[0]*nb0;
|
||||
memcpy(y, x, ggml_row_size(src1->type, ne10));
|
||||
}
|
||||
// copy n_rows rows of n elements of es bytes from a source with element stride nb0, element by element
|
||||
template <size_t es>
|
||||
static void ggml_concat_copy_strided(char * y, size_t nby, const char * x, size_t nbx, size_t nb0, int64_t n, int64_t n_rows) {
|
||||
for (int64_t i0 = 0; i0 < n; i0++) {
|
||||
for (int64_t r = 0; r < n_rows; r++) {
|
||||
memcpy(y + r*nby + i0*es, x + r*nbx + i0*nb0, es);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_concat_i8(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
// copy n_rows rows of n elements of src into dst, rows are nbx bytes apart in src and nby bytes apart in dst
|
||||
static void ggml_concat_copy_rows(char * y, size_t nby, const ggml_tensor * src, const char * x, size_t nbx, int64_t n, int64_t n_rows) {
|
||||
const size_t es = ggml_type_size(src->type);
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(int8_t));
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
||||
|
||||
GGML_ASSERT(dim >= 0 && dim < 4);
|
||||
|
||||
int64_t o[4] = {0, 0, 0, 0};
|
||||
o[dim] = src0->ne[dim];
|
||||
|
||||
const int8_t * x;
|
||||
|
||||
// TODO: smarter multi-theading
|
||||
for (int i3 = 0; i3 < ne3; i3++) {
|
||||
for (int i2 = ith; i2 < ne2; i2 += nth) {
|
||||
for (int i1 = 0; i1 < ne1; i1++) {
|
||||
for (int i0 = 0; i0 < ne0; i0++) {
|
||||
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
|
||||
x = (const int8_t *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
|
||||
} else {
|
||||
x = (const int8_t *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
|
||||
}
|
||||
|
||||
int8_t * y = (int8_t *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
|
||||
|
||||
*y = *x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_concat_f16(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(ggml_fp16_t));
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
||||
|
||||
GGML_ASSERT(dim >= 0 && dim < 4);
|
||||
|
||||
int64_t o[4] = {0, 0, 0, 0};
|
||||
o[dim] = src0->ne[dim];
|
||||
|
||||
const ggml_fp16_t * x;
|
||||
|
||||
// TODO: smarter multi-theading
|
||||
for (int i3 = 0; i3 < ne3; i3++) {
|
||||
for (int i2 = ith; i2 < ne2; i2 += nth) {
|
||||
for (int i1 = 0; i1 < ne1; i1++) {
|
||||
for (int i0 = 0; i0 < ne0; i0++) {
|
||||
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
|
||||
x = (const ggml_fp16_t *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
|
||||
} else {
|
||||
x = (const ggml_fp16_t *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
|
||||
}
|
||||
|
||||
ggml_fp16_t * y = (ggml_fp16_t *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
|
||||
|
||||
*y = *x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_concat_f32(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(float));
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
||||
|
||||
GGML_ASSERT(dim >= 0 && dim < 4);
|
||||
|
||||
int64_t o[4] = {0, 0, 0, 0};
|
||||
o[dim] = src0->ne[dim];
|
||||
|
||||
const float * x;
|
||||
|
||||
// TODO: smarter multi-theading
|
||||
for (int i3 = 0; i3 < ne3; i3++) {
|
||||
for (int i2 = ith; i2 < ne2; i2 += nth) {
|
||||
for (int i1 = 0; i1 < ne1; i1++) {
|
||||
for (int i0 = 0; i0 < ne0; i0++) {
|
||||
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
|
||||
x = (const float *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
|
||||
} else {
|
||||
x = (const float *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
|
||||
}
|
||||
|
||||
float * y = (float *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
|
||||
|
||||
*y = *x;
|
||||
}
|
||||
}
|
||||
if (src->nb[0] == es) {
|
||||
const size_t row_size = ggml_row_size(src->type, n);
|
||||
for (int64_t r = 0; r < n_rows; r++) {
|
||||
memcpy(y + r*nby, x + r*nbx, row_size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (es) {
|
||||
case 1: ggml_concat_copy_strided<1>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
|
||||
case 2: ggml_concat_copy_strided<2>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
|
||||
case 4: ggml_concat_copy_strided<4>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
|
||||
case 8: ggml_concat_copy_strided<8>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
|
||||
default: GGML_ABORT("unsupported element size");
|
||||
}
|
||||
}
|
||||
|
||||
// each dst row comes from src0 or src1, or for dim 0 from the row of src0 followed by the row of src1
|
||||
void ggml_compute_forward_concat(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
switch (src0->type) {
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_BF16:
|
||||
case GGML_TYPE_I16:
|
||||
{
|
||||
ggml_compute_forward_concat_f16(params, dst);
|
||||
} break;
|
||||
case GGML_TYPE_I8:
|
||||
{
|
||||
ggml_compute_forward_concat_i8(params, dst);
|
||||
} break;
|
||||
case GGML_TYPE_F32:
|
||||
case GGML_TYPE_I32:
|
||||
{
|
||||
ggml_compute_forward_concat_f32(params, dst);
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
ggml_compute_forward_concat_any(params, dst);
|
||||
}
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
||||
|
||||
GGML_ASSERT(dim >= 0 && dim < 4);
|
||||
GGML_ASSERT(nb0 == ggml_type_size(dst->type));
|
||||
GGML_ASSERT(!ggml_is_quantized(dst->type) || (nb00 == nb0 && nb10 == nb0));
|
||||
GGML_ASSERT(dim != 0 || (ne00 % ggml_blck_size(dst->type) == 0 && ne10 % ggml_blck_size(dst->type) == 0));
|
||||
|
||||
const size_t y1 = dim == 0 ? ggml_row_size(dst->type, ne00) : 0;
|
||||
|
||||
const int64_t nr = ne1*ne2*ne3;
|
||||
const int64_t dr = (nr + params->nth - 1)/params->nth;
|
||||
|
||||
const int64_t ir0 = dr*params->ith;
|
||||
const int64_t ir1 = MIN(ir0 + dr, nr);
|
||||
|
||||
for (int64_t ir = ir0; ir < ir1; ) {
|
||||
int64_t i[4] = { 0, ir % ne1, (ir/ne1) % ne2, ir/(ne1*ne2) };
|
||||
|
||||
// a tile of consecutive rows sharing i2, i3 and their source
|
||||
int64_t nt = MIN(MIN(ir1 - ir, ne1 - i[1]), GGML_CONCAT_TILE);
|
||||
if (dim == 1 && i[1] < ne01) {
|
||||
nt = MIN(nt, ne01 - i[1]);
|
||||
}
|
||||
|
||||
char * y = (char *) dst->data + i[1]*nb1 + i[2]*nb2 + i[3]*nb3;
|
||||
|
||||
if (dim == 0 || i[dim] < src0->ne[dim]) {
|
||||
ggml_concat_copy_rows(y, nb1, src0, (const char *) src0->data + i[1]*nb01 + i[2]*nb02 + i[3]*nb03, nb01, ne00, nt);
|
||||
}
|
||||
|
||||
if (dim == 0 || i[dim] >= src0->ne[dim]) {
|
||||
i[dim] -= src0->ne[dim]; // i[0] does not address rows
|
||||
ggml_concat_copy_rows(y + y1, nb1, src1, (const char *) src1->data + i[1]*nb11 + i[2]*nb12 + i[3]*nb13, nb11, ne10, nt);
|
||||
}
|
||||
|
||||
ir += nt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4967,8 +4846,8 @@ static void ggml_compute_forward_scale_f32(
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(dst));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(dst));
|
||||
GGML_ASSERT(ggml_are_same_shape(src0, dst));
|
||||
|
||||
float s; // scale factor
|
||||
@@ -4983,6 +4862,8 @@ static void ggml_compute_forward_scale_f32(
|
||||
const int nc = src0->ne[0];
|
||||
const int nr = ggml_nrows(src0);
|
||||
|
||||
GGML_TENSOR_UNARY_OP_LOCALS
|
||||
|
||||
// rows per thread
|
||||
const int dr = (nr + nth - 1)/nth;
|
||||
|
||||
@@ -4990,25 +4871,23 @@ static void ggml_compute_forward_scale_f32(
|
||||
const int ir0 = dr*ith;
|
||||
const int ir1 = MIN(ir0 + dr, nr);
|
||||
|
||||
const size_t nb01 = src0->nb[1];
|
||||
for (int i = ir0; i < ir1; i++) {
|
||||
const int64_t i1 = i % ne01;
|
||||
const int64_t i2 = (i / ne01) % ne02;
|
||||
const int64_t i3 = i / (ne01*ne02);
|
||||
|
||||
const size_t nb1 = dst->nb[1];
|
||||
float * y = (float *) ((char *) dst->data + i1*nb1 + i2*nb2 + i3*nb3);
|
||||
const float * x = (const float *) ((char *) src0->data + i1*nb01 + i2*nb02 + i3*nb03);
|
||||
|
||||
if (b == 0.0f) {
|
||||
for (int i1 = ir0; i1 < ir1; i1++) {
|
||||
if (b == 0.0f) {
|
||||
if (dst->data != src0->data) {
|
||||
// src0 is same shape as dst => same indices
|
||||
// TODO: add x parameter to ggml_vec_scale_f32 and remove this memcpy
|
||||
memcpy((char *)dst->data + i1*nb1, (char *)src0->data + i1*nb01, nc * sizeof(float));
|
||||
memcpy(y, x, nc * sizeof(float));
|
||||
}
|
||||
ggml_vec_scale_f32(nc, (float *) ((char *) dst->data + i1*nb1), s);
|
||||
}
|
||||
} else {
|
||||
for (int i1 = ir0; i1 < ir1; i1++) {
|
||||
ggml_vec_mad1_f32(nc,
|
||||
(float *) ((char *) dst->data + i1*nb1),
|
||||
(float *) ((char *) src0->data + i1*nb1),
|
||||
s, b);
|
||||
ggml_vec_scale_f32(nc, y, s);
|
||||
} else {
|
||||
ggml_vec_mad1_f32(nc, y, x, s, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5019,8 +4898,8 @@ static void ggml_compute_forward_scale_bf16(
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(dst));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(dst));
|
||||
GGML_ASSERT(ggml_are_same_shape(src0, dst));
|
||||
|
||||
float s; // scale factor
|
||||
@@ -5035,6 +4914,8 @@ static void ggml_compute_forward_scale_bf16(
|
||||
const int nc = src0->ne[0];
|
||||
const int nr = ggml_nrows(src0);
|
||||
|
||||
GGML_TENSOR_UNARY_OP_LOCALS
|
||||
|
||||
// rows per thread
|
||||
const int dr = (nr + nth - 1)/nth;
|
||||
|
||||
@@ -5042,9 +4923,13 @@ static void ggml_compute_forward_scale_bf16(
|
||||
const int ir0 = dr*ith;
|
||||
const int ir1 = MIN(ir0 + dr, nr);
|
||||
|
||||
for (int i1 = ir0; i1 < ir1; i1++) {
|
||||
const ggml_bf16_t * x = (const ggml_bf16_t *) ((const char *) src0->data + i1*src0->nb[1]);
|
||||
ggml_bf16_t * y = (ggml_bf16_t *) ((char *) dst->data + i1*dst->nb[1]);
|
||||
for (int i = ir0; i < ir1; i++) {
|
||||
const int64_t i1 = i % ne01;
|
||||
const int64_t i2 = (i / ne01) % ne02;
|
||||
const int64_t i3 = i / (ne01*ne02);
|
||||
|
||||
const ggml_bf16_t * x = (const ggml_bf16_t *) ((const char *) src0->data + i1*nb01 + i2*nb02 + i3*nb03);
|
||||
ggml_bf16_t * y = (ggml_bf16_t *) ((char *) dst->data + i1*nb1 + i2*nb2 + i3*nb3);
|
||||
for (int k = 0; k < nc; k++) {
|
||||
y[k] = GGML_FP32_TO_BF16(s*GGML_BF16_TO_FP32(x[k]) + b);
|
||||
}
|
||||
|
||||
@@ -4855,7 +4855,12 @@ template <typename BLOC_TYPE, int64_t INTER_SIZE, int64_t NB_COLS, ggml_type PAR
|
||||
const int32_t i02 =
|
||||
*(const int32_t *) ((const char *) ids->data + iid1 * ids->nb[1] + id * ids->nb[0]);
|
||||
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = { id, iid1 };
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
@@ -682,7 +682,12 @@ template <typename BLOC_TYPE, int64_t INTER_SIZE, int64_t NB_COLS> class tensor_
|
||||
const int32_t i02 =
|
||||
*(const int32_t *) ((const char *) ids->data + iid1 * ids->nb[1] + id * ids->nb[0]);
|
||||
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id * nb1 + iid1 * nb2, 0, ne0 * sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = { id, iid1 };
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
@@ -78,7 +78,7 @@ static inline float op_expm1(float x) {
|
||||
}
|
||||
|
||||
static inline float op_softplus(float x) {
|
||||
return (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
return (x > 20.0f) ? x : log1pf(expf(x));
|
||||
}
|
||||
|
||||
static inline float op_floor(float x) {
|
||||
|
||||
@@ -1264,15 +1264,12 @@ struct ggml_tensor_extra_gpu {
|
||||
struct ggml_cuda_graph {
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
~ggml_cuda_graph() {
|
||||
if (instance != nullptr) {
|
||||
for (cudaGraphExec_t instance : instances) {
|
||||
CUDA_CHECK(cudaGraphExecDestroy(instance));
|
||||
}
|
||||
if (graph != nullptr) {
|
||||
CUDA_CHECK(cudaGraphDestroy(graph));
|
||||
}
|
||||
}
|
||||
cudaGraph_t graph = nullptr;
|
||||
cudaGraphExec_t instance = nullptr;
|
||||
// the ggml graph is captured as a series of CUDA graphs, so the GPU can run one chunk while the next is enqueued
|
||||
std::vector<cudaGraphExec_t> instances;
|
||||
size_t num_nodes = 0;
|
||||
std::vector<cudaGraphNode_t> nodes;
|
||||
bool disable_due_to_gpu_arch = false;
|
||||
@@ -1501,7 +1498,7 @@ struct ggml_backend_cuda_context {
|
||||
// Check if any CUDA graph has an instance for this context
|
||||
bool any_cuda_graph_has_instance() const {
|
||||
for (const auto & [key, graph] : cuda_graphs) {
|
||||
if (graph && graph->instance != nullptr) {
|
||||
if (graph && !graph->instances.empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,6 @@ static __global__ void conv2d_implicit_gemm_f16(const half * __restrict__ input,
|
||||
const int end = int(int64_t(tiles) * (split + 1) / split_k) * BK;
|
||||
for (int k0 = begin; k0 < end; k0 += BK) {
|
||||
if (k_total % 8 == 0 && uintptr_t(weight) % 16 == 0) {
|
||||
#pragma unroll
|
||||
for (int i = tid; i < BM * BK / 8; i += nthreads) {
|
||||
const int row = i / (BK / 8), col = 8 * (i % (BK / 8));
|
||||
const int4 v = m0 + row < oc && k0 + col < k_total ?
|
||||
@@ -189,7 +188,6 @@ static __global__ void conv2d_implicit_gemm_f16(const half * __restrict__ input,
|
||||
*(int4 *) &a_s[row][col / 2] = v;
|
||||
}
|
||||
} else {
|
||||
#pragma unroll
|
||||
for (int i = tid; i < BM * BK / 2; i += nthreads) {
|
||||
const int row = i / (BK / 2), col = 2 * (i % (BK / 2));
|
||||
half lo = __float2half(0.0f), hi = lo;
|
||||
@@ -202,7 +200,6 @@ static __global__ void conv2d_implicit_gemm_f16(const half * __restrict__ input,
|
||||
a_s[row][col / 2] = __halves2half2(lo, hi);
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int k = load_row; k < BK; k += nthreads / (BN / 2)) {
|
||||
const int ki = k0 + k;
|
||||
const int ci = ki / (kw * kh), ky = ki / kw % kh, kx = ki % kw;
|
||||
|
||||
+175
-63
@@ -214,52 +214,36 @@ static cudaError_t ggml_cuda_device_malloc(void ** ptr, size_t size, int device)
|
||||
}
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
static int ggml_cuda_parse_id(char devName[]) {
|
||||
static int ggml_cuda_parse_id(const char * devName) {
|
||||
// A list of possible Target IDs can be found under the rocclr/clr repo in device.cpp
|
||||
// these values are not stable so this is susceptible to breakage
|
||||
// https://github.com/ROCm/clr/blob/amd-staging/rocclr/device/device.cpp
|
||||
int archMajor = 0x0;
|
||||
int archMinor = 0x0;
|
||||
int archNum = GGML_CUDA_CC_OFFSET_AMD;
|
||||
int archLen = strlen(devName);
|
||||
char archName[archLen + 1];
|
||||
// Extract the base architecture model ("gfx908:sramecc+:xnack-" -> "908")
|
||||
std::string archName = devName;
|
||||
archName.erase(0, 3);
|
||||
archName = archName.substr(0, archName.find(':'));
|
||||
|
||||
// strip leading 'gfx' while copying into our buffer
|
||||
if (archLen > 3) {
|
||||
strcpy(archName, &devName[3]);
|
||||
archLen -= 3;
|
||||
}
|
||||
const auto hex = [](const std::string & s) { return (int) strtoul(s.c_str(), nullptr, 16); };
|
||||
|
||||
// trim trailing :xnack- or :sramecc- statuses
|
||||
archLen = strcspn(archName, ":");
|
||||
archName[archLen] = '\0';
|
||||
int cc = 0;
|
||||
|
||||
// tease out the version information
|
||||
if (archLen > 8) {
|
||||
if (archName.size() > 8) {
|
||||
// versions labeled generic use '-' as delimiter
|
||||
// strip the trailing "-generic" then iterate through what remains
|
||||
if ((strstr(archName, "-generic"))) {
|
||||
archName[archLen - 8] = '\0';
|
||||
char * pch;
|
||||
if ((pch = strtok(archName, "-"))) {
|
||||
archMajor = (int)strtoul(pch, 0, 16);
|
||||
if ((pch = strtok(NULL, "-"))) {
|
||||
archMinor = 0x10 * (int)strtoul(pch, 0, 16);
|
||||
}
|
||||
if (archName.compare(archName.size() - 8, 8, "-generic") == 0) {
|
||||
archName.resize(archName.size() - 8);
|
||||
const size_t sep = archName.find('-');
|
||||
cc = 0x100 * hex(archName.substr(0, sep));
|
||||
if (sep != std::string::npos) {
|
||||
cc += 0x10 * hex(archName.substr(sep + 1));
|
||||
}
|
||||
}
|
||||
} else if (archLen >= 3) {
|
||||
// last two digits should be the minor * 0x10 + stepping
|
||||
archMinor = (int)strtoul(&archName[archLen - 2], 0, 16);
|
||||
archName[archLen - 2] = '\0';
|
||||
|
||||
// only the major version remains
|
||||
archMajor = (int)strtoul(archName, 0, 16);
|
||||
} else if (archName.size() >= 3) {
|
||||
// ordinary IDs are the version in hex
|
||||
cc = hex(archName);
|
||||
}
|
||||
archNum += archMajor * 0x100;
|
||||
archNum += archMinor;
|
||||
|
||||
return archNum;
|
||||
return GGML_CUDA_CC_OFFSET_AMD + cc;
|
||||
}
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
|
||||
@@ -496,7 +480,7 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
int n_captured = 0;
|
||||
for (const auto & [key, graph] : ctx.cuda_graphs) {
|
||||
n_captured += graph->instance != nullptr;
|
||||
n_captured += graph->instances.size();
|
||||
}
|
||||
if (n_captured > 0) {
|
||||
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: memory freed, %d captured CUDA graphs must be captured again\n", device, n_captured);
|
||||
@@ -1973,6 +1957,67 @@ static bool ggml_cuda_match_shared_expert(const ggml_cgraph * graph, int routed_
|
||||
return true;
|
||||
}
|
||||
|
||||
// Buffer- and batch-size independent, so graph_optimize produces the same graph topology for every ubatch size (see #29986).
|
||||
static bool ggml_cuda_mul_mat_q_fusion_matches(const ggml_tensor * up, const ggml_tensor * gate, const ggml_tensor * glu, const int device) {
|
||||
const ggml_tensor * x_up = up->src[0];
|
||||
const ggml_tensor * y = up->src[1];
|
||||
|
||||
if ((up->op != GGML_OP_MUL_MAT && up->op != GGML_OP_MUL_MAT_ID) || !ggml_cuda_mmq_fusion_supported(x_up->type) || y->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The fused kernel is only compiled for weights whose rows fill whole tiles (no fallback).
|
||||
if (ggml_cuda_mmq_needs_fallback(x_up->ne[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ggml_cuda_should_fuse_mul_mat(up, gate, glu)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The fused write-back applies SWIGLU_OAI with the default alpha and limit of ggml_cuda_op_swiglu_oai_single.
|
||||
if (ggml_get_glu_op(glu) == GGML_GLU_OP_SWIGLU_OAI && (ggml_get_op_params_f32(glu, 2) != 1.702f || ggml_get_op_params_f32(glu, 3) != 7.0f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NVIDIA only: the fused write-back relies on the MMA accumulator layout.
|
||||
return turing_mma_available(ggml_cuda_info().devices[device].cc);
|
||||
}
|
||||
|
||||
static bool ggml_cuda_should_fuse_mul_mat_q(const ggml_tensor * up, const ggml_tensor * gate, const ggml_tensor * glu) {
|
||||
// A hinted matmul can be fused, but its unfused path may be faster (e.g. FWHT for GGML_HINT_SRC0_IS_HADAMARD).
|
||||
if (ggml_get_op_params_i32(up, 1) != GGML_HINT_NONE || ggml_get_op_params_i32(gate, 1) != GGML_HINT_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The padding of a compute-buffer weight that is a view cannot be cleared, see ggml_cuda_mul_mat.
|
||||
const auto bad_padding_clear = [](const ggml_tensor * x) {
|
||||
return ggml_backend_buffer_get_usage(x->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE &&
|
||||
ggml_nbytes(x) != ggml_backend_buffer_get_alloc_size(x->buffer, x) && x->view_src;
|
||||
};
|
||||
const int device = ggml_cuda_get_device();
|
||||
if (!ggml_cuda_mul_mat_q_fusion_matches(up, gate, glu, device) || bad_padding_clear(up->src[0]) || bad_padding_clear(gate->src[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The batch size checks are done here, not in ggml_cuda_mul_mat_q_fusion_matches.
|
||||
const ggml_tensor * x_up = up->src[0];
|
||||
const ggml_tensor * y = up->src[1];
|
||||
const int cc = ggml_cuda_info().devices[device].cc;
|
||||
|
||||
// For MUL_MAT_ID: same kernel choice as ggml_cuda_mul_mat_id, with the nominal tiles of all experts (the per-expert token counts are only known on the GPU).
|
||||
const bool is_id = up->op == GGML_OP_MUL_MAT_ID;
|
||||
const int64_t ncols = is_id ? y->ne[2] : y->ne[1];
|
||||
const int64_t nchannels = is_id ? x_up->ne[2] : y->ne[2]*y->ne[3];
|
||||
const bool use_mmvq = is_id ? ncols <= get_mmvq_mmid_max_batch(x_up->type, cc) : ggml_cuda_should_use_mmvq(x_up->type, cc, ncols);
|
||||
if (use_mmvq || !ggml_cuda_should_use_mmq(x_up->type, cc, ncols, /*n_experts =*/ is_id ? x_up->ne[2] : 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The fused kernel needs one block per tile (no stream-k split), so only fuse if the tiling is efficient.
|
||||
return ggml_cuda_mmq_fusion_is_efficient(x_up->type, x_up->ne[1], ncols, nchannels, device);
|
||||
}
|
||||
|
||||
static void ggml_cuda_mul_mat(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
@@ -2803,16 +2848,14 @@ static bool ggml_cuda_graph_update_required(ggml_backend_cuda_context * cuda_ctx
|
||||
return res;
|
||||
}
|
||||
|
||||
static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
|
||||
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
|
||||
|
||||
static void ggml_cuda_graph_update_executable(cudaGraphExec_t & instance, cudaGraph_t cuda_graph) {
|
||||
#if CUDART_VERSION >= 12000
|
||||
cudaGraphExecUpdateResultInfo result_info;
|
||||
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &result_info);
|
||||
cudaError_t stat = cudaGraphExecUpdate(instance, cuda_graph, &result_info);
|
||||
#else
|
||||
cudaGraphNode_t errorNode;
|
||||
cudaGraphExecUpdateResult result_info;
|
||||
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &errorNode, &result_info);
|
||||
cudaError_t stat = cudaGraphExecUpdate(instance, cuda_graph, &errorNode, &result_info);
|
||||
#endif // CUDART_VERSION >= 12000
|
||||
|
||||
if (stat == cudaErrorGraphExecUpdateFailure) {
|
||||
@@ -2823,13 +2866,39 @@ static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_c
|
||||
// The pre-existing graph exec cannot be updated due to violated constraints
|
||||
// so instead clear error and re-instantiate
|
||||
(void)cudaGetLastError();
|
||||
CUDA_CHECK(cudaGraphExecDestroy(graph->instance));
|
||||
graph->instance = nullptr;
|
||||
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
|
||||
CUDA_CHECK(cudaGraphExecDestroy(instance));
|
||||
instance = nullptr;
|
||||
CUDA_CHECK(cudaGraphInstantiate(&instance, cuda_graph, nullptr, nullptr, 0));
|
||||
} else {
|
||||
GGML_ASSERT(stat == cudaSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
// number of ggml nodes per CUDA graph chunk, 0 captures the whole ggml graph as one CUDA graph
|
||||
static int ggml_cuda_graph_chunk_size() {
|
||||
static const int chunk_size = [] {
|
||||
const char * env = getenv("GGML_CUDA_GRAPH_CHUNK_SIZE");
|
||||
return env != nullptr ? atoi(env) : 512;
|
||||
}();
|
||||
return chunk_size;
|
||||
}
|
||||
|
||||
// end the capture of chunk `idx`, update or create its executable and launch it
|
||||
static void ggml_cuda_graph_launch_chunk(ggml_backend_cuda_context * cuda_ctx, ggml_cuda_graph * graph, size_t idx) {
|
||||
cudaGraph_t cuda_graph = nullptr;
|
||||
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &cuda_graph));
|
||||
|
||||
if (idx < graph->instances.size()) {
|
||||
ggml_cuda_graph_update_executable(graph->instances[idx], cuda_graph);
|
||||
} else {
|
||||
GGML_ASSERT(idx == graph->instances.size());
|
||||
graph->instances.emplace_back(nullptr);
|
||||
CUDA_CHECK(cudaGraphInstantiate(&graph->instances[idx], cuda_graph, nullptr, nullptr, 0));
|
||||
}
|
||||
CUDA_CHECK(cudaGraphDestroy(cuda_graph));
|
||||
|
||||
CUDA_CHECK(cudaGraphLaunch(graph->instances[idx], cuda_ctx->stream()));
|
||||
}
|
||||
#endif // USE_CUDA_GRAPH
|
||||
|
||||
static bool ggml_cuda_should_fuse_rope_set_rows(const ggml_tensor * rope,
|
||||
@@ -2857,9 +2926,10 @@ static bool ggml_cuda_should_fuse_rope_set_rows(const ggml_tensor * rope,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only norm/neox shaders have the fusion code
|
||||
// Only norm/neox/multi shaders have the fusion code
|
||||
const int mode = ((const int32_t *) rope->op_params)[2];
|
||||
if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX) {
|
||||
if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX &&
|
||||
mode != GGML_ROPE_TYPE_MROPE && mode != GGML_ROPE_TYPE_IMROPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3758,6 +3828,11 @@ static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph,
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: make the fused softcap kernel stride aware, it reads src and writes dst as dense
|
||||
if (!ggml_is_contiguous(scale->src[0]) || !ggml_is_contiguous(scale2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ggml_get_unary_op(tanh) != GGML_UNARY_OP_TANH) {
|
||||
return false;
|
||||
}
|
||||
@@ -4353,6 +4428,18 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
|
||||
fused_node_count = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ggml_cuda_should_fuse_mul_mat_q(up, gate, glu)) {
|
||||
ggml_cuda_mm_fusion_args_host fusion_data{};
|
||||
fusion_data.gate = gate->src[0];
|
||||
fusion_data.glu_op = ggml_get_glu_op(glu);
|
||||
fusion_data.glu_limit = ggml_get_op_params_f32(glu, 3);
|
||||
|
||||
ggml_cuda_mul_mat_q(*cuda_ctx, src0, src1, ids, glu, &fusion_data);
|
||||
fused_mul_mat_vec = true;
|
||||
fused_node_count = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4583,6 +4670,13 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
|
||||
ggml_cuda_concurrent_event * concurrent_event = nullptr;
|
||||
bool should_launch_concurrent_events = false;
|
||||
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
ggml_cuda_graph * graph = use_cuda_graph ? cuda_ctx->cuda_graph(graph_key) : nullptr;
|
||||
const int chunk_size = ggml_cuda_graph_chunk_size();
|
||||
int chunk_end = chunk_size;
|
||||
size_t n_chunks = 0;
|
||||
#endif // USE_CUDA_GRAPH
|
||||
|
||||
const auto try_launch_concurrent_event = [&](const ggml_tensor * node) {
|
||||
if (stream_ctx.concurrent_events.find(node) != stream_ctx.concurrent_events.end()) {
|
||||
concurrent_event = &stream_ctx.concurrent_events[node];
|
||||
@@ -4672,6 +4766,15 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
|
||||
|
||||
for (int i = 0; i < cgraph->n_nodes; i++) {
|
||||
ggml_tensor * node = cgraph->nodes[i];
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
// fused nodes are skipped together, so a cut never splits a fusion
|
||||
// a concurrent region must join before its capture can end
|
||||
if (use_cuda_graph && chunk_size > 0 && i >= chunk_end && !is_concurrent_event_active) {
|
||||
ggml_cuda_graph_launch_chunk(cuda_ctx, graph, n_chunks++);
|
||||
CUDA_CHECK(cudaStreamBeginCapture(cuda_ctx->stream(), cudaStreamCaptureModeRelaxed));
|
||||
chunk_end = i + chunk_size;
|
||||
}
|
||||
#endif // USE_CUDA_GRAPH
|
||||
if (is_concurrent_event_active) {
|
||||
GGML_ASSERT(concurrent_event);
|
||||
|
||||
@@ -4754,14 +4857,14 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
|
||||
if (use_cuda_graph && cuda_graph_update_required) { // End CUDA graph capture
|
||||
if (graph->graph != nullptr) {
|
||||
CUDA_CHECK(cudaGraphDestroy(graph->graph));
|
||||
graph->graph = nullptr;
|
||||
}
|
||||
ggml_cuda_graph_launch_chunk(cuda_ctx, graph, n_chunks++);
|
||||
|
||||
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &graph->graph));
|
||||
// drop the chunks left over from a longer graph
|
||||
for (size_t i = n_chunks; i < graph->instances.size(); ++i) {
|
||||
CUDA_CHECK(cudaGraphExecDestroy(graph->instances[i]));
|
||||
}
|
||||
graph->instances.resize(n_chunks);
|
||||
graph_evaluated_or_captured = true; // CUDA graph has been captured
|
||||
|
||||
std::lock_guard<std::mutex> lock(ggml_cuda_lock);
|
||||
@@ -4773,16 +4876,11 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
|
||||
}
|
||||
}
|
||||
|
||||
if (use_cuda_graph) {
|
||||
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
|
||||
if (graph->instance == nullptr) { // Create executable graph from captured graph.
|
||||
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
|
||||
if (use_cuda_graph && !cuda_graph_update_required) {
|
||||
// the chunks were launched during capture, otherwise launch them here
|
||||
for (cudaGraphExec_t instance : graph->instances) {
|
||||
CUDA_CHECK(cudaGraphLaunch(instance, cuda_ctx->stream()));
|
||||
}
|
||||
if (cuda_graph_update_required) { // Update graph executable
|
||||
ggml_cuda_graph_update_executable(cuda_ctx, graph_key);
|
||||
}
|
||||
// Launch graph
|
||||
CUDA_CHECK(cudaGraphLaunch(graph->instance, cuda_ctx->stream()));
|
||||
#else
|
||||
GGML_UNUSED(graph_key);
|
||||
graph_evaluated_or_captured = true;
|
||||
@@ -4794,7 +4892,7 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
|
||||
static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
|
||||
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
|
||||
|
||||
if (graph->graph == nullptr) {
|
||||
if (graph->instances.empty()) {
|
||||
if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_VOLTA) {
|
||||
if (!graph->disable_due_to_gpu_arch && ggml_cuda_graph_debug_enabled()) {
|
||||
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__);
|
||||
@@ -4848,7 +4946,7 @@ static enum ggml_status ggml_backend_cuda_graph_compute(ggml_backend_t backend,
|
||||
}
|
||||
} else {
|
||||
use_cuda_graph = true;
|
||||
cuda_graph_update_required = graph->instance == nullptr;
|
||||
cuda_graph_update_required = graph->instances.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4938,6 +5036,19 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < cgraph->n_nodes; ++i) {
|
||||
// Keep the inputs of the fused gate/up/GLU MMQ kernel alive until the GLU so that they cannot alias its output (under op offload the weights are buffer copies).
|
||||
if (i + 2 < cgraph->n_nodes && cgraph->nodes[i + 2]->op == GGML_OP_GLU) {
|
||||
ggml_tensor * glu = cgraph->nodes[i + 2];
|
||||
ggml_tensor * gate = glu->src[0];
|
||||
ggml_tensor * up = glu->src[1];
|
||||
const bool ok = (gate == cgraph->nodes[i] && up == cgraph->nodes[i + 1]) || (gate == cgraph->nodes[i + 1] && up == cgraph->nodes[i]);
|
||||
if (ok && ggml_cuda_mul_mat_q_fusion_matches(up, gate, glu, cuda_ctx->device)) {
|
||||
add_alloc_deps(i, i + 2);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ggml_cuda_moe_weighted_reduction_match match;
|
||||
if (ggml_cuda_match_moe_weighted_reduction(cgraph, i, match)) {
|
||||
params->add_alloc_dep(params->user_data, const_cast<ggml_tensor *>(match.experts), match.dst);
|
||||
@@ -5872,7 +5983,8 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
case GGML_OP_LOG:
|
||||
return true;
|
||||
case GGML_OP_SCALE:
|
||||
return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_BF16) && op->type == op->src[0]->type;
|
||||
return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_BF16) && op->type == op->src[0]->type &&
|
||||
ggml_is_contiguous_rows(op->src[0]);
|
||||
case GGML_OP_CLAMP:
|
||||
return ggml_is_contiguous_rows(op->src[0]);
|
||||
case GGML_OP_ADD:
|
||||
|
||||
@@ -1409,14 +1409,14 @@ namespace ggml_cuda_mma {
|
||||
tile<16, 16, float, dl_d> & D, const tile<16, 8, nv_bfloat162, dl_ab> & A, const tile<16, 8, nv_bfloat162, dl_ab> & B) {
|
||||
#if defined(AMD_WMMA_AVAILABLE)
|
||||
#if defined(RDNA4)
|
||||
using bf16x8_t = __attribute__((ext_vector_type(8))) __bf16;
|
||||
using bf16x8_t = __attribute__((ext_vector_type(8))) short;
|
||||
using floatx8_t = __attribute__((ext_vector_type(8))) float;
|
||||
floatx8_t& acc_frag = reinterpret_cast<floatx8_t&>(D.x[0]);
|
||||
const bf16x8_t& a_frag = reinterpret_cast<const bf16x8_t&>(A.x[0]);
|
||||
const bf16x8_t& b_frag = reinterpret_cast<const bf16x8_t&>(B.x[0]);
|
||||
acc_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12(a_frag, b_frag, acc_frag);
|
||||
#elif defined(RDNA3)
|
||||
using bf16x16_t = __attribute__((ext_vector_type(16))) __bf16;
|
||||
using bf16x16_t = __attribute__((ext_vector_type(16))) short;
|
||||
using floatx8_t = __attribute__((ext_vector_type(8))) float;
|
||||
floatx8_t& acc_frag = reinterpret_cast<floatx8_t&>(D.x[0]);
|
||||
const bf16x16_t& a_frag = reinterpret_cast<const bf16x16_t&>(A.x[0]);
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "mmq.cuh"
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q1_0(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -60,10 +62,10 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
const int v0 = ggml_hip_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = ggml_hip_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = ggml_hip_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = ggml_hip_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
@@ -116,7 +118,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q2_0(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -162,8 +166,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const uint32_t qx_indices = (q & 0x03) | ((q & 0x0C) << 6) | ((q & 0x30) << 12) | ((q & 0xC0) << 18);
|
||||
const uint32_t qy_bits = q >> 8;
|
||||
const uint32_t qy_indices = (qy_bits & 0x03) | ((qy_bits & 0x0C) << 6) | ((qy_bits & 0x30) << 12) | ((qy_bits & 0xC0) << 18);
|
||||
const int qx = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
const int qx = ggml_hip_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = ggml_hip_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
#else
|
||||
// unpack even and odd crumbs into byte values
|
||||
const int qe = __byte_perm(0x020100FF, 0x020100FF, q >> 0);
|
||||
@@ -205,7 +209,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_0(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -268,7 +274,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_1(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -331,7 +339,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_0(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -411,7 +421,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_1(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -488,8 +500,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q8_0(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q8_0(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -519,7 +531,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q8_0 * bxi = (const block_q8_0 *) x + kbx0 + i*stride + kbx;
|
||||
const block_q8_0 * bxi = ggml_cuda_mmq_x_row<block_q8_0, has_fusion>(x, x_gate, kbx0, i, stride) + kbx;
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_qs[i*sram_stride + 0 + txi] = get_int_b2(bxi[0].qs, kqsx);
|
||||
@@ -542,7 +554,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q8_0 * bxi = (const block_q8_0 *) x + kbx0 + i*stride + kbxd;
|
||||
const block_q8_0 * bxi = ggml_cuda_mmq_x_row<block_q8_0, has_fusion>(x, x_gate, kbx0, i, stride) + kbxd;
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_df[i*sram_stride + kbxd] = bxi->d;
|
||||
@@ -555,7 +567,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q2_K(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -616,7 +630,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q3_K(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -728,8 +744,8 @@ static __device__ __forceinline__ int unpack_scales_q45_K(const int * scales, co
|
||||
((scales[ksc/2] >> (2 * (ksc % 2))) & 0x30303030); // upper 2 bits
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_K(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_K(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -757,7 +773,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
|
||||
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
const int qs0 = get_int_b4(bxi->qs, txi);
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
@@ -786,7 +802,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
|
||||
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const int * scales = (const int *) bxi->scales;
|
||||
const int ksc = threadIdx.x % 2;
|
||||
@@ -814,7 +830,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
|
||||
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
x_dm[i] = bxi->dm;
|
||||
}
|
||||
@@ -827,7 +843,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride + (threadIdx.x % (MMQ_TILE_NE_K/8)) / (QI4_K/8);
|
||||
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride) + (threadIdx.x % (MMQ_TILE_NE_K/8)) / (QI4_K/8);
|
||||
|
||||
const int * scales = (const int *) bxi->scales;
|
||||
|
||||
@@ -839,8 +855,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
#endif // defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_K(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_K(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -868,7 +884,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
|
||||
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
const int ky = QR5_K*txi;
|
||||
|
||||
const int ql = get_int_b4(bxi->qs, txi);
|
||||
@@ -909,7 +925,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
|
||||
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const int * scales = (const int *) bxi->scales;
|
||||
const int ksc = threadIdx.x % 2;
|
||||
@@ -937,7 +953,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
|
||||
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
x_dm[i] = bxi->dm;
|
||||
}
|
||||
@@ -951,7 +967,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
|
||||
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const int * scales = (const int *) bxi->scales;
|
||||
|
||||
@@ -963,8 +979,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
#endif // defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q6_K(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q6_K(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -993,7 +1009,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride;
|
||||
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const int ql = get_int_b2(bxi->ql, txi);
|
||||
const int ql0 = (ql >> 0) & 0x0F0F0F0F;
|
||||
@@ -1023,7 +1039,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride;
|
||||
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_df[i*sram_stride] = bxi->d;
|
||||
@@ -1041,7 +1057,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride + (threadIdx.x % (MMQ_TILE_NE_K/8)) / 4;
|
||||
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride) + (threadIdx.x % (MMQ_TILE_NE_K/8)) / 4;
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_sc[i*sram_stride + threadIdx.x%4] = get_int_b2(bxi->scales, threadIdx.x % (MMQ_TILE_NE_K/8));
|
||||
@@ -1054,7 +1070,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq1_s(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1116,7 +1134,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_xxs(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1180,7 +1200,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_xs(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1245,7 +1267,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_s(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1313,7 +1337,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq3_xxs(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1377,7 +1403,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq3_s(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1445,8 +1473,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_xs(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_xs(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1473,7 +1501,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_iq4_xs * bxi = (const block_iq4_xs *) x + kbx0 + i*stride;
|
||||
const block_iq4_xs * bxi = ggml_cuda_mmq_x_row<block_iq4_xs, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const int aux_q4 = get_int_b4(bxi->qs, kqsx);
|
||||
const int2 v = get_int_from_table_16(aux_q4, kvalues_iq4nl);
|
||||
@@ -1497,7 +1525,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
i = min(i, i_max);
|
||||
}
|
||||
|
||||
const block_iq4_xs * bxi = (const block_iq4_xs *) x + kbx0 + i*stride;
|
||||
const block_iq4_xs * bxi = ggml_cuda_mmq_x_row<block_iq4_xs, has_fusion>(x, x_gate, kbx0, i, stride);
|
||||
|
||||
const float d = __half2float(bxi->d);
|
||||
|
||||
@@ -1513,7 +1541,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_nl(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1582,7 +1612,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_mxfp4(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
|
||||
@@ -1649,7 +1681,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_mxfp4_fp4(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q4) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q4);
|
||||
@@ -1691,7 +1725,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_nvfp4(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kb0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kb0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
|
||||
@@ -1747,7 +1783,9 @@ template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __de
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_nvfp4_nvfp4(
|
||||
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
|
||||
GGML_UNUSED(x_gate);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q4) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q4);
|
||||
|
||||
+78
-32
@@ -131,12 +131,54 @@ static ggml_prec ggml_cuda_mmq_get_prec_src1(const ggml_tensor * src0, const ggm
|
||||
return GGML_PREC_Q4;
|
||||
}
|
||||
|
||||
// Tile width in ne11 (dense) / ne12 (MoE) direction with the fewest tiles that fits into shared memory, 0 if there is none.
|
||||
static int ggml_cuda_mmq_get_J_best(
|
||||
const ggml_type type, const int64_t ncols, const bool fallback, const int cc, const size_t smpbo, const ggml_prec prec_src1,
|
||||
int * nthreads_best = nullptr) {
|
||||
int J_best = 0;
|
||||
int ntiles_J_best = INT_MAX;
|
||||
|
||||
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
|
||||
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(type, J, fallback, cc, prec_src1);
|
||||
if (config.type == GGML_TYPE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ntiles_x = (ncols + config.J - 1) / config.J;
|
||||
|
||||
if (ntiles_x < ntiles_J_best) {
|
||||
J_best = J;
|
||||
if (nthreads_best) {
|
||||
*nthreads_best = config.nthreads;
|
||||
}
|
||||
ntiles_J_best = ntiles_x;
|
||||
}
|
||||
}
|
||||
return J_best;
|
||||
}
|
||||
|
||||
void ggml_cuda_mul_mat_q(
|
||||
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst) {
|
||||
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst,
|
||||
const ggml_cuda_mm_fusion_args_host * fusion) {
|
||||
GGML_ASSERT( src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT( dst->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(!ids || ids->type == GGML_TYPE_I32); // Optional, used for batched GGML_MUL_MAT_ID.
|
||||
|
||||
// Fused gate/up/GLU: src0 is the up weight, dst the GLU output.
|
||||
const ggml_tensor * gate = fusion ? fusion->gate : nullptr;
|
||||
ggml_cuda_mm_fusion_args_device fusion_device = {};
|
||||
if (fusion) {
|
||||
GGML_ASSERT(gate && !fusion->x_bias && !fusion->gate_bias && !fusion->x_scale && !fusion->gate_scale);
|
||||
GGML_ASSERT(gate->type == src0->type && ggml_are_same_shape(gate, src0) && ggml_are_same_stride(gate, src0));
|
||||
fusion_device.gate = gate->data;
|
||||
fusion_device.glu_op = fusion->glu_op;
|
||||
fusion_device.glu_limit = fusion->glu_limit;
|
||||
}
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS;
|
||||
|
||||
cudaStream_t stream = ctx.stream();
|
||||
@@ -158,14 +200,16 @@ void ggml_cuda_mul_mat_q(
|
||||
const float * src1_d = (const float *) src1->data;
|
||||
float * dst_d = (float *) dst->data;
|
||||
|
||||
// If src0 is a temporary compute buffer, clear any potential padding.
|
||||
if (ggml_backend_buffer_get_usage(src0->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
|
||||
const size_t size_data = ggml_nbytes(src0);
|
||||
const size_t size_alloc = ggml_backend_buffer_get_alloc_size(src0->buffer, src0);
|
||||
if (size_alloc > size_data) {
|
||||
GGML_ASSERT(ggml_is_contiguously_allocated(src0));
|
||||
GGML_ASSERT(!src0->view_src);
|
||||
CUDA_CHECK(cudaMemsetAsync((char *) src0->data + size_data, 0, size_alloc - size_data, stream));
|
||||
// If src0 (or the fused gate weight) is a temporary compute buffer, clear any potential padding.
|
||||
for (const ggml_tensor * t : { src0, gate }) {
|
||||
if (t && ggml_backend_buffer_get_usage(t->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
|
||||
const size_t size_data = ggml_nbytes(t);
|
||||
const size_t size_alloc = ggml_backend_buffer_get_alloc_size(t->buffer, t);
|
||||
if (size_alloc > size_data) {
|
||||
GGML_ASSERT(ggml_is_contiguously_allocated(t));
|
||||
GGML_ASSERT(!t->view_src);
|
||||
CUDA_CHECK(cudaMemsetAsync((char *) t->data + size_data, 0, size_alloc - size_data, stream));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +224,9 @@ void ggml_cuda_mul_mat_q(
|
||||
|
||||
const bool fallback = ggml_cuda_mmq_needs_fallback(ne01);
|
||||
|
||||
const ggml_prec prec_src1 = ggml_cuda_mmq_get_prec_src1(src0, dst, cc);
|
||||
// With fusion dst is the GLU output, not a MUL_MAT; the fused types always quantize src1 to Q8_1.
|
||||
// TODO support for Q4 precision if both tensors allow it
|
||||
const ggml_prec prec_src1 = gate ? GGML_PREC_Q8 : ggml_cuda_mmq_get_prec_src1(src0, dst, cc);
|
||||
|
||||
const bool use_native_fp4 = prec_src1 == GGML_PREC_Q4;
|
||||
const size_t y_block_size = use_native_fp4 ? sizeof(block_fp4_mmq) : sizeof(block_q8_1_mmq);
|
||||
@@ -201,26 +247,7 @@ void ggml_cuda_mul_mat_q(
|
||||
}
|
||||
}
|
||||
|
||||
int ntiles_J_best = INT_MAX;
|
||||
|
||||
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
|
||||
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(src0->type, J, fallback, cc, prec_src1);
|
||||
if (config.type == GGML_TYPE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int ntiles_x = (ncols_opt + config.J - 1) / config.J;
|
||||
|
||||
if (ntiles_x < ntiles_J_best) {
|
||||
J_best = J;
|
||||
nthreads_best = config.nthreads;
|
||||
ntiles_J_best = ntiles_x;
|
||||
}
|
||||
}
|
||||
J_best = ggml_cuda_mmq_get_J_best(src0->type, ncols_opt, fallback, cc, smpbo, prec_src1, &nthreads_best);
|
||||
}
|
||||
GGML_ASSERT(J_best > 0);
|
||||
|
||||
@@ -268,7 +295,7 @@ void ggml_cuda_mul_mat_q(
|
||||
ne00, ne01, ne1, s01, ne11, s1,
|
||||
ne02, ne12, s02, s12, s2,
|
||||
ne03, ne13, s03, s13, s3,
|
||||
ne1, J_best};
|
||||
ne1, J_best, fusion_device};
|
||||
ggml_cuda_mul_mat_q_switch_type(ctx, args, stream, prec_src1);
|
||||
return;
|
||||
}
|
||||
@@ -347,7 +374,7 @@ void ggml_cuda_mul_mat_q(
|
||||
ne00, ne01, ne_get_rows, s01, ne_get_rows, s1,
|
||||
ne02, ne02, s02, s12, s2,
|
||||
ne03, ne13, s03, s13, s3,
|
||||
ne12, J_best};
|
||||
ne12, J_best, fusion_device};
|
||||
|
||||
ggml_cuda_mul_mat_q_switch_type(ctx, args, stream, prec_src1);
|
||||
}
|
||||
@@ -485,3 +512,22 @@ bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t
|
||||
|
||||
return (!GGML_CUDA_CC_IS_CDNA(cc)) || ne11 < MMQ_DP4A_MAX_BATCH_SIZE;
|
||||
}
|
||||
|
||||
bool ggml_cuda_mmq_fusion_is_efficient(const ggml_type type, const int64_t nrows_x, const int64_t ncols, const int64_t nchannels, const int device) {
|
||||
const int cc = ggml_cuda_info().devices[device].cc;
|
||||
const int nsm = ggml_cuda_info().devices[device].nsm;
|
||||
const size_t smpbo = ggml_cuda_info().devices[device].smpbo;
|
||||
|
||||
// Same tile width as ggml_cuda_mul_mat_q; the fused kernel has no fallback, quantizes src1 to Q8_1 and writes I/2 output rows per tile.
|
||||
const bool fallback = false;
|
||||
const int J_best = ggml_cuda_mmq_get_J_best(type, ncols, fallback, cc, smpbo, GGML_PREC_Q8);
|
||||
if (J_best == 0) {
|
||||
return false;
|
||||
}
|
||||
const int64_t nrows_tile = ggml_cuda_mmq_get_config(type, J_best, fallback, cc, GGML_PREC_Q8).I / 2;
|
||||
const int64_t ntiles = (ncols + J_best - 1)/J_best * ((nrows_x + nrows_tile - 1)/nrows_tile) * nchannels;
|
||||
// Same condition as launch_mul_mat_q uses to run the stream-k kernel with one block per tile.
|
||||
const int64_t tiles_nwaves = (ntiles + nsm - 1) / nsm;
|
||||
const int64_t tiles_efficiency_percent = 100 * ntiles / (nsm*tiles_nwaves);
|
||||
return tiles_efficiency_percent >= 90;
|
||||
}
|
||||
|
||||
+178
-106
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.cuh"
|
||||
#include "unary.cuh"
|
||||
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
@@ -10,10 +11,12 @@
|
||||
#define MMQ_ITER_K_FP4 512
|
||||
#define MMQ_NWARPS 8
|
||||
|
||||
typedef void (*ggml_cuda_mmq_load_tiles_t)(const char * __restrict__ x, int * x_tile, const int kbx0, const int i_max, const int stride);
|
||||
typedef void (*ggml_cuda_mmq_load_tiles_t)(const char * __restrict__ x, const char * __restrict__ x_gate, int * x_tile,
|
||||
const int kbx0, const int i_max, const int stride);
|
||||
typedef void (*ggml_cuda_mmq_vec_dot_t)(const int * __restrict__ x, const int * __restrict__ y, float * __restrict__ sum, const int k00);
|
||||
typedef void (*ggml_cuda_mmq_write_back_t)(const float * __restrict__ sum, const int32_t * __restrict__ get_rows_to_sorted,
|
||||
float * __restrict__ dst, const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max);
|
||||
float * __restrict__ dst, const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
|
||||
const ggml_glu_op glu_op, const float glu_limit);
|
||||
|
||||
enum mmq_q8_1_ds_layout {
|
||||
MMQ_Q8_1_DS_LAYOUT_D4,
|
||||
@@ -426,15 +429,41 @@ static __host__ int ggml_cuda_mmq_get_nbytes_shared_x(const ggml_cuda_mmq_config
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
// Weight types with a fused gate/up/GLU path (see ggml_cuda_mmq_get_util_funcs).
|
||||
static constexpr __host__ __device__ bool ggml_cuda_mmq_fusion_supported(const ggml_type type) {
|
||||
return type == GGML_TYPE_Q4_K || type == GGML_TYPE_Q5_K || type == GGML_TYPE_Q6_K || type == GGML_TYPE_Q8_0 || type == GGML_TYPE_IQ4_XS;
|
||||
}
|
||||
|
||||
// Fused gate/up/GLU: the weight tile alternates chunks of this many up and gate rows, so rows r and r + 8 of an MMA tile (same thread) pair up.
|
||||
static constexpr int GGML_CUDA_MMQ_FUSION_ROWS = 8;
|
||||
|
||||
// Source of weight tile row i; with has_fusion the up or gate row that the interleaved tile holds there.
|
||||
template <typename block_t, bool has_fusion>
|
||||
static __device__ __forceinline__ const block_t * ggml_cuda_mmq_x_row(
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, const int kbx0, const int i, const int stride) {
|
||||
if constexpr (has_fusion) {
|
||||
constexpr int R = GGML_CUDA_MMQ_FUSION_ROWS;
|
||||
const char * x_src = (i / R) % 2 == 0 ? x : x_gate;
|
||||
return (const block_t *) x_src + kbx0 + ((i / (2*R))*R + i % R)*stride;
|
||||
} else {
|
||||
GGML_UNUSED(x_gate);
|
||||
return (const block_t *) x + kbx0 + i*stride;
|
||||
}
|
||||
}
|
||||
|
||||
#include "mmq-load-tiles.cuh"
|
||||
#include "mmq-vec-dot.cuh"
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __device__ __forceinline__ void ggml_cuda_mmq_write_back_dp4a(
|
||||
const float * __restrict__ sum, const int32_t * __restrict__ ids_dst, float * __restrict__ dst,
|
||||
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max) {
|
||||
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
|
||||
const ggml_glu_op glu_op, const float glu_limit) {
|
||||
GGML_UNUSED_VARS(glu_op, glu_limit);
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
|
||||
static_assert(I % warp_size == 0, "DP4A MMQ tile height must be a multiple of the physical warp size");
|
||||
|
||||
const bool y_scale_used = y_scale != nullptr;
|
||||
|
||||
@@ -468,16 +497,19 @@ template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __de
|
||||
}
|
||||
}
|
||||
|
||||
template<ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
// has_fusion: accumulators l and l + ne/2 hold the up and gate values of one output element, the GLU of the two is written.
|
||||
template<ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
|
||||
const float * __restrict__ sum, const int * __restrict__ ids_dst, float * __restrict__ dst,
|
||||
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max) {
|
||||
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
|
||||
const ggml_glu_op glu_op, const float glu_limit) {
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
typedef tile<16, 16, int, DATA_LAYOUT_J_MAJOR> tile_C;
|
||||
#else
|
||||
typedef tile<16, 8, int> tile_C;
|
||||
#endif // defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
static_assert(!has_fusion || tile_C::I == 2*GGML_CUDA_MMQ_FUSION_ROWS, "each C tile must hold one chunk of up rows and one of gate rows");
|
||||
|
||||
constexpr int rows_per_warp = ggml_cuda_mmq_get_rows_per_warp(type, J, fallback, prec_src1);
|
||||
constexpr int ntx = rows_per_warp/tile_C::I; // Number of x minitiles per warp.
|
||||
@@ -491,20 +523,23 @@ static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
|
||||
#pragma unroll
|
||||
for (int n = 0; n < ntx; ++n) {
|
||||
#pragma unroll
|
||||
for (int l = 0; l < tile_C::ne; ++l) {
|
||||
for (int l = 0; l < (has_fusion ? tile_C::ne/2 : tile_C::ne); ++l) {
|
||||
const int j = j0 + (threadIdx.y % ntx) * tile_C::J + tile_C::get_j(l);
|
||||
|
||||
if (j > j_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int i = i0 + n*tile_C::I + tile_C::get_i(l);
|
||||
const int i = (i0 + n*tile_C::I)/(has_fusion ? 2 : 1) + tile_C::get_i(l);
|
||||
|
||||
if (fallback && i > i_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if constexpr (type == GGML_TYPE_NVFP4) {
|
||||
if constexpr (has_fusion) {
|
||||
const int sum_index = (j0/tile_C::J + n)*tile_C::ne + l;
|
||||
dst[ids_dst[j]*stride + i] = ggml_cuda_op_glu_single(glu_op, sum[sum_index + tile_C::ne/2], sum[sum_index], glu_limit);
|
||||
} else if constexpr (type == GGML_TYPE_NVFP4) {
|
||||
if (y_scale_used) {
|
||||
dst[ids_dst[j]*stride + i] = y_scale[j] * sum[(j0/tile_C::J + n)*tile_C::ne + l];
|
||||
} else {
|
||||
@@ -517,6 +552,8 @@ static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GGML_UNUSED_VARS(y_scale_used, glu_op, glu_limit);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -533,8 +570,9 @@ struct ggml_cuda_mmq_util_funcs {
|
||||
vdr(vdr), load_tiles(load_tiles), vec_dot(vec_dot), write_back(write_back) {}
|
||||
};
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_funcs() {
|
||||
static_assert(!has_fusion || ggml_cuda_mmq_fusion_supported(type), "no fused loader for this type");
|
||||
if (!ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).use_mma_data_layout()) {
|
||||
switch (type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
@@ -576,7 +614,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
case GGML_TYPE_Q8_0:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
VDR_Q8_0_Q8_1_MMQ,
|
||||
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback, false>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_dp4a<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -595,19 +633,19 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
case GGML_TYPE_Q4_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
VDR_Q4_K_Q8_1_MMQ,
|
||||
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback, false>,
|
||||
ggml_cuda_mmq_vec_dot_q4_K_q8_1_dp4a<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
VDR_Q5_K_Q8_1_MMQ,
|
||||
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback, false>,
|
||||
ggml_cuda_mmq_vec_dot_q5_K_q8_1_dp4a<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
|
||||
case GGML_TYPE_Q6_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
VDR_Q6_K_Q8_1_MMQ,
|
||||
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback, false>,
|
||||
ggml_cuda_mmq_vec_dot_q6_K_q8_1_dp4a<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -650,7 +688,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
case GGML_TYPE_IQ4_XS:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
VDR_IQ4_XS_Q8_1_MMQ,
|
||||
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback, false>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_dp4a<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
@@ -687,7 +725,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_mxfp4_fp4<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_fp4_fp4_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
}
|
||||
break;
|
||||
case GGML_TYPE_NVFP4:
|
||||
@@ -696,7 +734,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_nvfp4_nvfp4<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_fp4_fp4_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -712,136 +750,136 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q1_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q2_0:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q2_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q4_0:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q4_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_DS4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q4_1:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q4_1<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q5_0:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q5_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q5_1:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q5_1<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q8_0:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback, has_fusion>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
case GGML_TYPE_Q2_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q2_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q2_K_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q3_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q3_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_Q4_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback, has_fusion>,
|
||||
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback, has_fusion>,
|
||||
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
|
||||
case GGML_TYPE_Q6_K:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback, has_fusion>,
|
||||
ggml_cuda_mmq_vec_dot_q6_K_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
case GGML_TYPE_IQ1_S:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq1_s<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ2_XXS:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq2_xxs<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ2_XS:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq2_xs<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ2_S:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq2_s<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ3_XXS:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq3_xxs<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ3_S:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq3_s<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_IQ4_XS:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback>,
|
||||
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback, has_fusion>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_iq4_nl<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
case GGML_TYPE_MXFP4:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_mxfp4<type, J, fallback>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
case GGML_TYPE_NVFP4:
|
||||
return ggml_cuda_mmq_util_funcs(
|
||||
-1,
|
||||
ggml_cuda_mmq_load_tiles_nvfp4<type, J, fallback, prec_src1>,
|
||||
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
|
||||
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
|
||||
default:
|
||||
return ggml_cuda_mmq_util_funcs(1, nullptr, nullptr, nullptr);
|
||||
}
|
||||
@@ -849,41 +887,44 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
static constexpr __device__ int ggml_cuda_mmq_get_vdr() {
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().vdr;
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, false>().vdr;
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
static constexpr __device__ ggml_cuda_mmq_load_tiles_t ggml_cuda_mmq_get_load_tiles() {
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().load_tiles;
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, has_fusion>().load_tiles;
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
static constexpr __device__ ggml_cuda_mmq_vec_dot_t ggml_cuda_mmq_get_vec_dot() {
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().vec_dot;
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, false>().vec_dot;
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
static constexpr __device__ ggml_cuda_mmq_write_back_t ggml_cuda_mmq_get_write_back() {
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().write_back;
|
||||
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, has_fusion>().write_back;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
template <ggml_type type, int J, bool fallback, bool fixup, ggml_prec prec_src1>
|
||||
// has_fusion: x and x_gate are the up and gate weights.
|
||||
template <ggml_type type, int J, bool fallback, bool fixup, ggml_prec prec_src1, bool has_fusion>
|
||||
static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
const char * __restrict__ x, const int offset_x, const int * __restrict__ y,
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, const int offset_x, const int * __restrict__ y,
|
||||
const int * __restrict__ ids_dst, float * __restrict__ dst, float * __restrict__ tmp_fixup,
|
||||
const float * __restrict__ y_scale,
|
||||
const float * __restrict__ y_scale, const ggml_glu_op glu_op, const float glu_limit,
|
||||
const int stride_row_x, const int ncols_y, const int stride_col_dst,
|
||||
const int tile_x_max_i, const int tile_y_max_j, const int kb0_start, const int kb0_stop) {
|
||||
static_assert(!(fixup && has_fusion), "the fused gate/up path does not split K");
|
||||
static_assert(!(fallback && has_fusion), "the fused gate/up path needs nrows_x % 128 == 0");
|
||||
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
|
||||
constexpr int qk = ggml_cuda_type_traits<type>::qk;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
|
||||
constexpr ggml_cuda_mmq_load_tiles_t load_tiles = ggml_cuda_mmq_get_load_tiles<type, J, fallback, prec_src1>();
|
||||
constexpr ggml_cuda_mmq_load_tiles_t load_tiles = ggml_cuda_mmq_get_load_tiles<type, J, fallback, prec_src1, has_fusion>();
|
||||
constexpr ggml_cuda_mmq_vec_dot_t vec_dot = ggml_cuda_mmq_get_vec_dot<type, J, fallback, prec_src1>();
|
||||
constexpr ggml_cuda_mmq_write_back_t write_back = ggml_cuda_mmq_get_write_back<type, J, fallback, prec_src1>();
|
||||
constexpr ggml_cuda_mmq_write_back_t write_back = ggml_cuda_mmq_get_write_back<type, J, fallback, prec_src1, has_fusion>();
|
||||
|
||||
extern __shared__ int data_mul_mat_q[];
|
||||
int * tile_y = data_mul_mat_q + J;
|
||||
@@ -906,7 +947,7 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
constexpr int sz = sizeof(block_q8_1_mmq) / sizeof(int);
|
||||
|
||||
for (int kb0 = kb0_start; kb0 < kb0_stop; kb0 += blocks_per_iter) {
|
||||
load_tiles(x, tile_x, offset_x + kb0, tile_x_max_i, stride_row_x);
|
||||
load_tiles(x, x_gate, tile_x, offset_x + kb0, tile_x_max_i, stride_row_x);
|
||||
{
|
||||
const int * by0 = y + ncols_y * (kb0 * qk / ne_block) * sz;
|
||||
#pragma unroll
|
||||
@@ -941,28 +982,34 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
}
|
||||
|
||||
if (fixup) {
|
||||
write_back(sum, ids_dst, tmp_fixup + blockIdx.x*(J*I), y_scale, I, I, J);
|
||||
write_back(sum, ids_dst, tmp_fixup + blockIdx.x*(J*I), y_scale, I, I, J, glu_op, glu_limit);
|
||||
} else {
|
||||
write_back(sum, ids_dst, dst, y_scale, stride_col_dst, tile_x_max_i, tile_y_max_j);
|
||||
write_back(sum, ids_dst, dst, y_scale, stride_col_dst, tile_x_max_i, tile_y_max_j, glu_op, glu_limit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The mul_mat_q kernel implements "stream-k" work partitioning as described in https://arxiv.org/abs/2301.03598
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
__launch_bounds__(ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1), ggml_cuda_mmq_get_occupancy(type, J, fallback, prec_src1))
|
||||
static __global__ void mul_mat_q(
|
||||
const char * __restrict__ x, const int * __restrict__ y, const int32_t * __restrict__ ids_dst,
|
||||
const char * __restrict__ x, const char * __restrict__ x_gate, const int * __restrict__ y, const int32_t * __restrict__ ids_dst,
|
||||
const int32_t * __restrict__ expert_bounds, float * __restrict__ dst, float * __restrict__ tmp_fixup,
|
||||
const float * __restrict__ y_scale,
|
||||
const float * __restrict__ y_scale, const ggml_glu_op glu_op, const float glu_limit,
|
||||
const uint3 blocks_per_ne00, const int nrows_x, const int ncols_dst, const int stride_row_x, const int ncols_y, const int stride_col_dst,
|
||||
const uint3 channel_ratio, const uint3 nchannels_y, const int stride_channel_x, const int stride_channel_y, const int stride_channel_dst,
|
||||
const uint3 sample_ratio, const uint3 nsamples_y, const int stride_sample_x, const int stride_sample_y, const int stride_sample_dst,
|
||||
const uint3 ntx) {
|
||||
|
||||
#ifdef TURING_MMA_AVAILABLE
|
||||
constexpr bool fusion_available = true;
|
||||
#else
|
||||
constexpr bool fusion_available = false;
|
||||
#endif // TURING_MMA_AVAILABLE
|
||||
|
||||
// Skip unused template specializations for faster compilation:
|
||||
if (ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).type == GGML_TYPE_COUNT) {
|
||||
if (ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).type == GGML_TYPE_COUNT || (has_fusion && !fusion_available)) {
|
||||
NO_DEVICE_CODE;
|
||||
return;
|
||||
}
|
||||
@@ -990,12 +1037,16 @@ static __global__ void mul_mat_q(
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if constexpr (!ggml_cuda_mmq_get_stream_k(type, J, fallback, prec_src1)) {
|
||||
// The fused kernel needs the whole K range of a tile in one block, so it always uses tiling.
|
||||
if constexpr (has_fusion || !ggml_cuda_mmq_get_stream_k(type, J, fallback, prec_src1)) {
|
||||
// The fused kernel writes the GLU of I/2 up rows and I/2 gate rows per tile.
|
||||
constexpr int I_dst = has_fusion ? I/2 : I;
|
||||
|
||||
const uint2 tmp2 = fast_div_modulo(blockIdx.z, nchannels_y);
|
||||
const int wt = tmp2.x;
|
||||
const int zt = tmp2.y;
|
||||
const int jt = blockIdx.y;
|
||||
const int it = blockIdx.x;
|
||||
const int jt = blockIdx.x;
|
||||
const int it = blockIdx.y;
|
||||
|
||||
// Defaults for regular matrix multiplication:
|
||||
int col_low = 0;
|
||||
@@ -1040,22 +1091,22 @@ static __global__ void mul_mat_q(
|
||||
}
|
||||
|
||||
offset_y += (col_low + jt*J)*(sizeof(block_q8_1_mmq)/sizeof(int));
|
||||
offset_dst += it*I;
|
||||
offset_dst += it*I_dst;
|
||||
const float * y_scale_tile = nullptr;
|
||||
if constexpr (type == GGML_TYPE_NVFP4) {
|
||||
offset_y_scale += col_low + jt*J;
|
||||
y_scale_tile = y_scale ? y_scale + offset_y_scale : nullptr;
|
||||
}
|
||||
|
||||
const int tile_x_max_i = nrows_x - it*I - 1;
|
||||
const int tile_x_max_i = nrows_x - it*I_dst - 1;
|
||||
const int tile_y_max_j = col_diff - jt*J - 1;
|
||||
|
||||
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
|
||||
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I_dst*stride_row_x;
|
||||
|
||||
constexpr bool fixup = false;
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
|
||||
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
stride_row_x, ncols_y, stride_col_dst,
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, has_fusion>
|
||||
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
|
||||
tile_x_max_i, tile_y_max_j, 0, blocks_per_ne00.z);
|
||||
return;
|
||||
}
|
||||
@@ -1147,9 +1198,9 @@ static __global__ void mul_mat_q(
|
||||
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
|
||||
|
||||
constexpr bool fixup = false; // All but (potentially) the last iterations write their data to dst rather than the fixup buffer.
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
|
||||
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
stride_row_x, ncols_y, stride_col_dst,
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, false>
|
||||
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
|
||||
tile_x_max_i, tile_y_max_j, kb0_start, kb0_stop);
|
||||
|
||||
kbc += blocks_per_ne00.z;
|
||||
@@ -1231,9 +1282,9 @@ static __global__ void mul_mat_q(
|
||||
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
|
||||
|
||||
constexpr bool fixup = true; // Last index writes its data to fixup buffer to avoid data races with other blocks.
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
|
||||
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
stride_row_x, ncols_y, stride_col_dst,
|
||||
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, false>
|
||||
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
|
||||
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
|
||||
tile_x_max_i, tile_y_max_j, kb0_start, kb0_stop);
|
||||
}
|
||||
|
||||
@@ -1383,6 +1434,7 @@ struct mmq_args {
|
||||
int64_t nsamples_x; int64_t nsamples_y; int64_t stride_sample_x; int64_t stride_sample_y; int64_t stride_sample_dst;
|
||||
int64_t ncols_max;
|
||||
int J_best; // Tile width in ne11(dense)/ne12(MoE) direction to use for optimal performance.
|
||||
ggml_cuda_mm_fusion_args_device fusion = {}; // gate != nullptr selects the fused gate/up/GLU path
|
||||
};
|
||||
|
||||
static size_t mmq_get_nbytes_shared(const ggml_cuda_mmq_config & config, const int cc) {
|
||||
@@ -1392,7 +1444,7 @@ static size_t mmq_get_nbytes_shared(const ggml_cuda_mmq_config & config, const i
|
||||
return nbs_ids + nbs_x + GGML_PAD(nbs_y, config.nthreads*sizeof(int));
|
||||
}
|
||||
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
const int id = ggml_cuda_get_device();
|
||||
const int cc = ggml_cuda_info().devices[id].cc;
|
||||
@@ -1406,13 +1458,17 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
|
||||
const dim3 block_dims(warp_size, nwarps, 1);
|
||||
|
||||
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, false, prec_src1>), nbytes_shared);
|
||||
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, true, prec_src1>), nbytes_shared);
|
||||
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, /*fallback =*/ false, prec_src1, has_fusion>), nbytes_shared);
|
||||
if constexpr (!has_fusion) {
|
||||
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, /*fallback =*/ true, prec_src1, false>), nbytes_shared);
|
||||
}
|
||||
|
||||
const int nty = (args.nrows_x + config.I - 1) / config.I;
|
||||
const int I_dst = has_fusion ? config.I/2 : config.I; // Rows of dst per tile, see mul_mat_q.
|
||||
const int nty = (args.nrows_x + I_dst - 1) / I_dst;
|
||||
const int ntx = (args.ncols_max + config.J - 1) / config.J;
|
||||
const int ntzw = args.nchannels_y * args.nsamples_y;
|
||||
const dim3 block_nums_xy_tiling(nty, ntx, ntzw);
|
||||
// Column tiles innermost so that the concurrently running blocks share the same tile of x and read it from L2.
|
||||
const dim3 block_nums_xy_tiling(ntx, nty, ntzw);
|
||||
|
||||
GGML_ASSERT(args.nchannels_y % args.nchannels_x == 0);
|
||||
GGML_ASSERT(args.nsamples_y % args.nsamples_x == 0);
|
||||
@@ -1426,9 +1482,10 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
const uint3 channel_ratio_fd = init_fastdiv_values(channel_ratio);
|
||||
const uint3 sample_ratio_fd = init_fastdiv_values(sample_ratio);
|
||||
|
||||
if (!config.stream_k) {
|
||||
mul_mat_q<type, J, fallback, prec_src1><<<block_nums_xy_tiling, block_dims, nbytes_shared, stream>>>
|
||||
(args.x, args.y, args.ids_dst, args.expert_bounds, args.dst, nullptr, args.y_scale,
|
||||
if (has_fusion || !config.stream_k) {
|
||||
GGML_ASSERT(nty <= 65535); // gridDim.y limit
|
||||
mul_mat_q<type, J, fallback, prec_src1, has_fusion><<<block_nums_xy_tiling, block_dims, nbytes_shared, stream>>>
|
||||
(args.x, (const char *) args.fusion.gate, args.y, args.ids_dst, args.expert_bounds, args.dst, nullptr, args.y_scale, args.fusion.glu_op, args.fusion.glu_limit,
|
||||
blocks_per_ne00_fd, args.nrows_x, args.ncols_dst, args.stride_row_x, args.ncols_y, args.nrows_dst,
|
||||
channel_ratio_fd, nchannels_y_fd, args.stride_channel_x, args.stride_channel_y, args.stride_channel_dst,
|
||||
sample_ratio_fd, nsamples_y_fd, args.stride_sample_x, args.stride_sample_y, args.stride_sample_dst,
|
||||
@@ -1458,8 +1515,8 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
const dim3 block_nums_fixup(block_nums_stream_k.x, config.I/warp_size, 1);
|
||||
const dim3 block_dims_fixup(block_dims.x, block_dims.y/2, block_dims.z);
|
||||
|
||||
mul_mat_q<type, J, fallback, prec_src1><<<block_nums_stream_k, block_dims, nbytes_shared, stream>>>
|
||||
(args.x, args.y, args.ids_dst, args.expert_bounds, args.dst, tmp_fixup.ptr, args.y_scale,
|
||||
mul_mat_q<type, J, fallback, prec_src1, has_fusion><<<block_nums_stream_k, block_dims, nbytes_shared, stream>>>
|
||||
(args.x, (const char *) args.fusion.gate, args.y, args.ids_dst, args.expert_bounds, args.dst, tmp_fixup.ptr, args.y_scale, args.fusion.glu_op, args.fusion.glu_limit,
|
||||
blocks_per_ne00_fd, args.nrows_x, args.ncols_dst, args.stride_row_x, args.ncols_y, args.nrows_dst,
|
||||
channel_ratio_fd, nchannels_y_fd, args.stride_channel_x, args.stride_channel_y, args.stride_channel_dst,
|
||||
sample_ratio_fd, nsamples_y_fd, args.stride_sample_x, args.stride_sample_y, args.stride_sample_dst,
|
||||
@@ -1476,56 +1533,56 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
ntx_fd);
|
||||
}
|
||||
|
||||
template <ggml_type type, bool fallback, ggml_prec prec_src1>
|
||||
template <ggml_type type, bool fallback, ggml_prec prec_src1, bool has_fusion>
|
||||
void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
switch (args.J_best) {
|
||||
case 8:
|
||||
launch_mul_mat_q<type, 8, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 8, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 16:
|
||||
launch_mul_mat_q<type, 16, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 16, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 24:
|
||||
launch_mul_mat_q<type, 24, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 24, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 32:
|
||||
launch_mul_mat_q<type, 32, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 32, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 40:
|
||||
launch_mul_mat_q<type, 40, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 40, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 48:
|
||||
launch_mul_mat_q<type, 48, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 48, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 56:
|
||||
launch_mul_mat_q<type, 56, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 56, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 64:
|
||||
launch_mul_mat_q<type, 64, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 64, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 72:
|
||||
launch_mul_mat_q<type, 72, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 72, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 80:
|
||||
launch_mul_mat_q<type, 80, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 80, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 88:
|
||||
launch_mul_mat_q<type, 88, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 88, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 96:
|
||||
launch_mul_mat_q<type, 96, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 96, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 104:
|
||||
launch_mul_mat_q<type, 104, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 104, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 112:
|
||||
launch_mul_mat_q<type, 112, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 112, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 120:
|
||||
launch_mul_mat_q<type, 120, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 120, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
case 128:
|
||||
launch_mul_mat_q<type, 128, fallback, prec_src1>(ctx, args, stream);
|
||||
launch_mul_mat_q<type, 128, fallback, prec_src1, has_fusion>(ctx, args, stream);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "J_best=%d\n", args.J_best);
|
||||
@@ -1533,14 +1590,26 @@ void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args,
|
||||
}
|
||||
}
|
||||
|
||||
template <ggml_type type, bool fallback, ggml_prec prec_src1>
|
||||
static void mul_mat_q_switch_fusion(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
if constexpr (!fallback && ggml_cuda_mmq_fusion_supported(type)) {
|
||||
if (args.fusion.gate) {
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1, true>(ctx, args, stream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
GGML_ASSERT(!args.fusion.gate);
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1, false>(ctx, args, stream);
|
||||
}
|
||||
|
||||
template <ggml_type type, ggml_prec prec_src1>
|
||||
void mul_mat_q_case(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
|
||||
if (ggml_cuda_mmq_needs_fallback(args.nrows_x)) {
|
||||
constexpr bool fallback = true;
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
|
||||
mul_mat_q_switch_fusion<type, fallback, prec_src1>(ctx, args, stream);
|
||||
} else {
|
||||
constexpr bool fallback = false;
|
||||
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
|
||||
mul_mat_q_switch_fusion<type, fallback, prec_src1>(ctx, args, stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1582,6 +1651,9 @@ extern DECL_MMQ_CASE_W4A4(GGML_TYPE_NVFP4);
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void ggml_cuda_mul_mat_q(
|
||||
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst);
|
||||
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst,
|
||||
const ggml_cuda_mm_fusion_args_host * fusion = nullptr);
|
||||
|
||||
bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t n_experts);
|
||||
|
||||
bool ggml_cuda_mmq_fusion_is_efficient(ggml_type type, int64_t nrows_x, int64_t ncols, int64_t nchannels, int device);
|
||||
|
||||
+39
-20
@@ -196,9 +196,9 @@ static __global__ void rope_neox(const T * x,
|
||||
dst[idst + n_offs/2 + n_dims / 2] = ggml_cuda_cast<D>(x0 * sin_theta + x1 * cos_theta);
|
||||
}
|
||||
|
||||
template <bool forward, bool has_ff, typename T>
|
||||
template <bool forward, bool has_ff, typename T, typename D>
|
||||
static __global__ void rope_multi(const T * x,
|
||||
T * dst,
|
||||
D * dst,
|
||||
const int ne00,
|
||||
const int ne01,
|
||||
const int ne02,
|
||||
@@ -219,6 +219,8 @@ static __global__ void rope_multi(const T * x,
|
||||
const float * freq_factors,
|
||||
const mrope_sections sections,
|
||||
const bool is_imrope,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride,
|
||||
const bool inplace) {
|
||||
const int i0 = 2 * (blockDim.y * blockIdx.y + threadIdx.y);
|
||||
|
||||
@@ -236,12 +238,20 @@ static __global__ void rope_multi(const T * x,
|
||||
const int ix = i0 / 2 + i1 * s01 + i2 * s02 + i3 * s03;
|
||||
|
||||
ggml_cuda_pdl_sync();
|
||||
|
||||
// Fusion optimization: ROPE + VIEW + SET_ROWS.
|
||||
// The rope output is viewed as a 1D tensor and offset based on a row index in row_indices.
|
||||
if (set_rows_stride != 0) {
|
||||
idst = i1 * s1 + i0 / 2;
|
||||
idst += row_indices[i2] * set_rows_stride;
|
||||
}
|
||||
|
||||
if (i0 < n_offs || i0 >= n_offs + n_dims) {
|
||||
if (inplace) {
|
||||
return;
|
||||
}
|
||||
dst[idst + i0/2 + 0] = x[ix + i0/2 + 0];
|
||||
dst[idst + i0/2 + 1] = x[ix + i0/2 + 1];
|
||||
dst[idst + i0/2 + 0] = ggml_cuda_cast<D>(x[ix + i0/2 + 0]);
|
||||
dst[idst + i0/2 + 1] = ggml_cuda_cast<D>(x[ix + i0/2 + 1]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -286,8 +296,8 @@ static __global__ void rope_multi(const T * x,
|
||||
const float x0 = x[ix + n_offs/2 + 0];
|
||||
const float x1 = x[ix + n_offs/2 + n_dims/2];
|
||||
|
||||
dst[idst + n_offs/2 + 0] = x0*cos_theta - x1*sin_theta;
|
||||
dst[idst + n_offs/2 + n_dims/2] = x0*sin_theta + x1*cos_theta;
|
||||
dst[idst + n_offs/2 + 0] = ggml_cuda_cast<D>(x0*cos_theta - x1*sin_theta);
|
||||
dst[idst + n_offs/2 + n_dims/2] = ggml_cuda_cast<D>(x0*sin_theta + x1*cos_theta);
|
||||
}
|
||||
|
||||
template <bool forward, bool has_ff, typename T>
|
||||
@@ -443,9 +453,9 @@ static void rope_neox_cuda(const T * x,
|
||||
}
|
||||
}
|
||||
|
||||
template <bool forward, typename T>
|
||||
template <bool forward, typename T, typename D>
|
||||
static void rope_multi_cuda(const T * x,
|
||||
T * dst,
|
||||
D * dst,
|
||||
const int ne00,
|
||||
const int ne01,
|
||||
const int ne02,
|
||||
@@ -467,6 +477,8 @@ static void rope_multi_cuda(const T * x,
|
||||
const float * freq_factors,
|
||||
const mrope_sections sections,
|
||||
const bool is_imrope,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride,
|
||||
const bool inplace,
|
||||
cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 2 == 0);
|
||||
@@ -478,14 +490,14 @@ static void rope_multi_cuda(const T * x,
|
||||
|
||||
if (freq_factors == nullptr) {
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, false, T>, launch_params,
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, false, T, D>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, row_indices, set_rows_stride, inplace);
|
||||
} else {
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, true, T>, launch_params,
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, true, T, D>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, row_indices, set_rows_stride, inplace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,14 +660,21 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
} else if (is_mrope && !is_vision) {
|
||||
if (src0->type == GGML_TYPE_F32) {
|
||||
rope_multi_cuda<forward>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
|
||||
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F16) {
|
||||
rope_multi_cuda<forward>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
|
||||
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
|
||||
if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F32) {
|
||||
rope_multi_cuda<forward, float, float>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
|
||||
row_indices, set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F16) {
|
||||
rope_multi_cuda<forward, float, half>((const float *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
|
||||
row_indices, set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F16 && dst_type == GGML_TYPE_F16) {
|
||||
rope_multi_cuda<forward, half, half>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
|
||||
row_indices, set_rows_stride, inplace, stream);
|
||||
} else {
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
|
||||
+33
-10
@@ -3,23 +3,45 @@
|
||||
|
||||
#define MAX_GRIDDIM_X 0x7FFFFFFF
|
||||
|
||||
// src and dst may be views: rows are contiguous, dims 1..3 follow the strides (in elements).
|
||||
template <typename T>
|
||||
static __global__ void scale_f32(const T * x, T * dst, const float scale, const float bias, const int64_t nelements) {
|
||||
static __global__ void scale_f32(const T * x, T * dst, const float scale, const float bias, const uint32_t k,
|
||||
const uint3 ne0, const uint3 ne1, const uint3 ne2,
|
||||
const uint32_t s01, const uint32_t s02, const uint32_t s03,
|
||||
const uint32_t s1, const uint32_t s2, const uint32_t s3) {
|
||||
ggml_cuda_pdl_lc();
|
||||
int64_t tid = (int64_t)blockIdx.x * (int64_t)blockDim.x + (int64_t)threadIdx.x;
|
||||
int64_t stride = (int64_t)blockDim.x * (int64_t)gridDim.x;
|
||||
const uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
ggml_cuda_pdl_sync();
|
||||
for (int64_t i = tid; i < nelements; i += stride) {
|
||||
dst[i] = ggml_cuda_cast<T>(scale * ggml_cuda_cast<float>(x[i]) + bias);
|
||||
if (i >= k) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint2 d0 = fast_div_modulo(i, ne0); // <i / ne0, i0>
|
||||
const uint2 d1 = fast_div_modulo(d0.x, ne1); // <i / (ne0*ne1), i1>
|
||||
const uint2 d2 = fast_div_modulo(d1.x, ne2); // <i3, i2>
|
||||
|
||||
const size_t i_src = d0.y + size_t(d1.y)*s01 + size_t(d2.y)*s02 + size_t(d2.x)*s03;
|
||||
const size_t i_dst = d0.y + size_t(d1.y)*s1 + size_t(d2.y)*s2 + size_t(d2.x)*s3;
|
||||
|
||||
dst[i_dst] = ggml_cuda_cast<T>(scale * ggml_cuda_cast<float>(x[i_src]) + bias);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void scale_f32_cuda(const T * x, T * dst, const float scale, const float bias, const int64_t nelements, cudaStream_t stream) {
|
||||
const int64_t num_blocks = (nelements + CUDA_SCALE_BLOCK_SIZE - 1) / CUDA_SCALE_BLOCK_SIZE;
|
||||
static void scale_f32_cuda(const T * x, T * dst, const float scale, const float bias, const ggml_tensor * src0, const ggml_tensor * t, cudaStream_t stream) {
|
||||
const int64_t k = ggml_nelements(src0);
|
||||
const size_t ts = sizeof(T);
|
||||
GGML_ASSERT(k <= std::numeric_limits<uint32_t>::max());
|
||||
|
||||
const uint3 ne0 = init_fastdiv_values(src0->ne[0]);
|
||||
const uint3 ne1 = init_fastdiv_values(src0->ne[1]);
|
||||
const uint3 ne2 = init_fastdiv_values(src0->ne[2]);
|
||||
|
||||
const int64_t num_blocks = (k + CUDA_SCALE_BLOCK_SIZE - 1) / CUDA_SCALE_BLOCK_SIZE;
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(MIN(MAX_GRIDDIM_X, num_blocks), CUDA_SCALE_BLOCK_SIZE, 0, stream);
|
||||
ggml_cuda_kernel_launch(scale_f32<T>, launch_params, x, dst, scale, bias, nelements);
|
||||
ggml_cuda_kernel_launch(scale_f32<T>, launch_params, x, dst, scale, bias, (uint32_t) k, ne0, ne1, ne2,
|
||||
src0->nb[1]/ts, src0->nb[2]/ts, src0->nb[3]/ts,
|
||||
t->nb[1]/ts, t->nb[2]/ts, t->nb[3]/ts);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_scale(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
@@ -28,6 +50,7 @@ void ggml_cuda_op_scale(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_BF16);
|
||||
GGML_ASSERT(dst->type == src0->type);
|
||||
GGML_ASSERT(ggml_is_contiguous_rows(src0) && ggml_is_contiguous_rows(dst));
|
||||
|
||||
float scale;
|
||||
float bias;
|
||||
@@ -35,8 +58,8 @@ void ggml_cuda_op_scale(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
memcpy(&bias, (float *) dst->op_params + 1, sizeof(float));
|
||||
|
||||
if (src0->type == GGML_TYPE_BF16) {
|
||||
scale_f32_cuda((const nv_bfloat16 *) src0->data, (nv_bfloat16 *) dst->data, scale, bias, ggml_nelements(src0), stream);
|
||||
scale_f32_cuda((const nv_bfloat16 *) src0->data, (nv_bfloat16 *) dst->data, scale, bias, src0, dst, stream);
|
||||
} else {
|
||||
scale_f32_cuda((const float *) src0->data, (float *) dst->data, scale, bias, ggml_nelements(src0), stream);
|
||||
scale_f32_cuda((const float *) src0->data, (float *) dst->data, scale, bias, src0, dst, stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ __device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], co
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
const int idx = lane + i * WARP_SIZE;
|
||||
const bool active = !use_limit || (idx < limit);
|
||||
vals[i] = active ? sqrtf(vals[i] > 20.0f ? vals[i] : logf(1.0f + expf(vals[i]))) : -INFINITY;
|
||||
vals[i] = active ? sqrtf(vals[i] > 20.0f ? vals[i] : log1pf(expf(vals[i]))) : -INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,10 @@ static void launch_topk_moe_cuda(ggml_backend_cuda_context & ctx,
|
||||
ggml_cuda_kernel_launch(topk_moe_cuda<288, has_bias>, launch_params,
|
||||
logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
|
||||
break;
|
||||
case 384: // DeepSeek V4.1
|
||||
ggml_cuda_kernel_launch(topk_moe_cuda<384, has_bias>, launch_params,
|
||||
logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
|
||||
break;
|
||||
case 512:
|
||||
ggml_cuda_kernel_launch(topk_moe_cuda<512, has_bias>, launch_params,
|
||||
logits, weights, ids, bias, n_rows, n_expert_used, clamp_val, scale_val, config);
|
||||
@@ -472,7 +476,7 @@ bool ggml_cuda_should_use_topk_moe(const ggml_tensor * gating_op,
|
||||
// must match an instantiation of launch_topk_moe_cuda: a power of 2 up to 512,
|
||||
// or one of the non-power-of-2 expert counts of supported models
|
||||
const int n_expert = ids->nb[1] / ids->nb[0];
|
||||
if (((n_expert & (n_expert - 1)) != 0 || n_expert > 512) && n_expert != 288 && n_expert != 576) {
|
||||
if (((n_expert & (n_expert - 1)) != 0 || n_expert > 512) && n_expert != 288 && n_expert != 384 && n_expert != 576) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ static __device__ __forceinline__ float op_expm1(float x) {
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ float op_softplus(float x) {
|
||||
return (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
return (x > 20.0f) ? x : log1pf(expf(x));
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ float op_elu(float x) {
|
||||
|
||||
@@ -121,3 +121,18 @@ __device__ __forceinline__ float ggml_cuda_op_swiglu_clamp_single(float gate, fl
|
||||
|
||||
return ggml_cuda_op_silu_single(gate) * up;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float ggml_cuda_op_glu_single(ggml_glu_op op, float gate, float up, float limit) {
|
||||
switch (op) {
|
||||
case GGML_GLU_OP_SWIGLU:
|
||||
return up * ggml_cuda_op_silu_single(gate);
|
||||
case GGML_GLU_OP_GEGLU:
|
||||
return up * ggml_cuda_op_gelu_single(gate);
|
||||
case GGML_GLU_OP_SWIGLU_OAI:
|
||||
return ggml_cuda_op_swiglu_oai_single(gate, up);
|
||||
case GGML_GLU_OP_SWIGLU_CLAMP:
|
||||
return ggml_cuda_op_swiglu_clamp_single(gate, up, limit);
|
||||
default:
|
||||
return up * gate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +40,18 @@ static __device__ __forceinline__ int2 get_int_from_table_16(const int & q4, con
|
||||
const uint32_t q_odd = (q4 >> 4);
|
||||
|
||||
// Perform lookups in the lower half of the table (indices 0-7).
|
||||
uint32_t v_even_low = __builtin_amdgcn_perm(values[1], values[0], q_even & 0x07070707);
|
||||
uint32_t v_odd_low = __builtin_amdgcn_perm(values[1], values[0], q_odd & 0x07070707);
|
||||
uint32_t v_even_low = ggml_hip_perm(values[1], values[0], q_even & 0x07070707);
|
||||
uint32_t v_odd_low = ggml_hip_perm(values[1], values[0], q_odd & 0x07070707);
|
||||
|
||||
// Perform lookups in the upper half of the table (indices 8-15).
|
||||
uint32_t v_even_high = __builtin_amdgcn_perm(values[3], values[2], q_even & 0x07070707);
|
||||
uint32_t v_odd_high = __builtin_amdgcn_perm(values[3], values[2], q_odd & 0x07070707);
|
||||
uint32_t v_even_high = ggml_hip_perm(values[3], values[2], q_even & 0x07070707);
|
||||
uint32_t v_odd_high = ggml_hip_perm(values[3], values[2], q_odd & 0x07070707);
|
||||
|
||||
// Select between the low and high results based on the MSB of each index nibble.
|
||||
uint32_t mask_even = 0x03020100 | ((q_even & 0x08080808) >> 1);
|
||||
uint32_t res_x = __builtin_amdgcn_perm(v_even_high, v_even_low, mask_even);
|
||||
uint32_t res_x = ggml_hip_perm(v_even_high, v_even_low, mask_even);
|
||||
uint32_t mask_odd = 0x03020100 | ((q_odd & 0x08080808) >> 1);
|
||||
uint32_t res_y = __builtin_amdgcn_perm(v_odd_high, v_odd_low, mask_odd);
|
||||
uint32_t res_y = ggml_hip_perm(v_odd_high, v_odd_low, mask_odd);
|
||||
|
||||
return make_int2(res_x, res_y);
|
||||
#elif !defined(GGML_USE_MUSA)
|
||||
@@ -720,10 +720,10 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
const int v0 = ggml_hip_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = ggml_hip_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = ggml_hip_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = ggml_hip_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
@@ -777,8 +777,8 @@ static __device__ __forceinline__ float vec_dot_q2_0_q8_1(
|
||||
const uint32_t qx_indices = (q & 0x03) | ((q & 0x0C) << 6) | ((q & 0x30) << 12) | ((q & 0xC0) << 18);
|
||||
const uint32_t qy_bits = q >> 8;
|
||||
const uint32_t qy_indices = (qy_bits & 0x03) | ((qy_bits & 0x0C) << 6) | ((qy_bits & 0x30) << 12) | ((qy_bits & 0xC0) << 18);
|
||||
const int qx = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
const int qx = ggml_hip_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = ggml_hip_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
#else
|
||||
// unpack even and odd crumbs into byte values
|
||||
const int qe = __byte_perm(0x020100FF, 0x020100FF, q >> 0);
|
||||
|
||||
Vendored
+19
@@ -311,3 +311,22 @@ static __device__ __forceinline__ unsigned int __vcmpne4(unsigned int a, unsigne
|
||||
|
||||
return (ne_any_bit >> 7) * 0xff;
|
||||
}
|
||||
|
||||
// v_perm_b32 with selector bytes in [0, 7]: 0-3 pick bytes of lo, 4-7 pick bytes of hi.
|
||||
static __device__ __forceinline__ uint32_t ggml_hip_perm(const uint32_t hi, const uint32_t lo, const uint32_t sel) {
|
||||
#if __has_builtin(__builtin_amdgcn_is_invocable)
|
||||
if (__builtin_amdgcn_is_invocable(__builtin_amdgcn_perm)) {
|
||||
return __builtin_amdgcn_perm(hi, lo, sel);
|
||||
}
|
||||
const uint64_t src = ((uint64_t) hi << 32) | lo;
|
||||
uint32_t res = 0;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const uint32_t byte_sel = (sel >> (8 * i)) & 0x07;
|
||||
res |= (uint32_t) ((src >> (8 * byte_sel)) & 0xFF) << (8 * i);
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
return __builtin_amdgcn_perm(hi, lo, sel);
|
||||
#endif // __has_builtin(__builtin_amdgcn_is_invocable)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,13 @@ endif()
|
||||
set(TARGET_NAME ggml-hexagon)
|
||||
ggml_add_backend_library(${TARGET_NAME}
|
||||
ggml-hexagon.cpp
|
||||
htp-common.h
|
||||
htp-kparams.cpp
|
||||
htp-kparams.h
|
||||
htp-opnode.cpp
|
||||
htp-opnode.h
|
||||
htp-fusion.cpp
|
||||
htp-fusion.h
|
||||
htp-repack.cpp
|
||||
htp-repack.h
|
||||
htp-drv.cpp
|
||||
|
||||
+307
-5280
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
#ifndef HTP_COMMON_H
|
||||
#define HTP_COMMON_H
|
||||
|
||||
#include "htp-opnode.h"
|
||||
#include "htp-repack.h"
|
||||
|
||||
struct htp_tensor_extra {
|
||||
std::vector<uint8_t> shadow_buf;
|
||||
size_t shadow_size { 0 };
|
||||
uint32_t flags { 0 };
|
||||
};
|
||||
|
||||
extern int opt_arch;
|
||||
extern size_t opt_ndev;
|
||||
extern size_t opt_nhvx;
|
||||
extern int opt_nhmx;
|
||||
extern size_t opt_vmem;
|
||||
extern int opt_etm;
|
||||
extern int opt_verbose;
|
||||
extern int opt_profile;
|
||||
extern bool opt_hostbuf;
|
||||
extern bool opt_dma64;
|
||||
extern int opt_power_mode;
|
||||
|
||||
extern size_t opt_mbuf_dyn;
|
||||
extern size_t opt_mbuf_static;
|
||||
extern size_t opt_mbuf_total;
|
||||
|
||||
extern int opt_mm_select;
|
||||
extern int opt_fa_select;
|
||||
extern int opt_fa_split;
|
||||
extern int opt_gdn_select;
|
||||
extern int opt_ar_select;
|
||||
extern int opt_ar_scatter;
|
||||
|
||||
extern int opt_opbatch;
|
||||
extern int opt_opqueue;
|
||||
extern int opt_optrace;
|
||||
extern int opt_oppoll;
|
||||
extern int opt_opfusion;
|
||||
extern int opt_rt_repack;
|
||||
|
||||
#ifndef HEX_VERBOSE
|
||||
#define HEX_VERBOSE(...) \
|
||||
if (opt_verbose) GGML_LOG_DEBUG(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
static inline bool htp_tensor_is_fuseable(const struct ggml_tensor * t) {
|
||||
if (!t || !t->extra) return false;
|
||||
const auto * extra = (const struct htp_tensor_extra *) t->extra;
|
||||
return (extra->flags & HTP_TENSOR_FUSEABLE) != 0;
|
||||
}
|
||||
|
||||
static inline bool htp_can_rt_repack(const struct ggml_tensor * t) {
|
||||
if (opt_rt_repack == 0 || !htp_is_tiled_weight_tensor(t)) {
|
||||
return false;
|
||||
}
|
||||
if (t->buffer && ggml_backend_buft_is_host(t->buffer->buft)) {
|
||||
return false;
|
||||
}
|
||||
return opt_rt_repack > 1 || ggml_is_quantized(t->type);
|
||||
}
|
||||
|
||||
static inline bool htp_tensor_is_tiled(const struct ggml_tensor * t) {
|
||||
const auto * extra = (const struct htp_tensor_extra *) t->extra;
|
||||
if (extra && (extra->flags & HTP_TENSOR_TILED)) {
|
||||
return true;
|
||||
}
|
||||
return htp_can_rt_repack(t);
|
||||
}
|
||||
|
||||
static inline bool htp_needs_repack(const struct ggml_tensor * t) {
|
||||
const auto * extra = (const struct htp_tensor_extra *) t->extra;
|
||||
if (extra && (extra->flags & HTP_TENSOR_TILED)) {
|
||||
return false;
|
||||
}
|
||||
return htp_can_rt_repack(t);
|
||||
}
|
||||
|
||||
static inline bool htp_tensors_overlap(const struct ggml_tensor * a, const struct ggml_tensor * b) {
|
||||
const uintptr_t a0 = (uintptr_t) a->data;
|
||||
const uintptr_t b0 = (uintptr_t) b->data;
|
||||
const uintptr_t a1 = a0 + ggml_nbytes(a);
|
||||
const uintptr_t b1 = b0 + ggml_nbytes(b);
|
||||
|
||||
return a0 < b1 && b0 < a1;
|
||||
}
|
||||
|
||||
static inline bool htp_can_row_partition(const struct ggml_tensor * t) {
|
||||
if (t->ne[1] > 1 && (t->nb[1] & 127) != 0) return false;
|
||||
if (t->ne[2] > 1 && (t->nb[2] & 127) != 0) return false;
|
||||
if (t->ne[3] > 1 && (t->nb[3] & 127) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ggml_hexagon_session;
|
||||
|
||||
const char * htp_session_name(const struct ggml_hexagon_session * sess);
|
||||
size_t htp_session_vtcm_size(const struct ggml_hexagon_session * sess);
|
||||
uint32_t htp_session_n_threads(const struct ggml_hexagon_session * sess);
|
||||
uint32_t htp_session_n_hmx(const struct ggml_hexagon_session * sess);
|
||||
uint32_t htp_session_mdev_count(const struct ggml_hexagon_session * sess);
|
||||
|
||||
htp_op_code htp_op_remap(const struct ggml_tensor * t);
|
||||
|
||||
#endif // HTP_COMMON_H
|
||||
@@ -0,0 +1,649 @@
|
||||
#include "htp-fusion.h"
|
||||
#include "htp-kparams.h"
|
||||
#include "ggml-impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
static inline bool htp_same_tensor_data(const ggml_tensor * a, const ggml_tensor * b) {
|
||||
return a && b && (a == b || a->data == b->data);
|
||||
}
|
||||
|
||||
static inline bool htp_same_shape_and_contiguity(const ggml_tensor * a, const ggml_tensor * b) {
|
||||
return ggml_are_same_shape(a, b) && (ggml_is_contiguous(a) == ggml_is_contiguous(b));
|
||||
}
|
||||
|
||||
static inline bool htp_inplace_overlap_unsafe(const ggml_tensor * dst, const ggml_tensor * in) {
|
||||
return htp_tensors_overlap(dst, in) && (dst->data != in->data || !ggml_are_same_stride(dst, in));
|
||||
}
|
||||
|
||||
static inline bool htp_inputs_overlap_unsafe(const htp_opnode & prev, const ggml_tensor * dst, const ggml_tensor * extra = nullptr) {
|
||||
if (extra && htp_inplace_overlap_unsafe(dst, extra)) return true;
|
||||
for (const auto * in : prev.inputs) {
|
||||
if (htp_inplace_overlap_unsafe(dst, in)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool htp_match_consumer(
|
||||
const htp_opnode & prev,
|
||||
const htp_opnode & cur,
|
||||
bool commutative,
|
||||
const ggml_tensor ** other_src = nullptr
|
||||
) {
|
||||
if (prev.outputs.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
const ggml_tensor * prev_out = prev.dst();
|
||||
if (!htp_tensor_is_fuseable(prev_out)) {
|
||||
return false;
|
||||
}
|
||||
if (htp_same_tensor_data(cur.src0(), prev_out)) {
|
||||
if (other_src) *other_src = cur.src1();
|
||||
return true;
|
||||
}
|
||||
if (commutative && htp_same_tensor_data(cur.src1(), prev_out)) {
|
||||
if (other_src) *other_src = cur.src0();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_supported_mul_mat_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
|
||||
if (kparams->n_hmx) {
|
||||
return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
|
||||
}
|
||||
return htp_tensor_is_tiled(src0) && (kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW);
|
||||
}
|
||||
|
||||
static bool is_supported_mul_mat_id_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
|
||||
if (kparams->n_hmx) {
|
||||
return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
|
||||
}
|
||||
return htp_tensor_is_tiled(src0) &&
|
||||
(kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW || kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_BLOCK);
|
||||
}
|
||||
|
||||
// --- Fusion rules ---
|
||||
|
||||
static bool fuse_allreduce_add(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (opt_ar_select != 2 || prev.opcode != HTP_OP_ALLREDUCE) return false;
|
||||
|
||||
const auto * ar_kparams = prev.kparams<htp_allreduce_kernel_params>();
|
||||
const uint32_t rank = (uint32_t) ar_kparams->rank;
|
||||
const uint32_t n_ranks = (uint32_t) ar_kparams->n_ranks;
|
||||
|
||||
const ggml_tensor * ar_local = prev.inputs[rank];
|
||||
if (!htp_tensor_is_fuseable(ar_local)) return false;
|
||||
|
||||
const ggml_tensor * add_dst = cur.dst();
|
||||
const ggml_tensor * res_tensor = nullptr;
|
||||
if (htp_same_tensor_data(cur.src0(), ar_local)) {
|
||||
res_tensor = cur.src1();
|
||||
} else if (htp_same_tensor_data(cur.src1(), ar_local)) {
|
||||
res_tensor = cur.src0();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ar_local->type != res_tensor->type) return false;
|
||||
|
||||
const bool is_same_shape = ggml_are_same_shape(ar_local, res_tensor);
|
||||
const bool is_row_bcast = !is_same_shape && (ar_local->ne[0] == res_tensor->ne[0] &&
|
||||
res_tensor->ne[1] == 1 && res_tensor->ne[2] == 1 && res_tensor->ne[3] == 1);
|
||||
if (!is_same_shape && !is_row_bcast) return false;
|
||||
|
||||
if (is_same_shape && !ggml_are_same_stride(ar_local, res_tensor)) {
|
||||
return false;
|
||||
}
|
||||
if (ggml_is_contiguous(ar_local) != ggml_is_contiguous(add_dst)) return false;
|
||||
|
||||
const bool is_shard_ok = n_ranks <= HTP_OP_MAX_OUTPUTS && add_dst->data == ar_local->data;
|
||||
|
||||
struct htp_allreduce_kernel_params kparams;
|
||||
if (!htp_kparams_solve_allreduce(sess, add_dst, rank, n_ranks, true, is_row_bcast, is_shard_ok, &kparams)) {
|
||||
HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: solver failed\n", htp_session_name(sess));
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool scatter_ok = kparams.n_dsts > 1;
|
||||
kparams.mode = scatter_ok ? HTP_ALLREDUCE_SHARDED_FANOUT : HTP_ALLREDUCE_FULL;
|
||||
|
||||
for (uint32_t r = 0; r < n_ranks; r++) {
|
||||
const ggml_tensor * ar_src = prev.inputs[r];
|
||||
if (!htp_tensors_overlap(add_dst, ar_src)) continue;
|
||||
if (scatter_ok && r == rank) continue;
|
||||
HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: dst overlaps allreduce src %u\n", htp_session_name(sess), r);
|
||||
return false;
|
||||
}
|
||||
|
||||
prev.opcode = HTP_OP_ALLREDUCE_ADD;
|
||||
prev.inputs.push_back(res_tensor);
|
||||
if (kparams.mode != HTP_ALLREDUCE_SHARDED_FANOUT) {
|
||||
prev.outputs = { add_dst };
|
||||
}
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_rms_norm_mul(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_RMS_NORM) return false;
|
||||
|
||||
const ggml_tensor * weight = nullptr;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &weight)) return false;
|
||||
|
||||
const ggml_tensor * src0 = prev.src0();
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (src0->type != GGML_TYPE_F32 || src0->ne[0] != weight->ne[0] || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
|
||||
if (htp_inputs_overlap_unsafe(prev, dst, weight)) return false;
|
||||
|
||||
const bool is_row_bcast = (weight->ne[1] == 1 && weight->ne[2] == 1 && weight->ne[3] == 1);
|
||||
const bool is_same_shape = ggml_are_same_shape(src0, weight);
|
||||
if (!is_row_bcast && !is_same_shape) return false;
|
||||
|
||||
struct htp_unary_kernel_params kparams;
|
||||
if (!htp_kparams_solve_rms_norm_mul(sess, src0, weight, dst, &kparams)) {
|
||||
HEX_VERBOSE("ggml-hex: %s skip RMS_NORM_MUL fusion: VTCM needed (%d) > budget (%zu)\n",
|
||||
htp_session_name(sess), kparams.vtcm_size, htp_session_vtcm_size(sess));
|
||||
return false;
|
||||
}
|
||||
|
||||
prev.opcode = HTP_OP_RMS_NORM_MUL;
|
||||
prev.inputs = { src0, weight };
|
||||
prev.outputs = { dst };
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_rms_norm_scale(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
GGML_UNUSED(sess);
|
||||
if (prev.opcode != HTP_OP_RMS_NORM && prev.opcode != HTP_OP_RMS_NORM_MUL) return false;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
|
||||
|
||||
const ggml_tensor * src0 = prev.src0();
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (src0->type != GGML_TYPE_F32 || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
|
||||
if (htp_inputs_overlap_unsafe(prev, dst)) return false;
|
||||
|
||||
float scale = 0.0f;
|
||||
float bias = 0.0f;
|
||||
memcpy(&scale, (const float *) cur.params + 0, sizeof(float));
|
||||
memcpy(&bias, (const float *) cur.params + 1, sizeof(float));
|
||||
if (bias != 0.0f || scale == 0.0f) return false;
|
||||
|
||||
float prev_scale = 0.0f;
|
||||
memcpy(&prev_scale, &prev.params[1], sizeof(float));
|
||||
if (prev_scale != 0.0f) {
|
||||
scale *= prev_scale;
|
||||
}
|
||||
memcpy(&prev.params[1], &scale, sizeof(float));
|
||||
|
||||
prev.outputs[0] = dst;
|
||||
prev.fused.push_back(cur.node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_mul_mat_add(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_MUL_MAT) return false;
|
||||
|
||||
const ggml_tensor * src2 = nullptr;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &src2)) return false;
|
||||
if (src2->type != GGML_TYPE_F32) return false;
|
||||
|
||||
const ggml_tensor * src0 = prev.node->src[0];
|
||||
const ggml_tensor * src1 = prev.node->src[1];
|
||||
const ggml_tensor * dst = cur.node;
|
||||
if (!ggml_are_same_shape(prev.node, dst)) return false;
|
||||
|
||||
const bool prev_collapsed = htp_matmul_can_collapse(src0, src1, prev.node);
|
||||
const bool cur_collapsed = htp_matmul_add_can_collapse(src0, src1, src2, dst);
|
||||
if (prev_collapsed != cur_collapsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
|
||||
struct htp_mm_kernel_params kparams;
|
||||
if (!htp_kparams_solve_matmul_add(sess, src0, src1, src2, dst, &kparams)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int src1_nrows = src1->ne[1] * src1->ne[2] * src1->ne[3];
|
||||
if (kparams.n_hmx == 0 && src1_nrows != 1) return false;
|
||||
|
||||
if (kparams.n_hmx > 0 && orig_kparams->n_hmx > 0) {
|
||||
if (kparams.m_chunk < orig_kparams->m_chunk ||
|
||||
kparams.n_chunk < orig_kparams->n_chunk ||
|
||||
kparams.n_act_threads < orig_kparams->n_act_threads) {
|
||||
HEX_VERBOSE("ggml-hex: %s skip MUL_MAT_ADD fusion: HMX efficiency reduced (m %d->%d, n %d->%d, th %d->%d)\n",
|
||||
htp_session_name(sess), orig_kparams->m_chunk, kparams.m_chunk,
|
||||
orig_kparams->n_chunk, kparams.n_chunk,
|
||||
orig_kparams->n_act_threads, kparams.n_act_threads);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
prev.opcode = HTP_OP_MUL_MAT_ADD;
|
||||
prev.inputs = { src0, src1, src2 };
|
||||
prev.outputs = { dst };
|
||||
htp_opnode_collapse(sess, prev);
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_mul_mat_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_MUL_MAT && prev.opcode != HTP_OP_MUL_MAT_NX) return false;
|
||||
|
||||
const uint32_t curr_n = (uint32_t) prev.outputs.size();
|
||||
if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 1 >= HTP_OP_MAX_INPUTS) return false;
|
||||
|
||||
const ggml_tensor * w0 = prev.inputs[0];
|
||||
const ggml_tensor * x_orig = prev.node->src[1];
|
||||
const ggml_tensor * w_in = cur.node->src[0];
|
||||
const ggml_tensor * x_in = cur.node->src[1];
|
||||
const ggml_tensor * d_in = cur.node;
|
||||
|
||||
auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
|
||||
const auto * cur_kparams = cur.kparams<htp_mm_kernel_params>();
|
||||
|
||||
if (x_in != x_orig || w_in->type != w0->type || w_in->ne[0] != w0->ne[0] ||
|
||||
w0->ne[2] != 1 || w0->ne[3] != 1 || w_in->ne[2] != 1 || w_in->ne[3] != 1 ||
|
||||
htp_tensor_is_tiled(w_in) != htp_tensor_is_tiled(w0) ||
|
||||
orig_kparams->n_hmx != cur_kparams->n_hmx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool collapse = htp_matmul_can_collapse(w0, x_orig, prev.node) && htp_matmul_can_collapse(w_in, x_in, d_in);
|
||||
if ((x_orig->ne[2] > 1 || x_orig->ne[3] > 1) && !collapse) return false;
|
||||
|
||||
if (prev.opcode == HTP_OP_MUL_MAT_NX) {
|
||||
orig_kparams->n_weights = curr_n + 1;
|
||||
} else {
|
||||
struct htp_mm_kernel_params kparams;
|
||||
const ggml_tensor x_collapsed = collapse ? htp_tensor_collapse_rows(x_orig) : *x_orig;
|
||||
if (!htp_kparams_solve_mmnx(sess, w0, &x_collapsed, curr_n + 1, &kparams)) return false;
|
||||
if (!is_supported_mul_mat_nx_kernel(w0, &kparams)) return false;
|
||||
|
||||
prev.set_kparams(kparams);
|
||||
prev.opcode = HTP_OP_MUL_MAT_NX;
|
||||
}
|
||||
|
||||
prev.inputs.insert(prev.inputs.begin() + curr_n, w_in);
|
||||
prev.outputs.push_back(collapse ? prev.add_dummy(htp_tensor_collapse_rows(d_in)) : d_in);
|
||||
prev.fused.push_back(cur.node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_mul_mat_id_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_MUL_MAT_ID && prev.opcode != HTP_OP_MUL_MAT_ID_NX) return false;
|
||||
|
||||
const uint32_t curr_n = (uint32_t) prev.outputs.size();
|
||||
if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 2 >= HTP_OP_MAX_INPUTS) return false;
|
||||
|
||||
const ggml_tensor * w0 = prev.inputs[0];
|
||||
const ggml_tensor * x = prev.inputs[curr_n];
|
||||
const ggml_tensor * ids = prev.inputs[curr_n + 1];
|
||||
const ggml_tensor * w_in = cur.src0();
|
||||
const ggml_tensor * x_in = cur.src1();
|
||||
const ggml_tensor * ids_in = cur.node->src[2];
|
||||
const ggml_tensor * d_in = cur.dst();
|
||||
|
||||
auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
|
||||
const auto * cur_kparams = cur.kparams<htp_mm_kernel_params>();
|
||||
|
||||
if (x_in != x || ids_in != ids || w_in->type != w0->type ||
|
||||
w_in->ne[0] != w0->ne[0] || w_in->ne[2] != w0->ne[2] ||
|
||||
htp_tensor_is_tiled(w_in) != htp_tensor_is_tiled(w0) ||
|
||||
orig_kparams->n_hmx != cur_kparams->n_hmx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct htp_mm_kernel_params kparams;
|
||||
if (d_in->nb[1] == prev.node->nb[1]) {
|
||||
kparams = *orig_kparams;
|
||||
kparams.n_weights = curr_n + 1;
|
||||
} else if (!htp_kparams_solve_mmidnx(sess, w0, x, d_in, curr_n + 1, &kparams)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_supported_mul_mat_id_nx_kernel(w0, &kparams)) return false;
|
||||
|
||||
prev.opcode = HTP_OP_MUL_MAT_ID_NX;
|
||||
prev.inputs.insert(prev.inputs.begin() + curr_n, w_in);
|
||||
prev.outputs.push_back(d_in);
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_gdn_cpy(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
GGML_UNUSED(sess);
|
||||
if (prev.opcode != HTP_OP_GATED_DELTA_NET || prev.outputs.size() != 1) return false;
|
||||
|
||||
const ggml_tensor * gdn_out = prev.dst();
|
||||
const ggml_tensor * cpy_node = cur.node;
|
||||
const ggml_tensor * cpy_src = cur.src0();
|
||||
const ggml_tensor * cpy_dst = cur.dst();
|
||||
|
||||
if (gdn_out->type != GGML_TYPE_F32 || cpy_src->type != GGML_TYPE_F32 || cpy_dst->type != GGML_TYPE_F32) return false;
|
||||
if ((gdn_out->flags & GGML_TENSOR_FLAG_OUTPUT) || (cpy_node->flags & GGML_TENSOR_FLAG_OUTPUT)) return false;
|
||||
|
||||
const ggml_tensor * v = prev.node->src[2];
|
||||
if (!v) return false;
|
||||
|
||||
const int64_t S_v = v->ne[0];
|
||||
const int64_t H = v->ne[1];
|
||||
const int64_t n_tokens = v->ne[2];
|
||||
const int64_t n_seqs = v->ne[3];
|
||||
const int64_t K = ggml_get_op_params_i32(prev.node, 0);
|
||||
const size_t tail_off = (size_t) S_v * H * n_tokens * n_seqs * sizeof(float);
|
||||
const int64_t D = S_v * S_v * H;
|
||||
const int64_t n_written = std::min<int64_t>(n_tokens, K);
|
||||
|
||||
if (cpy_src->op != GGML_OP_VIEW || !htp_same_tensor_data(cpy_src->view_src, gdn_out) ||
|
||||
cpy_src->view_offs != tail_off || !ggml_is_contiguous(cpy_src)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cpy_dst->ne[0] != D || cpy_dst->ne[1] != n_seqs || cpy_dst->ne[2] != n_written || !ggml_is_contiguous(cpy_dst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prev.outputs.push_back(cpy_dst);
|
||||
prev.fused.push_back(cur.node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_ssm_conv_silu(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
GGML_UNUSED(sess);
|
||||
if (prev.opcode != HTP_OP_SSM_CONV) return false;
|
||||
|
||||
auto * kparams = prev.kparams<htp_ssm_conv_kernel_params>();
|
||||
if (kparams->silu) return false;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
|
||||
|
||||
const ggml_tensor * conv_dst = prev.dst();
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (!ggml_are_same_layout(conv_dst, dst)) return false;
|
||||
if (htp_tensors_overlap(dst, prev.src0()) || htp_tensors_overlap(dst, prev.src1())) return false;
|
||||
|
||||
kparams->silu = 1;
|
||||
prev.outputs[0] = dst;
|
||||
prev.fused.push_back(cur.node);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_repeat_get_rows(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_REPEAT) return false;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
|
||||
|
||||
const ggml_tensor * repeat_out = prev.dst();
|
||||
const ggml_tensor * repeat_src0 = prev.src0();
|
||||
const ggml_tensor * gr_src1 = cur.src1();
|
||||
const ggml_tensor * gr_dst = cur.dst();
|
||||
|
||||
if (repeat_src0->ne[0] != repeat_out->ne[0] || repeat_src0->ne[1] != repeat_out->ne[1]) return false;
|
||||
if (repeat_src0->type != repeat_out->type) return false;
|
||||
|
||||
struct htp_get_rows_kernel_params kparams;
|
||||
if (!htp_kparams_solve_repeat_get_rows(sess, repeat_src0, gr_src1, gr_dst, &kparams) || kparams.vtcm_size == 0) return false;
|
||||
|
||||
prev.opcode = HTP_OP_GET_ROWS;
|
||||
prev.inputs = { repeat_src0, gr_src1 };
|
||||
prev.outputs = { gr_dst };
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_unary_mul_glu(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
htp_op_code glu_op = HTP_OP_INVALID;
|
||||
switch (prev.opcode) {
|
||||
case HTP_OP_UNARY_SILU: glu_op = HTP_OP_GLU_SWIGLU; break;
|
||||
case HTP_OP_UNARY_GELU: glu_op = HTP_OP_GLU_GEGLU; break;
|
||||
case HTP_OP_UNARY_GELU_QUICK: glu_op = HTP_OP_GLU_GEGLU_QUICK; break;
|
||||
case HTP_OP_UNARY_GELU_ERF: glu_op = HTP_OP_GLU_GEGLU_ERF; break;
|
||||
case HTP_OP_UNARY_RELU: glu_op = HTP_OP_GLU_REGLU; break;
|
||||
case HTP_OP_UNARY_SIGMOID: glu_op = HTP_OP_GLU_SIGMOID; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
const ggml_tensor * up = nullptr;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &up)) return false;
|
||||
|
||||
const ggml_tensor * gate = prev.src0();
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (htp_inputs_overlap_unsafe(prev, dst, up)) return false;
|
||||
|
||||
prev.opcode = glu_op;
|
||||
prev.inputs = { gate, up };
|
||||
prev.outputs = { dst };
|
||||
if (!htp_kparams_solve(sess, prev)) return false;
|
||||
|
||||
prev.fused.push_back(cur.node);
|
||||
memset(prev.params, 0, sizeof(prev.params));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_mul_mul_scalar(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != HTP_OP_MUL || prev.inputs.size() != 2) return false;
|
||||
|
||||
const auto * k0 = prev.kparams<htp_binary_kernel_params>();
|
||||
const auto * k1 = cur.kparams<htp_binary_kernel_params>();
|
||||
if (k0->kernel_type != HTP_BINARY_KERNEL_SCALAR || k1->kernel_type != HTP_BINARY_KERNEL_SCALAR) return false;
|
||||
if (k0->src1_strided || k1->src1_strided) return false;
|
||||
|
||||
const ggml_tensor * src2 = nullptr;
|
||||
if (!htp_match_consumer(prev, cur, /*commutative=*/ false, &src2)) return false;
|
||||
|
||||
const ggml_tensor * src1 = prev.src1();
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (!ggml_are_same_shape(src1, src2) || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
|
||||
if (htp_inputs_overlap_unsafe(prev, dst, src2)) return false;
|
||||
|
||||
const size_t vtcm_size = htp_session_vtcm_size(sess);
|
||||
struct htp_binary_kernel_params kparams = *k0;
|
||||
kparams.src2_size = kparams.src1_size;
|
||||
kparams.rows_per_buffer = 0;
|
||||
if (kparams.src1_size > 65536 || kparams.src1_size + kparams.src2_size > vtcm_size) return false;
|
||||
|
||||
struct htp_binary_vtcm_layout L;
|
||||
htp_binary_vtcm_layout_build(&L, &kparams, vtcm_size);
|
||||
if (L.rows_per_buffer == 0 || L.total_bytes > vtcm_size) return false;
|
||||
|
||||
kparams.rows_per_buffer = L.rows_per_buffer;
|
||||
kparams.vtcm_size = L.total_bytes;
|
||||
|
||||
prev.inputs.push_back(src2);
|
||||
prev.outputs[0] = dst;
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fuse_binary_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
|
||||
if (prev.opcode != cur.opcode) return false;
|
||||
|
||||
const uint32_t curr_n = (uint32_t) prev.inputs.size();
|
||||
if (curr_n < 2 || curr_n >= HTP_OP_MAX_INPUTS) return false;
|
||||
|
||||
const auto * k0 = prev.kparams<htp_binary_kernel_params>();
|
||||
const auto * k1 = cur.kparams<htp_binary_kernel_params>();
|
||||
const bool k0_ok = (k0->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) ||
|
||||
(k0->kernel_type == HTP_BINARY_KERNEL_CHUNKED && !k0->is_scalar);
|
||||
const bool k1_ok = (k1->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) ||
|
||||
(k1->kernel_type == HTP_BINARY_KERNEL_CHUNKED && !k1->is_scalar);
|
||||
if (!k0_ok || !k1_ok) return false;
|
||||
|
||||
const bool is_comm = (cur.opcode == HTP_OP_ADD || cur.opcode == HTP_OP_MUL);
|
||||
const ggml_tensor * new_src = nullptr;
|
||||
if (!htp_match_consumer(prev, cur, is_comm, &new_src)) return false;
|
||||
|
||||
const ggml_tensor * src0 = prev.inputs[0];
|
||||
const ggml_tensor * dst = cur.dst();
|
||||
if (!ggml_are_same_shape(src0, prev.inputs[1]) ||
|
||||
!ggml_are_same_shape(src0, new_src) ||
|
||||
!htp_same_shape_and_contiguity(prev.dst(), dst)) {
|
||||
return false;
|
||||
}
|
||||
if (htp_inputs_overlap_unsafe(prev, dst, new_src)) return false;
|
||||
|
||||
const size_t vtcm_size = htp_session_vtcm_size(sess);
|
||||
struct htp_binary_kernel_params kparams = *k0;
|
||||
struct htp_binary_vtcm_layout L;
|
||||
memset(&L, 0, sizeof(L));
|
||||
|
||||
const uint32_t n_srcs = curr_n + 1;
|
||||
if (k0->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE || k1->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) {
|
||||
if (k0->kernel_type != HTP_BINARY_KERNEL_SAME_SHAPE) {
|
||||
kparams = *k1;
|
||||
}
|
||||
kparams.n_srcs = (uint8_t) n_srcs;
|
||||
kparams.rows_per_buffer = 0;
|
||||
|
||||
auto check_split = [&](const ggml_tensor * t) {
|
||||
if (src0->ne[2] > 1 && t->nb[2] != t->ne[1] * t->nb[1]) kparams.split_at_ne01 = 1;
|
||||
if (src0->ne[3] > 1 && t->nb[3] != t->ne[2] * t->nb[2]) kparams.split_at_ne02 = 1;
|
||||
};
|
||||
for (const auto * in : prev.inputs) check_split(in);
|
||||
check_split(new_src);
|
||||
check_split(dst);
|
||||
|
||||
htp_binary_vtcm_layout_build(&L, &kparams, vtcm_size);
|
||||
}
|
||||
|
||||
if (L.rows_per_buffer == 0 || L.total_bytes == 0 || L.total_bytes > vtcm_size) {
|
||||
bool all_contig = ggml_is_contiguous(new_src) && ggml_is_contiguous(dst);
|
||||
for (const auto * in : prev.inputs) {
|
||||
all_contig = all_contig && ggml_is_contiguous(in);
|
||||
}
|
||||
if (!all_contig) return false;
|
||||
|
||||
const uint32_t total_elems = (uint32_t) ggml_nelements(src0);
|
||||
const uint32_t elem_size = (uint32_t) ggml_type_size(src0->type);
|
||||
const uint32_t max_threads = htp_session_n_threads(sess);
|
||||
if (!htp_binary_solve_chunked(&L, &kparams, total_elems, elem_size, max_threads, n_srcs, false, vtcm_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
kparams.rows_per_buffer = L.rows_per_buffer;
|
||||
kparams.vtcm_size = (uint32_t) L.total_bytes;
|
||||
|
||||
prev.inputs.push_back(new_src);
|
||||
prev.outputs[0] = dst;
|
||||
prev.fused.push_back(cur.node);
|
||||
prev.set_kparams(kparams);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool htp_fusion_can_stack(const struct ggml_tensor * a) {
|
||||
if (!a || !a->src[0] || !a->src[1]) return false;
|
||||
switch (a->op) {
|
||||
case GGML_OP_MUL_MAT:
|
||||
if (!htp_fusion_enabled(HTP_FUSE_MUL_MAT_NX)) return false;
|
||||
if (htp_is_hadamard(a)) return false;
|
||||
if (a->src[0]->ne[2] != 1 || a->src[0]->ne[3] != 1) return false;
|
||||
return ggml_is_quantized(a->src[0]->type);
|
||||
case GGML_OP_MUL_MAT_ID:
|
||||
if (!htp_fusion_enabled(HTP_FUSE_MUL_MAT_ID_NX)) return false;
|
||||
return a->src[2] != nullptr && ggml_is_quantized(a->src[0]->type);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool htp_fusion_can_stack(const struct ggml_tensor * a, const struct ggml_tensor * b) {
|
||||
if (!htp_fusion_can_stack(b) || a->op != b->op || a->src[1] != b->src[1]) {
|
||||
return false;
|
||||
}
|
||||
if (a->op == GGML_OP_MUL_MAT_ID && a->src[2] != b->src[2]) {
|
||||
return false;
|
||||
}
|
||||
const ggml_tensor * w0 = a->src[0];
|
||||
const ggml_tensor * w1 = b->src[0];
|
||||
return w0->type == w1->type && w0->ne[0] == w1->ne[0] && w0->ne[2] == w1->ne[2];
|
||||
}
|
||||
|
||||
int htp_fusion_max(const struct ggml_cgraph * graph, int idx) {
|
||||
const int n = graph->n_nodes;
|
||||
const ggml_op op0 = graph->nodes[idx]->op;
|
||||
if (op0 != GGML_OP_ADD && op0 != GGML_OP_NORM && op0 != GGML_OP_RMS_NORM) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
enum ggml_op ops[HTP_OP_MAX_FUSED];
|
||||
ops[0] = op0;
|
||||
|
||||
int f = idx + 1;
|
||||
while (f < n && f < idx + HTP_OP_MAX_FUSED) {
|
||||
const ggml_op op = graph->nodes[f]->op;
|
||||
if (op != GGML_OP_ADD && op != GGML_OP_MUL && op != GGML_OP_SCALE &&
|
||||
op != GGML_OP_NORM && op != GGML_OP_RMS_NORM) {
|
||||
break;
|
||||
}
|
||||
ops[f - idx] = op;
|
||||
f++;
|
||||
}
|
||||
|
||||
f -= idx;
|
||||
for (; f > 1; f--) {
|
||||
if (ggml_can_fuse(graph, idx, ops, f)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
void htp_fusion_tag_graph(struct ggml_cgraph * graph) {
|
||||
for (int i = 0; i < graph->n_nodes; i++) {
|
||||
auto * extra = (htp_tensor_extra *) graph->nodes[i]->extra;
|
||||
if (!extra) continue;
|
||||
|
||||
extra->flags &= ~HTP_TENSOR_FUSEABLE;
|
||||
|
||||
const ggml_op op = graph->nodes[i]->op;
|
||||
if ((op == GGML_OP_RMS_NORM || op == GGML_OP_SCALE || op == GGML_OP_REPEAT ||
|
||||
op == GGML_OP_UNARY || op == GGML_OP_ADD || op == GGML_OP_SUB ||
|
||||
op == GGML_OP_MUL || op == GGML_OP_DIV || op == GGML_OP_MUL_MAT_ID ||
|
||||
op == GGML_OP_SSM_CONV ||
|
||||
(op == GGML_OP_MUL_MAT && !htp_is_hadamard(graph->nodes[i]))) &&
|
||||
ggml_node_has_n_uses(graph, i, 1)) {
|
||||
extra->flags |= HTP_TENSOR_FUSEABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool htp_fusion_try_fuse(const struct ggml_hexagon_session * sess, struct htp_opnode & prev, const struct htp_opnode & cur) {
|
||||
if (!opt_opfusion) return false;
|
||||
|
||||
switch (cur.opcode) {
|
||||
case HTP_OP_ADD:
|
||||
return (htp_fusion_enabled(HTP_FUSE_ALLREDUCE_ADD) && fuse_allreduce_add(sess, prev, cur)) ||
|
||||
(htp_fusion_enabled(HTP_FUSE_MUL_MAT_ADD) && fuse_mul_mat_add(sess, prev, cur)) ||
|
||||
(htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur));
|
||||
case HTP_OP_MUL:
|
||||
return (htp_fusion_enabled(HTP_FUSE_RMS_NORM_MUL) && fuse_rms_norm_mul(sess, prev, cur)) ||
|
||||
(htp_fusion_enabled(HTP_FUSE_UNARY_MUL_GLU) && fuse_unary_mul_glu(sess, prev, cur)) ||
|
||||
(htp_fusion_enabled(HTP_FUSE_MUL_MUL_SCALAR) && fuse_mul_mul_scalar(sess, prev, cur)) ||
|
||||
(htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur));
|
||||
case HTP_OP_SUB:
|
||||
case HTP_OP_DIV:
|
||||
return htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur);
|
||||
case HTP_OP_SCALE:
|
||||
return htp_fusion_enabled(HTP_FUSE_RMS_NORM_SCALE) && fuse_rms_norm_scale(sess, prev, cur);
|
||||
case HTP_OP_UNARY_SILU:
|
||||
return htp_fusion_enabled(HTP_FUSE_SSM_CONV_SILU) && fuse_ssm_conv_silu(sess, prev, cur);
|
||||
case HTP_OP_GET_ROWS:
|
||||
return htp_fusion_enabled(HTP_FUSE_REPEAT_GET_ROWS) && fuse_repeat_get_rows(sess, prev, cur);
|
||||
case HTP_OP_CPY:
|
||||
return htp_fusion_enabled(HTP_FUSE_GDN_CPY) && fuse_gdn_cpy(sess, prev, cur);
|
||||
case HTP_OP_MUL_MAT:
|
||||
return htp_fusion_enabled(HTP_FUSE_MUL_MAT_NX) && fuse_mul_mat_nx(sess, prev, cur);
|
||||
case HTP_OP_MUL_MAT_ID:
|
||||
return htp_fusion_enabled(HTP_FUSE_MUL_MAT_ID_NX) && fuse_mul_mat_id_nx(sess, prev, cur);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef HTP_FUSION_H
|
||||
#define HTP_FUSION_H
|
||||
|
||||
#include "htp-common.h"
|
||||
|
||||
enum htp_fusion_flags {
|
||||
HTP_FUSE_ALLREDUCE_ADD = (1 << 1), // 2
|
||||
HTP_FUSE_RMS_NORM_MUL = (1 << 2), // 4
|
||||
HTP_FUSE_MUL_MAT_ADD = (1 << 3), // 8
|
||||
HTP_FUSE_MUL_MAT_NX = (1 << 4), // 16
|
||||
HTP_FUSE_MUL_MAT_ID_NX = (1 << 5), // 32
|
||||
HTP_FUSE_GDN_CPY = (1 << 6), // 64
|
||||
HTP_FUSE_REPEAT_GET_ROWS = (1 << 7), // 128
|
||||
HTP_FUSE_UNARY_MUL_GLU = (1 << 8), // 256
|
||||
HTP_FUSE_MUL_MUL_SCALAR = (1 << 9), // 512
|
||||
HTP_FUSE_BINARY_NX = (1 << 10), // 1024
|
||||
HTP_FUSE_RMS_NORM_SCALE = (1 << 11), // 2048
|
||||
HTP_FUSE_SSM_CONV_SILU = (1 << 12), // 4096
|
||||
};
|
||||
|
||||
static inline bool htp_fusion_enabled(int flag) {
|
||||
if (opt_opfusion <= 0) return false;
|
||||
if (opt_opfusion == 1) return true; // 1 enables all
|
||||
return (opt_opfusion & flag) != 0;
|
||||
}
|
||||
|
||||
bool htp_fusion_can_stack(const struct ggml_tensor * a);
|
||||
bool htp_fusion_can_stack(const struct ggml_tensor * a, const struct ggml_tensor * b);
|
||||
int htp_fusion_max(const struct ggml_cgraph * graph, int idx);
|
||||
void htp_fusion_tag_graph(struct ggml_cgraph * graph);
|
||||
bool htp_fusion_try_fuse(const struct ggml_hexagon_session * sess, struct htp_opnode & prev, const struct htp_opnode & cur);
|
||||
|
||||
#endif // HTP_FUSION_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user