mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-11-12 05:47:36 -06:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
827466b9ef | ||
|
|
8954f19483 | ||
|
|
a1a68eb797 | ||
|
|
e182fdad4a | ||
|
|
09452cdedd | ||
|
|
cfb9d4b754 | ||
|
|
72f4603c7c | ||
|
|
8c388364e1 | ||
|
|
08f49598c1 | ||
|
|
a8575a3ef4 | ||
|
|
a963f65957 | ||
|
|
ab4a77b9ad | ||
|
|
0d69255c52 | ||
|
|
3bce8bd50c | ||
|
|
81f21f4b27 | ||
|
|
b19f1d9e63 | ||
|
|
d90cfd4a9b | ||
|
|
60d46a5abe | ||
|
|
93cf736a8d | ||
|
|
4fffab623e | ||
|
|
09338b85e8 | ||
|
|
dcbff21661 | ||
|
|
f3d7725a77 | ||
|
|
7021f7e099 | ||
|
|
e0949d09b1 | ||
|
|
5d4c8eeb24 | ||
|
|
dd6b945be2 | ||
|
|
e1f7593c32 | ||
|
|
7bcbc04fbf | ||
|
|
dd00306bcf | ||
|
|
1d81992784 | ||
|
|
bd717e08f3 | ||
|
|
2cbe0ec429 | ||
|
|
664f031f49 | ||
|
|
4180722eeb | ||
|
|
055c3c26ef | ||
|
|
2656b71a19 | ||
|
|
cb44fdb189 | ||
|
|
cd2db800db | ||
|
|
f7b886fe6f | ||
|
|
956591f457 | ||
|
|
ccd360f060 | ||
|
|
b326dd6752 | ||
|
|
b8abe41a50 | ||
|
|
2e325dcd57 | ||
|
|
1648d64117 | ||
|
|
432433637b | ||
|
|
71a6a114bc | ||
|
|
7f24aacde3 | ||
|
|
af6b5243d6 | ||
|
|
d2b3eb9b60 | ||
|
|
eeb2104794 | ||
|
|
03d6c824e1 | ||
|
|
adde94c11b | ||
|
|
459ab1cdd3 | ||
|
|
77e2db8cab | ||
|
|
9d7b2d7397 | ||
|
|
9e9619d030 | ||
|
|
f3b407ca1b | ||
|
|
aa75ff3239 | ||
|
|
b5cb8f6f47 | ||
|
|
85822a7ab9 | ||
|
|
2b66bfbf71 | ||
|
|
582eab7186 | ||
|
|
7c2653acc7 | ||
|
|
7592b61d3b | ||
|
|
cd4ed902e1 | ||
|
|
202c1a229f | ||
|
|
e24d0b4c34 | ||
|
|
3e86ce2c2d | ||
|
|
494d0f47bd | ||
|
|
281e7dad7c | ||
|
|
90e3841d9f | ||
|
|
c75fba30cf | ||
|
|
40f4f4ac9d | ||
|
|
54cde7106a | ||
|
|
7d6fd845ec | ||
|
|
4ed88da2ea | ||
|
|
ac931b5bcb | ||
|
|
15759e0ca9 | ||
|
|
a591cbe17c | ||
|
|
203b5f404c | ||
|
|
351025ec53 | ||
|
|
0d9eacadf2 | ||
|
|
17701e48d7 | ||
|
|
ac007054e7 | ||
|
|
940a30ee3b | ||
|
|
45c8ea8374 | ||
|
|
c90aa996ba | ||
|
|
a2c942dcfe | ||
|
|
18c5a95e2b | ||
|
|
ae4893a092 | ||
|
|
6122b566b0 | ||
|
|
3837bf0bc0 | ||
|
|
c1a4febe9d | ||
|
|
78ffbaa185 | ||
|
|
58e38bdf65 | ||
|
|
d5bbcd1f31 | ||
|
|
8766c0273c | ||
|
|
3b4d6ab3f7 | ||
|
|
cf6463ace7 | ||
|
|
711f9f4c11 | ||
|
|
26d52b0f95 | ||
|
|
08f945ffb5 | ||
|
|
20c2e96655 | ||
|
|
3d94ffe452 | ||
|
|
0a94a3f289 | ||
|
|
610f7ab40a | ||
|
|
5f490d7bfe | ||
|
|
9d340ec341 | ||
|
|
359dc5858e | ||
|
|
a4d4f2fd4e | ||
|
|
1945793e00 | ||
|
|
0eebb6eddc | ||
|
|
d628a98f09 | ||
|
|
a8fad21e99 | ||
|
|
b39680c41a | ||
|
|
806b3c2bed | ||
|
|
0c6dca2fae | ||
|
|
fc0be86182 | ||
|
|
e01c9f1875 | ||
|
|
4b46d96538 | ||
|
|
f3178366b9 | ||
|
|
2d99adb108 | ||
|
|
cf92c1323f | ||
|
|
9f0d80cf4a | ||
|
|
a0ad695cb2 | ||
|
|
6590d25a32 | ||
|
|
53eb669432 | ||
|
|
d78f4255d4 | ||
|
|
7d7f028753 | ||
|
|
b65bf69213 | ||
|
|
a3c7cf08f9 | ||
|
|
57d04820df | ||
|
|
9982004913 | ||
|
|
cf34d31133 | ||
|
|
a691b23458 | ||
|
|
2277e75297 | ||
|
|
12ab597986 | ||
|
|
d6be065572 | ||
|
|
a2b301831d | ||
|
|
c19043f67e | ||
|
|
372dc5ca15 | ||
|
|
f477f795cb | ||
|
|
76a67730ff | ||
|
|
12e9084dc1 | ||
|
|
818ac6b078 | ||
|
|
e9a2a44fe9 | ||
|
|
a2654ec431 | ||
|
|
9f0e5098a5 | ||
|
|
e95426f169 | ||
|
|
f7591a6a01 | ||
|
|
80d2c0c2c5 | ||
|
|
0956684529 | ||
|
|
cf659f240b | ||
|
|
1938152648 | ||
|
|
5d7d4aec16 | ||
|
|
c0ab6e5c0b | ||
|
|
31424b7cf1 | ||
|
|
fd27b20349 | ||
|
|
ca89968a23 | ||
|
|
bcb0152876 | ||
|
|
669204ee2b | ||
|
|
954de248ac | ||
|
|
6c01da6278 | ||
|
|
8820b088de | ||
|
|
9a1d03c7ca | ||
|
|
18ab62a8ec | ||
|
|
52c9bad946 | ||
|
|
5d3ed2ae7e | ||
|
|
80088fa60b | ||
|
|
c4fb579695 | ||
|
|
3ed7284f2d | ||
|
|
d9cf1870be | ||
|
|
240d68fcfe | ||
|
|
bbc2e4b657 | ||
|
|
4b77dee052 | ||
|
|
67107ac34f | ||
|
|
e14a779719 | ||
|
|
d38bea3422 | ||
|
|
186eab1cb8 | ||
|
|
6023692b1e | ||
|
|
b1c902f026 | ||
|
|
e8df20d238 | ||
|
|
fecdc81eec | ||
|
|
45b6604b20 | ||
|
|
e30ad80fd7 | ||
|
|
e0f96a1a12 | ||
|
|
468fde90e8 | ||
|
|
1416f76aa1 | ||
|
|
10d4846a57 | ||
|
|
61d4472344 | ||
|
|
016c1d914c | ||
|
|
da3416d807 | ||
|
|
cc78da97c0 | ||
|
|
ea40a3a1e1 | ||
|
|
15e50990bb | ||
|
|
69ee2ecc01 | ||
|
|
2bc46baa92 | ||
|
|
3b83d12b08 | ||
|
|
e152332a15 | ||
|
|
1fa7f1c488 | ||
|
|
9c9074e14e | ||
|
|
f7b41cca4f | ||
|
|
c55b094880 | ||
|
|
2c192444c9 | ||
|
|
553d2c2e7e | ||
|
|
040ce199a1 | ||
|
|
d4c345d6c4 | ||
|
|
2ada83079a | ||
|
|
5a081c0a70 | ||
|
|
7e42b95f23 | ||
|
|
3259ab6e5d | ||
|
|
a01dff908b | ||
|
|
44f91a02b2 | ||
|
|
330f0189c9 | ||
|
|
7d516e25ea | ||
|
|
8bed7cfb99 | ||
|
|
8415bd7bb3 | ||
|
|
5ce06fc71f | ||
|
|
e56f006ed6 | ||
|
|
3de52ac388 | ||
|
|
6f1e654370 | ||
|
|
9e2a04a439 | ||
|
|
c6a0ff11a0 | ||
|
|
5851a8e505 | ||
|
|
efbf6e80a5 | ||
|
|
b393d73bb0 | ||
|
|
00df7c22d9 | ||
|
|
034e2535a9 | ||
|
|
52e53a76df | ||
|
|
cd959bae99 | ||
|
|
13ec8f614c | ||
|
|
cca14f64fe | ||
|
|
e853ec9a8c | ||
|
|
feaf800060 | ||
|
|
4c4ca5b188 | ||
|
|
162e2bb2e4 | ||
|
|
6900fb5ac8 | ||
|
|
bbb02c93e9 | ||
|
|
960b1d51b7 | ||
|
|
4b29d02629 | ||
|
|
bee23322f1 | ||
|
|
149f5641b9 | ||
|
|
b97627967c | ||
|
|
7071ba378a | ||
|
|
6cb3e672e8 | ||
|
|
6b9c0cfcba | ||
|
|
d1c4e5bec1 | ||
|
|
20a51f24a8 | ||
|
|
0f35d6d7b0 | ||
|
|
a464960ed3 | ||
|
|
9309bf1bcf | ||
|
|
d1044bc827 | ||
|
|
28b812d6cb | ||
|
|
749db7f034 | ||
|
|
0943ca5472 | ||
|
|
f9448cddc4 | ||
|
|
0978a6e902 | ||
|
|
0ef27309b7 | ||
|
|
8fc0376721 | ||
|
|
740251a920 | ||
|
|
71efc559a0 | ||
|
|
19e8a86fe8 | ||
|
|
259d91709d | ||
|
|
36ee3caf7a | ||
|
|
fac3a894fc | ||
|
|
b74298c109 | ||
|
|
37b0f9c36a | ||
|
|
ca8daad199 | ||
|
|
0f663d2471 | ||
|
|
00f113eceb | ||
|
|
e6192de0a6 | ||
|
|
4402e65401 | ||
|
|
74101b3cda | ||
|
|
713319e284 | ||
|
|
ee17f293b6 | ||
|
|
51fe42c5d1 | ||
|
|
2cbca389f5 | ||
|
|
29c720eaf7 | ||
|
|
d02b4c0d66 | ||
|
|
e4b6c8ca6a | ||
|
|
1b6935b227 | ||
|
|
3f8af15a76 | ||
|
|
cd8427d018 | ||
|
|
6b00d5627b | ||
|
|
458fcfc340 | ||
|
|
6ed812057c | ||
|
|
ea24b952cd | ||
|
|
84d9ce0fad | ||
|
|
dcbb091677 | ||
|
|
07ec7c8af0 | ||
|
|
cb02250b5b | ||
|
|
2c93f0b6e1 | ||
|
|
5e18eb021b | ||
|
|
5c2ea1ab11 | ||
|
|
23426d797e | ||
|
|
90080ce094 | ||
|
|
64be95b163 | ||
|
|
b12b405c17 | ||
|
|
aac6da8012 | ||
|
|
00ef136649 | ||
|
|
59695669f1 | ||
|
|
a7a490a490 | ||
|
|
f72dc30928 | ||
|
|
fcfe4b59df | ||
|
|
4e910a86e8 | ||
|
|
1c5ed702c9 | ||
|
|
785322d17f | ||
|
|
dde879bc4f | ||
|
|
060b2fea4e | ||
|
|
a91eab67d8 | ||
|
|
d5ef408e20 | ||
|
|
79bce0c881 | ||
|
|
bc428e407a | ||
|
|
f6312ad75e |
@@ -1,4 +1,4 @@
|
||||
ARG UBUNTU_VERSION=24.04
|
||||
ARG UBUNTU_VERSION=26.04
|
||||
ARG BUILD_DATE=N/A
|
||||
ARG APP_VERSION=N/A
|
||||
ARG APP_REVISION=N/A
|
||||
@@ -22,9 +22,7 @@ FROM docker.io/ubuntu:$UBUNTU_VERSION AS build
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gcc-14 g++-14 build-essential git cmake libssl-dev
|
||||
|
||||
ENV CC=gcc-14 CXX=g++-14
|
||||
apt-get install -y build-essential git cmake libssl-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ RUN apt-get update \
|
||||
&& find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \
|
||||
&& find /var/cache -type f -delete
|
||||
|
||||
# Some hosts (e.g. GKE) only mount the driver here, and CUDA 12.8.1-12.9.1 base images don't set these paths
|
||||
ENV LD_LIBRARY_PATH=/usr/local/nvidia/lib64:/usr/local/nvidia/lib:${LD_LIBRARY_PATH}
|
||||
|
||||
COPY --from=build /app/lib/ /app
|
||||
|
||||
### Full
|
||||
|
||||
+15
-13
@@ -1,4 +1,4 @@
|
||||
ARG ONEAPI_VERSION=2026.1.1-devel-ubuntu24.04
|
||||
ARG ONEAPI_VERSION=2026.2.0-devel-ubuntu24.04
|
||||
ARG BUILD_DATE=N/A
|
||||
ARG APP_VERSION=N/A
|
||||
ARG APP_REVISION=N/A
|
||||
@@ -22,20 +22,20 @@ RUN LLAMA_BUILD_NUMBER="$APP_VERSION" npm run build
|
||||
FROM docker.io/intel/deep-learning-essentials:$ONEAPI_VERSION AS build
|
||||
|
||||
RUN echo "Download oneDNN installation package" && \
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/94c3dbac-0852-45be-a57d-21c204cada3e/intel-onednn-2026.0.2.46_offline.sh -O intel-onednn_offline.sh && \
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/46452a4a-7b7f-49f2-be4c-602a900180bb/intel-onednn-2026.1.0.625_offline.sh -O intel-onednn_offline.sh && \
|
||||
echo "Install oneDNN components" && \
|
||||
bash intel-onednn_offline.sh -s -a --silent --eula accept
|
||||
|
||||
ARG GGML_SYCL_F16=ON
|
||||
ARG LEVEL_ZERO_VERSION=1.28.2
|
||||
ARG LEVEL_ZERO_VERSION=1.34.0
|
||||
ARG LEVEL_ZERO_UBUNTU_VERSION=u24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y git libssl-dev wget ca-certificates && \
|
||||
cd /tmp && \
|
||||
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero.deb && \
|
||||
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero-devel_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero-devel.deb && \
|
||||
apt-get -o Dpkg::Options::="--force-overwrite" install -y ./level-zero.deb ./level-zero-devel.deb && \
|
||||
rm -f /tmp/level-zero.deb /tmp/level-zero-devel.deb
|
||||
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/libze1_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O libze1.deb && \
|
||||
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/libze-dev_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O libze-dev.deb && \
|
||||
apt-get -o Dpkg::Options::="--force-overwrite" install -y ./libze1.deb ./libze-dev.deb && \
|
||||
rm -f /tmp/libze1.deb /tmp/libze-dev.deb
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -64,7 +64,7 @@ RUN mkdir -p /app/full \
|
||||
&& cp requirements.txt /app/full \
|
||||
&& cp .devops/tools.sh /app/full/tools.sh
|
||||
|
||||
FROM docker.io/intel/oneapi-toolkit:$ONEAPI_VERSION AS base
|
||||
FROM docker.io/intel/deep-learning-essentials:$ONEAPI_VERSION AS base
|
||||
|
||||
ARG BUILD_DATE=N/A
|
||||
ARG APP_VERSION=N/A
|
||||
@@ -79,6 +79,8 @@ LABEL org.opencontainers.image.created=$BUILD_DATE \
|
||||
org.opencontainers.image.url=$IMAGE_URL \
|
||||
org.opencontainers.image.source=$IMAGE_SOURCE
|
||||
|
||||
COPY --from=build /opt/intel/oneapi/dnnl /opt/intel/oneapi/dnnl
|
||||
|
||||
#Following versions are for multiple GPUs, since 26.x has known issue:
|
||||
# https://github.com/ggml-org/llama.cpp/issues/21747,
|
||||
# https://github.com/intel/compute-runtime/issues/921.
|
||||
@@ -89,11 +91,11 @@ LABEL org.opencontainers.image.created=$BUILD_DATE \
|
||||
#ARG IGDGMM_VERSION=22.8.2
|
||||
|
||||
|
||||
ARG IGC_VERSION=v2.34.4
|
||||
ARG IGC_VERSION_FULL=2_2.34.4+21428
|
||||
ARG COMPUTE_RUNTIME_VERSION=26.18.38308.1
|
||||
ARG COMPUTE_RUNTIME_VERSION_FULL=26.18.38308.1-0
|
||||
ARG IGDGMM_VERSION=22.10.0
|
||||
ARG IGC_VERSION=v2.42.6
|
||||
ARG IGC_VERSION_FULL=2_2.42.6+23070
|
||||
ARG COMPUTE_RUNTIME_VERSION=26.40.40196.8
|
||||
ARG COMPUTE_RUNTIME_VERSION_FULL=26.40.40196.8-0
|
||||
ARG IGDGMM_VERSION=22.10.2
|
||||
RUN mkdir /tmp/neo/ && cd /tmp/neo/ \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/$IGC_VERSION/intel-igc-core-${IGC_VERSION_FULL}_amd64.deb \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/$IGC_VERSION/intel-igc-opencl-${IGC_VERSION_FULL}_amd64.deb \
|
||||
|
||||
@@ -82,20 +82,14 @@ COPY --from=build /app/full /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PATH="/root/.venv/bin:/root/.local/bin:${PATH}"
|
||||
|
||||
# Flag for compatibility with pip
|
||||
ARG UV_INDEX_STRATEGY="unsafe-best-match"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
ca-certificates \
|
||||
&& curl -LsSf https://astral.sh/uv/install.sh | sh \
|
||||
&& uv python install 3.13 \
|
||||
&& uv venv --python 3.13 /root/.venv \
|
||||
&& uv pip install --python /root/.venv/bin/python -r requirements.txt \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
&& pip install --break-system-packages --upgrade setuptools \
|
||||
&& pip install --break-system-packages -r requirements.txt \
|
||||
&& apt autoremove -y \
|
||||
&& apt clean -y \
|
||||
&& rm -rf /tmp/* /var/tmp/* \
|
||||
|
||||
@@ -100,56 +100,56 @@ runs:
|
||||
echo "CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.1" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
echo "CUDA_PATH_V13_1=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.1" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
- name: Install Cuda Toolkit 13.4 for x64
|
||||
if: ${{ inputs.cuda_version == '13.4' && inputs.cuda_arch == 'x64' }}
|
||||
- name: Install Cuda Toolkit 13.5 for x64
|
||||
if: ${{ inputs.cuda_version == '13.5' && inputs.cuda_arch == 'x64' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir -p "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4"
|
||||
mkdir -p "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5"
|
||||
choco install unzip -y
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_crt/windows-x86_64/cuda_crt-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/windows-x86_64/cuda_cudart-windows-x86_64-13.4.49-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvrtc/windows-x86_64/cuda_nvrtc-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libcublas/windows-x86_64/libcublas-windows-x86_64-13.7.0.27-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libnvvm/windows-x86_64/libnvvm-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvtx/windows-x86_64/cuda_nvtx-windows-x86_64-13.4.49-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_profiler_api/windows-x86_64/cuda_profiler_api-windows-x86_64-13.4.49-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/visual_studio_integration/windows-x86_64/visual_studio_integration-windows-x86_64-13.4.49-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cccl/windows-x86_64/cccl-windows-x86_64-13.3.4.2.1-archive.zip"
|
||||
unzip '*.zip' -d "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4"
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_crt-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_cudart-windows-x86_64-13.4.49-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_nvcc-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_nvrtc-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\libcublas-windows-x86_64-13.7.0.27-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\libnvvm-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_nvtx-windows-x86_64-13.4.49-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_profiler_api-windows-x86_64-13.4.49-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\visual_studio_integration-windows-x86_64-13.4.49-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cccl-windows-x86_64-13.3.4.2.1-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
echo "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
echo "CUDA_PATH_V13_4=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_crt/windows-x86_64/cuda_crt-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/windows-x86_64/cuda_cudart-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvrtc/windows-x86_64/cuda_nvrtc-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libcublas/windows-x86_64/libcublas-windows-x86_64-13.10.0.20-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libnvvm/windows-x86_64/libnvvm-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvtx/windows-x86_64/cuda_nvtx-windows-x86_64-13.5.25-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_profiler_api/windows-x86_64/cuda_profiler_api-windows-x86_64-13.5.25-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/visual_studio_integration/windows-x86_64/visual_studio_integration-windows-x86_64-13.5.25-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cccl/windows-x86_64/cccl-windows-x86_64-13.3.5.0.6-archive.zip"
|
||||
unzip '*.zip' -d "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5"
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_crt-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_cudart-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_nvcc-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_nvrtc-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\libcublas-windows-x86_64-13.10.0.20-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\libnvvm-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_nvtx-windows-x86_64-13.5.25-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_profiler_api-windows-x86_64-13.5.25-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\visual_studio_integration-windows-x86_64-13.5.25-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cccl-windows-x86_64-13.3.5.0.6-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
echo "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
echo "CUDA_PATH_V13_5=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
- name: Install Cuda Toolkit 13.4 for ARM64
|
||||
if: ${{ inputs.cuda_version == '13.4' && inputs.cuda_arch == 'arm64' }}
|
||||
- name: Install Cuda Toolkit 13.5 for ARM64
|
||||
if: ${{ inputs.cuda_version == '13.5' && inputs.cuda_arch == 'arm64' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir -p "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4"
|
||||
mkdir -p "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5"
|
||||
choco install unzip -y
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cccl/windows-x86_64/cccl-windows-x86_64-13.3.4.2.1-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_crt/windows-x86_64/cuda_crt-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libnvvm/windows-x86_64/libnvvm-windows-x86_64-13.4.59-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/windows-arm64/cuda_cudart-windows-arm64-13.4.49-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libcublas/windows-arm64/libcublas-windows-arm64-13.7.0.27-archive.zip"
|
||||
unzip '*.zip' -d "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4"
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cccl-windows-x86_64-13.3.4.2.1-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_crt-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_nvcc-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\libnvvm-windows-x86_64-13.4.59-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\cuda_cudart-windows-arm64-13.4.49-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\libcublas-windows-arm64-13.7.0.27-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" /E /I /H /Y
|
||||
echo "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
echo "CUDA_PATH_V13_4=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cccl/windows-x86_64/cccl-windows-x86_64-13.3.5.0.6-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_crt/windows-x86_64/cuda_crt-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libnvvm/windows-x86_64/libnvvm-windows-x86_64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/windows-arm64/cuda_cudart-windows-arm64-13.5.37-archive.zip"
|
||||
curl -O "https://developer.download.nvidia.com/compute/cuda/redist/libcublas/windows-arm64/libcublas-windows-arm64-13.10.0.20-archive.zip"
|
||||
unzip '*.zip' -d "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5"
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cccl-windows-x86_64-13.3.5.0.6-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_crt-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_nvcc-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\libnvvm-windows-x86_64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\cuda_cudart-windows-arm64-13.5.37-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
xcopy "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\libcublas-windows-arm64-13.10.0.20-archive\*" "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" /E /I /H /Y
|
||||
echo "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
echo "CUDA_PATH_V13_5=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
@@ -31,8 +31,8 @@ env:
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
jobs:
|
||||
ubuntu-24-llguidance:
|
||||
runs-on: ${{ 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
ubuntu-26-llguidance:
|
||||
runs-on: ${{ 'ubuntu-26.04-arm' || 'ubuntu-26.04' }}
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
@@ -33,7 +33,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
android-ndk-snapdragon:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container:
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
|
||||
defaults:
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
path: pkg-snapdragon/llama.cpp
|
||||
|
||||
linux-iot-snapdragon:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container:
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
|
||||
defaults:
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
test-snapdragon-qdc:
|
||||
name: Test on QDC Device (${{ matrix.device }})
|
||||
needs: [android-ndk-snapdragon, linux-iot-snapdragon]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 180
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -37,7 +37,7 @@ env:
|
||||
|
||||
jobs:
|
||||
default:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
uses: android-actions/setup-android@be39fa834029ff78f1a44aa3bb0819b8fc2bd8fd # v4.0.4
|
||||
with:
|
||||
log-accepted-android-sdk-licenses: false
|
||||
packages: platform-tools cmake;3.31.6
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
@@ -63,7 +64,7 @@ jobs:
|
||||
./gradlew build --no-daemon
|
||||
|
||||
ndk:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container:
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.3'
|
||||
defaults:
|
||||
@@ -101,7 +102,7 @@ jobs:
|
||||
path: pkg-adb/llama.cpp
|
||||
|
||||
arm64:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
env:
|
||||
NDK_VERSION: "29.0.14206865"
|
||||
|
||||
@@ -40,8 +40,8 @@ jobs:
|
||||
# path: ./spacemit_toolchain
|
||||
# version: ${{ env.SPACEMIT_IME_TOOLCHAIN_VERSION }}
|
||||
|
||||
ubuntu-24-openvino-cache:
|
||||
runs-on: ubuntu-24.04
|
||||
ubuntu-26-openvino-cache:
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
env:
|
||||
# Sync versions in build-openvino.yml, ci-self-hosted-openvino.yml, release.yml, release-publish.yml, build-cache.yml, .devops/openvino.Dockerfile, docs/backend/OPENVINO.md
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
name: Build relocatable cmake package
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
cache-mode: none
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: [self-hosted, Linux, fast]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
PREFIX="$(pwd)"/inst
|
||||
cmake -S . -B build \
|
||||
-DCMAKE_PREFIX_PATH="$PREFIX" \
|
||||
-DLLAMA_OPENSSL=OFF \
|
||||
-DLLAMA_BUILD_TESTS=OFF \
|
||||
-DLLAMA_BUILD_TOOLS=OFF \
|
||||
-DLLAMA_BUILD_EXAMPLES=OFF \
|
||||
-DLLAMA_BUILD_APP=OFF \
|
||||
-DLLAMA_BUILD_IS_DEV=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
cmake --install build --prefix "$PREFIX" --config Release
|
||||
|
||||
export LLAMA_CONFIG="$PREFIX"/lib/cmake/llama/llama-config.cmake
|
||||
build_commit=$(git rev-parse --short HEAD | xargs)
|
||||
build_number=$(git rev-list --count HEAD | xargs)
|
||||
|
||||
major=$(grep -oE "set\(LLAMA_VERSION_MAJOR[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
minor=$(grep -oE "set\(LLAMA_VERSION_MINOR[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
patch=$(grep -oE "set\(LLAMA_VERSION_PATCH[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
build_version="$major.$minor.$patch"
|
||||
|
||||
checks=("set\(LLAMA_VERSION[[:space:]]+$build_version\)"
|
||||
"set\(LLAMA_BUILD_COMMIT[[:space:]]+$build_commit\)"
|
||||
"set\(LLAMA_BUILD_NUMBER[[:space:]]+$build_number\)")
|
||||
|
||||
for check in "${checks[@]}"; do
|
||||
if ! grep -qE "$check" "$LLAMA_CONFIG"; then
|
||||
echo "Checking llama-config.cmake version... \"$check\" failed!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Checking llama-config.cmake version... success."
|
||||
|
||||
cd examples/simple-cmake-pkg
|
||||
cmake -S . -B build -DCMAKE_PREFIX_PATH="$PREFIX"/lib/cmake
|
||||
cmake --build build -j $(nproc)
|
||||
@@ -7,7 +7,6 @@ on:
|
||||
- master
|
||||
paths: [
|
||||
'.github/workflows/build-cpu.yml',
|
||||
'.github/workflows/build-cmake-pkg.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
@@ -20,7 +19,6 @@ on:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths: [
|
||||
'.github/workflows/build-cpu.yml',
|
||||
'.github/workflows/build-cmake-pkg.yml',
|
||||
'ggml/src/ggml-rpc/**',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
@@ -47,17 +45,14 @@ env:
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
jobs:
|
||||
build-cmake-pkg:
|
||||
uses: ./.github/workflows/build-cmake-pkg.yml
|
||||
|
||||
ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build: 'x64'
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-26.04
|
||||
- build: 'arm64'
|
||||
os: ubuntu-24.04-arm
|
||||
os: ubuntu-26.04-arm
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -81,13 +76,6 @@ jobs:
|
||||
libjpeg-dev build-essential libssl-dev \
|
||||
git-lfs
|
||||
|
||||
- name: Toolchain workaround (GCC 14)
|
||||
if: ${{ contains(matrix.os, 'ubuntu-24.04') }}
|
||||
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: |
|
||||
@@ -242,3 +230,79 @@ jobs:
|
||||
older: 5m
|
||||
min: 1
|
||||
dry-run: ${{ github.event_name != 'push' || github.ref != 'refs/heads/master' }}
|
||||
|
||||
build-cmake-pkg:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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: build-cmake-pkg
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
PREFIX="$(pwd)"/inst
|
||||
cmake -S . -B build \
|
||||
-DCMAKE_PREFIX_PATH="$PREFIX" \
|
||||
-DLLAMA_OPENSSL=OFF \
|
||||
-DLLAMA_BUILD_TESTS=OFF \
|
||||
-DLLAMA_BUILD_TOOLS=OFF \
|
||||
-DLLAMA_BUILD_EXAMPLES=OFF \
|
||||
-DLLAMA_BUILD_APP=OFF \
|
||||
-DLLAMA_BUILD_IS_DEV=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
cmake --install build --prefix "$PREFIX" --config Release
|
||||
|
||||
export LLAMA_CONFIG="$PREFIX"/lib/cmake/llama/llama-config.cmake
|
||||
build_commit=$(git rev-parse --short HEAD | xargs)
|
||||
build_number=$(git rev-list --count HEAD | xargs)
|
||||
|
||||
major=$(grep -oE "set\(LLAMA_VERSION_MAJOR[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
minor=$(grep -oE "set\(LLAMA_VERSION_MINOR[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
patch=$(grep -oE "set\(LLAMA_VERSION_PATCH[[:space:]]+[0-9]+" CMakeLists.txt | grep -oE "[0-9]+$")
|
||||
build_version="$major.$minor.$patch"
|
||||
|
||||
checks=("set\(LLAMA_VERSION[[:space:]]+$build_version\)"
|
||||
"set\(LLAMA_BUILD_COMMIT[[:space:]]+$build_commit\)"
|
||||
"set\(LLAMA_BUILD_NUMBER[[:space:]]+$build_number\)")
|
||||
|
||||
for check in "${checks[@]}"; do
|
||||
if ! grep -qE "$check" "$LLAMA_CONFIG"; then
|
||||
echo "Checking llama-config.cmake version... \"$check\" failed!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Checking llama-config.cmake version... success."
|
||||
|
||||
cd examples/simple-cmake-pkg
|
||||
cmake -S . -B build -DCMAKE_PREFIX_PATH="$PREFIX"/lib/cmake
|
||||
cmake --build build -j $(nproc)
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: build-cmake-pkg
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
# cmake --build build --config Release -j $(nproc)
|
||||
|
||||
debian-13-loongarch64-cpu-cross:
|
||||
runs-on: ${{ 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ 'ubuntu-26.04-arm' || 'ubuntu-26.04' }}
|
||||
container: debian@sha256:653dfb9f86c3782e8369d5f7d29bb8faba1f4bff9025db46e807fa4c22903671
|
||||
|
||||
steps:
|
||||
@@ -218,7 +218,7 @@ jobs:
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
debian-13-loongarch64-vulkan-cross:
|
||||
runs-on: ${{ 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ 'ubuntu-26.04-arm' || 'ubuntu-26.04' }}
|
||||
container: debian@sha256:653dfb9f86c3782e8369d5f7d29bb8faba1f4bff9025db46e807fa4c22903671
|
||||
|
||||
steps:
|
||||
@@ -276,8 +276,8 @@ jobs:
|
||||
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
ubuntu-24-riscv64-cpu-spacemit-ime-cross:
|
||||
runs-on: ubuntu-24.04
|
||||
ubuntu-26-riscv64-cpu-spacemit-ime-cross:
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
env:
|
||||
# Make sure this is in sync with build-cache.yml
|
||||
|
||||
@@ -40,12 +40,12 @@ jobs:
|
||||
- cuda: '12.4'
|
||||
arch: x64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
- cuda: '13.4'
|
||||
- cuda: '13.5'
|
||||
arch: x64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
- cuda: '13.4'
|
||||
defines: ''
|
||||
- cuda: '13.5'
|
||||
arch: arm64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3 -DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
|
||||
defines: '-DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
run: |
|
||||
cd build
|
||||
echo "Fetch llama2c big-endian model"
|
||||
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
|
||||
wget https://huggingface.co/ggml-org-dev/stories260K/resolve/main/stories260K-be.gguf
|
||||
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
|
||||
|
||||
- name: Test quantize (s390x)
|
||||
|
||||
@@ -37,7 +37,7 @@ env:
|
||||
LLAMA_ARG_LOG_PREFIX: 1
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
# TODO: fix failing tests on OpenVINO backend
|
||||
CTEST_EXCLUDE: "test-llama-archs|test-llama-context"
|
||||
CTEST_EXCLUDE: "test-llama-archs|test-llama-context|test-fp8-kv"
|
||||
|
||||
jobs:
|
||||
ubuntu-24-openvino:
|
||||
|
||||
@@ -43,13 +43,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# thread and address doesn't run properly on some self hosted machines, so run it on Github instead
|
||||
- sanitizer: ADDRESS
|
||||
machine: ubuntu-24.04
|
||||
machine: ubuntu-26.04
|
||||
- sanitizer: THREAD
|
||||
machine: ubuntu-24.04
|
||||
machine: ubuntu-26.04
|
||||
- sanitizer: UNDEFINED
|
||||
machine: [self-hosted, X64, Linux, CPU]
|
||||
machine: ubuntu-26.04
|
||||
|
||||
runs-on: ${{ matrix.machine }}
|
||||
|
||||
@@ -58,31 +57,24 @@ jobs:
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# - name: ccache
|
||||
# uses: ggml-org/ccache-action@v1.2.24
|
||||
# if: ${{ matrix.sanitizer != 'UNDEFINED' }}
|
||||
# with:
|
||||
# key: ctest-${{ matrix.sanitizer }}-ubuntu-24.04
|
||||
# variant: ccache
|
||||
# evict-old-files: 1d
|
||||
# save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
# with UNDEFINED sanitizer, we have to build in Debug to avoid GCC 13 false-positive warnings
|
||||
- name: Build (undefined)
|
||||
id: cmake_build_undefined
|
||||
if: ${{ matrix.sanitizer == 'UNDEFINED' }}
|
||||
run: |
|
||||
cmake -B build \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
-DLLAMA_SANITIZE_${{ matrix.sanitizer }}=ON \
|
||||
-DGGML_SANITIZE_${{ matrix.sanitizer }}=ON
|
||||
|
||||
cmake --build build --config Debug -j $(nproc)
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: ctest-sanitizer-${{ matrix.sanitizer }}
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
if: ${{ matrix.sanitizer == 'ADDRESS' }}
|
||||
if: ${{ matrix.sanitizer == 'ADDRESS' || matrix.sanitizer == 'UNDEFINED' }}
|
||||
run: |
|
||||
cmake -B build \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
@@ -103,6 +95,18 @@ jobs:
|
||||
|
||||
cmake --build build --config RelWithDebInfo -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: ctest-sanitizer-${{ matrix.sanitizer }}
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
run: |
|
||||
|
||||
@@ -38,7 +38,7 @@ env:
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
jobs:
|
||||
ubuntu-24-sycl:
|
||||
ubuntu-26-sycl:
|
||||
strategy:
|
||||
matrix:
|
||||
build: [fp32, fp16]
|
||||
@@ -48,12 +48,12 @@ jobs:
|
||||
- build: fp16
|
||||
fp16: ON
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
env:
|
||||
ONEAPI_ROOT: /opt/intel/oneapi/
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
LEVEL_ZERO_VERSION: "1.33.1"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.2"
|
||||
LEVEL_ZERO_VERSION: "1.34.0"
|
||||
LEVEL_ZERO_UBUNTU_VERSION: "u24.04"
|
||||
|
||||
continue-on-error: true
|
||||
@@ -68,12 +68,12 @@ jobs:
|
||||
run: |
|
||||
cd /tmp
|
||||
echo "Download oneAPI Deep Learning Essentials installation package"
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b5c100a7-8052-4e88-90e2-63d9916a998b/intel-deep-learning-essentials-2026.1.4.17_offline.sh -O intel-deep-learning-essentials_offline.sh
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/aef232d2-aac5-4cd6-bceb-911019bbb6a8/intel-deep-learning-essentials-2026.2.0.640_offline.sh -O intel-deep-learning-essentials_offline.sh
|
||||
echo "Install oneAPI Deep Learning Essentials components"
|
||||
sudo bash intel-deep-learning-essentials_offline.sh -s -a --silent --eula accept
|
||||
|
||||
echo "Download oneDNN installation package"
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/94c3dbac-0852-45be-a57d-21c204cada3e/intel-onednn-2026.0.2.46_offline.sh -O intel-onednn_offline.sh
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/46452a4a-7b7f-49f2-be4c-602a900180bb/intel-onednn-2026.1.0.625_offline.sh -O intel-onednn_offline.sh
|
||||
echo "Install oneDNN components"
|
||||
sudo bash intel-onednn_offline.sh -s -a --silent --eula accept
|
||||
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: sycl-ubuntu-24-${{ matrix.build }}
|
||||
key: sycl-ubuntu-26-${{ matrix.build }}
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: sycl-ubuntu-24-${{ matrix.build }}
|
||||
key: sycl-ubuntu-26-${{ matrix.build }}
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
@@ -150,13 +150,13 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
WINDOWS_DLE_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/a51104eb-6f4a-4a0e-9dae-1b81b0c81ae1/intel-deep-learning-essentials-2026.1.4.16_offline.exe
|
||||
WINDOWS_ONEDNN_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/34e28083-d04b-46a3-826c-8c1d00bf1e1c/intel-onednn-2026.0.2.47_offline.exe
|
||||
WINDOWS_DLE_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b177a006-5cd9-4cb2-807f-8199cc73d53c/intel-deep-learning-essentials-2026.2.0.638_offline.exe
|
||||
WINDOWS_ONEDNN_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/8f905666-c85c-43fb-9bd0-2618c7ecd33b/intel-onednn-2026.1.0.622_offline.exe
|
||||
WINDOWS_DPCPP_MKL: intel.oneapi.win.cpp-dpcpp-common:intel.oneapi.win.mkl.devel:intel.oneapi.win.tbb.devel
|
||||
WINDOWS_DNNL: intel.oneapi.win.dnnl
|
||||
LEVEL_ZERO_SDK_URL: https://github.com/oneapi-src/level-zero/releases/download/v1.33.1/level-zero-win-sdk-1.33.1.zip
|
||||
LEVEL_ZERO_SDK_URL: https://github.com/oneapi-src/level-zero/releases/download/v1.34.0/level-zero-win-sdk-1.34.0.zip
|
||||
ONEAPI_ROOT: "C:/Program Files (x86)/Intel/oneAPI"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.2"
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
|
||||
@@ -31,8 +31,8 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ubuntu-24-virtgpu:
|
||||
runs-on: ${{ 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
ubuntu-26-virtgpu:
|
||||
runs-on: ${{ 'ubuntu-26.04-arm' || 'ubuntu-26.04' }}
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
@@ -42,7 +42,7 @@ env:
|
||||
|
||||
jobs:
|
||||
ubuntu-arm64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -53,9 +53,7 @@ jobs:
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-14 g++-14 build-essential glslc libvulkan-dev spirv-headers libssl-dev ninja-build
|
||||
echo "CC=gcc-14" >> "$GITHUB_ENV"
|
||||
echo "CXX=g++-14" >> "$GITHUB_ENV"
|
||||
sudo apt-get install -y build-essential glslc libvulkan-dev spirv-headers libssl-dev ninja-build
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
@@ -69,7 +67,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: vulkan-ubuntu-24.04-arm
|
||||
key: vulkan-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -92,14 +90,14 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: vulkan-ubuntu-24.04-arm
|
||||
key: vulkan-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
ubuntu-llvmpipe:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -137,7 +135,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: vulkan-ubuntu-24.04-llvmpipe
|
||||
key: vulkan-ubuntu-26.04-llvmpipe
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -156,7 +154,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: vulkan-ubuntu-24.04-llvmpipe
|
||||
key: vulkan-ubuntu-26.04-llvmpipe
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -49,7 +49,7 @@ env:
|
||||
|
||||
jobs:
|
||||
ubuntu-webgpu:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasi
|
||||
key: webgpu-ubuntu-26.04-arm-wasi
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasi
|
||||
key: webgpu-ubuntu-26.04-arm-wasi
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -50,7 +50,7 @@ env:
|
||||
|
||||
jobs:
|
||||
ubuntu-webgpu:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasm
|
||||
key: webgpu-ubuntu-26.04-arm-wasm
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasm
|
||||
key: webgpu-ubuntu-26.04-arm-wasm
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -34,7 +34,7 @@ env:
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key |
|
||||
sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
|
||||
sudo add-apt-repository -y \
|
||||
"deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main"
|
||||
"deb http://apt.llvm.org/resolute/ llvm-toolchain-resolute-22 main"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-22
|
||||
|
||||
|
||||
@@ -46,18 +46,55 @@ env:
|
||||
|
||||
jobs:
|
||||
cpu-x64-high-perf:
|
||||
runs-on: [self-hosted, Linux, X64, CPU]
|
||||
runs-on: hf-jobs-cpu-upgrade:ubuntu26_04
|
||||
|
||||
# note: this runner has 8 CPUs, but std::thread::hardware_concurrency reports the cores of the
|
||||
# host (https://github.com/ggml-org/llama.cpp/pull/31439#issuecomment-6683386059)
|
||||
env:
|
||||
TEST_ARG_HARDWARE_CONCURRENCY: 8
|
||||
TEST_ARG_PARALLEL_WORKERS: 4
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential cmake libssl-dev time unzip wget python3 python3-venv python3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
with:
|
||||
key: self-hosted-cpu-x64-high-perf
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- 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/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: self-hosted-cpu-x64-high-perf
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
cpu-arm64-high-perf-graviton4:
|
||||
runs-on: ah-ubuntu_24_04-c8g_8x
|
||||
|
||||
|
||||
@@ -93,13 +93,72 @@ jobs:
|
||||
save: true
|
||||
|
||||
gpu-rocm:
|
||||
runs-on: [self-hosted, Linux, AMD]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Strix Halo (iGPU, RDNA3.5)
|
||||
- gpu: gfx1151
|
||||
runner: [self-hosted, Linux, strix-halo, devlab-dispatch]
|
||||
# async-execution correctness workaround, gfx1151 only (see Test step)
|
||||
hip_launch_blocking: "1"
|
||||
# Radeon AI PRO R9700 (dGPU, RDNA4)
|
||||
- gpu: gfx1201
|
||||
runner: [self-hosted, Linux, radeon-r9700, devlab-dispatch]
|
||||
hip_launch_blocking: "0"
|
||||
# HIP-graph capture fails on gfx1201 (hipMemset -> hipErrorStreamCaptureImplicit)
|
||||
disable_hip_graphs: true
|
||||
|
||||
name: gpu-rocm (${{ matrix.gpu }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 120
|
||||
|
||||
env:
|
||||
ROCM_VERSION: "10.0.0"
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Verify GPU device
|
||||
run: |
|
||||
ls /dev/kfd /dev/dri/renderD* || { echo "ERROR: no amdgpu compute device"; exit 1; }
|
||||
|
||||
# same TheRock wheels as the ubuntu-24-rocm release build, so the runner does not need a ROCm install
|
||||
- name: Setup TheRock with Wheels
|
||||
run: |
|
||||
python3 -m venv "$RUNNER_TEMP/rocm"
|
||||
"$RUNNER_TEMP/rocm/bin/python" -m pip install --index-url https://stable.repo.amd.com/rocm/whl-next/ "rocm[libraries,devel,device-${{ matrix.gpu }}]==${ROCM_VERSION}"
|
||||
ROCM_PATH=$("$RUNNER_TEMP/rocm/bin/rocm-sdk" path --root)
|
||||
{
|
||||
echo "ROCM_PATH=$ROCM_PATH"
|
||||
echo "HIP_PATH=$ROCM_PATH"
|
||||
echo "CMAKE_PREFIX_PATH=$("$RUNNER_TEMP/rocm/bin/rocm-sdk" path --cmake)"
|
||||
echo "LD_LIBRARY_PATH=$ROCM_PATH/lib:${LD_LIBRARY_PATH:-}"
|
||||
} >> "$GITHUB_ENV"
|
||||
"$RUNNER_TEMP/rocm/bin/rocm-sdk" path --bin >> "$GITHUB_PATH"
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
# ROCm is installed again on each run, so hash the compiler content, not its mtime
|
||||
- name: Tune ccache for reinstalled ROCm toolchain
|
||||
run: |
|
||||
ccache --set-config=compiler_check=content
|
||||
ccache --set-config=sloppiness=time_macros,include_file_mtime,include_file_ctime
|
||||
ccache --set-config=depend_mode=true
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
with:
|
||||
key: self-hosted-gpu-rocm-${{ matrix.gpu }}
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Test
|
||||
id: ggml-ci
|
||||
# HIP_LAUNCH_BLOCKING=1: workaround for an async-execution correctness
|
||||
@@ -107,10 +166,25 @@ jobs:
|
||||
# incorrect output (perplexity ~88 vs ~9.4). Serializing kernel launches
|
||||
# restores correctness. Remove once the underlying ROCm/HIP issue is fixed.
|
||||
env:
|
||||
HIP_LAUNCH_BLOCKING: "1"
|
||||
HIP_LAUNCH_BLOCKING: ${{ matrix.hip_launch_blocking }}
|
||||
run: |
|
||||
rocminfo
|
||||
GG_BUILD_ROCM=1 GG_BUILD_AMDGPU_TARGETS=gfx1151 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
# Exported rather than set in env:, because ggml only checks that the
|
||||
# variable is defined: an empty value would disable graphs on gfx1151 too.
|
||||
if [ "${{ matrix.disable_hip_graphs }}" = "true" ]; then export GGML_CUDA_DISABLE_GRAPHS=1; fi
|
||||
GG_BUILD_ROCM=1 GG_BUILD_AMDGPU_TARGETS=${{ matrix.gpu }} bash ./ci/run.sh "$RUNNER_TEMP/results/llama.cpp" "$RUNNER_TEMP/mnt/llama.cpp"
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: self-hosted-gpu-rocm-${{ matrix.gpu }}
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
# TODO: provision AMD GPU machine
|
||||
# amd-rocm:
|
||||
|
||||
@@ -58,12 +58,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
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
|
||||
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
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
|
||||
@@ -16,7 +16,7 @@ cache-mode: none
|
||||
jobs:
|
||||
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
# Set the permissions to the lowest permissions possible needed for your steps.
|
||||
# Copilot will be given its own token for its operations.
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
prepare_matrices:
|
||||
name: Prepare Docker matrices
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
outputs:
|
||||
build_matrix: ${{ steps.matrices.outputs.build_matrix }}
|
||||
merge_matrix: ${{ steps.matrices.outputs.merge_matrix }}
|
||||
@@ -87,19 +87,19 @@ jobs:
|
||||
# Keep all build targets in one place and derive merge targets from it.
|
||||
cat > build-matrix.json <<'JSON'
|
||||
[
|
||||
{ "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-arm" },
|
||||
{ "tag": "cpu", "dockerfile": ".devops/s390x.Dockerfile", "platforms": "linux/s390x", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-s390x", "prebuilt_ui": true },
|
||||
{ "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04-arm" },
|
||||
{ "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.4.1", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.4.1", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04-arm" },
|
||||
{ "tag": "musa", "dockerfile": ".devops/musa.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "intel", "dockerfile": ".devops/intel.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04-arm" },
|
||||
{ "tag": "rocm", "dockerfile": ".devops/rocm.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-24.04" },
|
||||
{ "tag": "openvino", "dockerfile": ".devops/openvino.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-24.04" }
|
||||
{ "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "cpu", "dockerfile": ".devops/cpu.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04-arm" },
|
||||
{ "tag": "cpu", "dockerfile": ".devops/s390x.Dockerfile", "platforms": "linux/s390x", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04-s390x", "prebuilt_ui": true },
|
||||
{ "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "cuda cuda12", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "12.8.1", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04-arm" },
|
||||
{ "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.5.0", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "cuda13", "dockerfile": ".devops/cuda.Dockerfile", "cuda_version": "13.5.0", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04-arm" },
|
||||
{ "tag": "musa", "dockerfile": ".devops/musa.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "intel", "dockerfile": ".devops/intel.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "vulkan", "dockerfile": ".devops/vulkan.Dockerfile", "platforms": "linux/arm64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04-arm" },
|
||||
{ "tag": "rocm", "dockerfile": ".devops/rocm.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": true, "runs_on": "ubuntu-26.04" },
|
||||
{ "tag": "openvino", "dockerfile": ".devops/openvino.Dockerfile", "platforms": "linux/amd64", "full": true, "light": true, "server": true, "free_disk_space": false, "runs_on": "ubuntu-26.04" }
|
||||
]
|
||||
JSON
|
||||
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
merge_arch_tags:
|
||||
name: Create shared tags from digests
|
||||
needs: [prepare_matrices, push_to_registry, create_tag]
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
@@ -24,7 +24,7 @@ permissions:
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -39,7 +39,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
make-release:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -11,7 +11,8 @@ on:
|
||||
'tests/fusion/**',
|
||||
'tests/test-fusion.cpp',
|
||||
'tests/test-llama-archs.cpp',
|
||||
'src/llama-graph.cpp',
|
||||
'src/llama-graph*',
|
||||
'src/llama-kv-cache*',
|
||||
'src/llama-model*',
|
||||
'src/models/**'
|
||||
]
|
||||
@@ -24,7 +25,8 @@ on:
|
||||
'tests/fusion/**',
|
||||
'tests/test-fusion.cpp',
|
||||
'tests/test-llama-archs.cpp',
|
||||
'src/llama-graph.cpp',
|
||||
'src/llama-graph*',
|
||||
'src/llama-kv-cache*',
|
||||
'src/llama-model*',
|
||||
'src/models/**'
|
||||
]
|
||||
@@ -145,6 +147,11 @@ jobs:
|
||||
rm -rf build-ci-models && mkdir -p build-ci-models
|
||||
./build/bin/test-llama-archs -o build-ci-models
|
||||
|
||||
- name: Test KV shift
|
||||
id: test_kv_shift
|
||||
run: |
|
||||
./build/bin/test-llama-archs --kv-shift
|
||||
|
||||
- name: Test fusion
|
||||
id: test_fusion
|
||||
run: |
|
||||
@@ -293,6 +300,11 @@ jobs:
|
||||
# run: |
|
||||
# ./build/bin/test-fusion --models build-ci-models --device Vulkan0 --check tests/fusion/Vulkan.csv
|
||||
|
||||
- name: Test KV shift
|
||||
id: test_kv_shift
|
||||
run: |
|
||||
./build/bin/test-llama-archs --kv-shift
|
||||
|
||||
- name: Test archs
|
||||
id: test_archs
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
name: "PR video demo label"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
cache-mode: none
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
video-demo-label:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Add or remove the video demo label
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
# passed through env, so the body is only matched, never run, and is safe to read from a fork
|
||||
BODY: ${{ github.event.pull_request.body }}
|
||||
HAS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'video demo') }}
|
||||
run: |
|
||||
# sections written by demo:pr (see tools/ui/demo/cli.ts): the managed comment
|
||||
# markers, or a preview linking to a demo video uploaded for this PR
|
||||
has_demo=false
|
||||
if { grep -qF '<!-- demo:start -->' <<< "$BODY" && grep -qF '<!-- demo:end -->' <<< "$BODY"; } ||
|
||||
grep -qE "/resolve/videos/pr/$PR_NUMBER/[0-9a-f]{10}(-[0-9]+)?\.mp4" <<< "$BODY"; then
|
||||
has_demo=true
|
||||
fi
|
||||
|
||||
if [ "$has_demo" = true ] && [ "$HAS_LABEL" = false ]; then
|
||||
gh pr edit "$PR_URL" --add-label 'video demo'
|
||||
elif [ "$has_demo" = false ] && [ "$HAS_LABEL" = true ]; then
|
||||
gh pr edit "$PR_URL" --remove-label 'video demo'
|
||||
fi
|
||||
@@ -24,7 +24,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
python-check-requirements:
|
||||
runs-on: ${{ 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ 'ubuntu-26.04-arm' || 'ubuntu-26.04' }}
|
||||
name: check-requirements
|
||||
steps:
|
||||
- name: Check out source repository
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
pip-install: -r requirements/requirements-all.txt ty==0.0.88
|
||||
pip-install: -r requirements/requirements-all.txt ty==0.0.100
|
||||
# - name: Type-check with Pyright
|
||||
# uses: jakebailey/pyright-action@v2
|
||||
# with:
|
||||
|
||||
@@ -171,14 +171,14 @@ jobs:
|
||||
**Linux:**
|
||||
- [Ubuntu x64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-x64.tar.gz)
|
||||
- [Ubuntu arm64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-arm64.tar.gz)
|
||||
- [Ubuntu s390x (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-s390x.tar.gz)
|
||||
- [Ubuntu 24.04 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)
|
||||
- [Ubuntu x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-x64.tar.gz) - [CUDA 13.4 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-x64.tar.gz)
|
||||
- [Ubuntu arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-arm64.tar.gz) - [CUDA 13.4 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.4-arm64.tar.gz)
|
||||
- [Ubuntu x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.5-x64.tar.gz) - [CUDA 13.5 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.5-x64.tar.gz)
|
||||
- [Ubuntu arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.5-arm64.tar.gz) - [CUDA 13.5 libraries](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-${{ steps.tag.outputs.name }}-bin-ubuntu-cuda-13.5-arm64.tar.gz)
|
||||
- [Ubuntu x64 (ROCm 10.0)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-rocm-10.0-x64.tar.gz)
|
||||
- [Ubuntu x64 (OpenVINO)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-openvino-2026.4.1-x64.tar.gz)
|
||||
- [Ubuntu x64 (SYCL FP32)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-ubuntu-sycl-fp32-x64.tar.gz)
|
||||
@@ -194,8 +194,8 @@ jobs:
|
||||
- [Windows arm64 (CPU)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cpu-arm64.zip)
|
||||
- [Windows arm64 (OpenCL Adreno)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-opencl-adreno-arm64.zip)
|
||||
- [Windows x64 (CUDA 12)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-12.4-x64.zip) - [CUDA 12.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-12.4-x64.zip)
|
||||
- [Windows x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.4-x64.zip) - [CUDA 13.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.4-x64.zip)
|
||||
- [Windows arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.4-arm64.zip) - [CUDA 13.4 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.4-arm64.zip)
|
||||
- [Windows x64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.5-x64.zip) - [CUDA 13.5 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.5-x64.zip)
|
||||
- [Windows arm64 (CUDA 13)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-cuda-13.5-arm64.zip) - [CUDA 13.5 DLLs](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/cudart-llama-bin-win-cuda-13.5-arm64.zip)
|
||||
- [Windows x64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-vulkan-x64.zip)
|
||||
- [Windows arm64 (Vulkan)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-vulkan-arm64.zip)
|
||||
- [Windows x64 (OpenVINO)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-win-openvino-2026.4.1-x64.zip)
|
||||
|
||||
@@ -345,14 +345,14 @@ jobs:
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
- build: 'x64'
|
||||
os: ubuntu-24.04
|
||||
cuda: '13.4.1'
|
||||
label: '13.4'
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
cuda: '13.5.0'
|
||||
label: '13.5'
|
||||
defines: ''
|
||||
- build: 'arm64'
|
||||
os: ubuntu-24.04-arm
|
||||
cuda: '13.4.1'
|
||||
label: '13.4'
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
cuda: '13.5.0'
|
||||
label: '13.5'
|
||||
defines: ''
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu24.04
|
||||
@@ -463,7 +463,7 @@ jobs:
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
env:
|
||||
NDK_VERSION: "29.0.14206865"
|
||||
@@ -548,7 +548,7 @@ jobs:
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
|
||||
|
||||
defaults:
|
||||
@@ -601,7 +601,7 @@ jobs:
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
|
||||
|
||||
defaults:
|
||||
@@ -1211,12 +1211,12 @@ jobs:
|
||||
- cuda: '12.4'
|
||||
arch: x64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
- cuda: '13.4'
|
||||
- cuda: '13.5'
|
||||
arch: x64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3'
|
||||
- cuda: '13.4'
|
||||
defines: ''
|
||||
- cuda: '13.5'
|
||||
arch: arm64
|
||||
defines: '-DGGML_CUDA_CCCL_VERSION=v3.4.3 -DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
|
||||
defines: '-DCMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-msvc-cuda.cmake'
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -1307,14 +1307,14 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
WINDOWS_DLE_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/a51104eb-6f4a-4a0e-9dae-1b81b0c81ae1/intel-deep-learning-essentials-2026.1.4.16_offline.exe
|
||||
WINDOWS_ONEDNN_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/34e28083-d04b-46a3-826c-8c1d00bf1e1c/intel-onednn-2026.0.2.47_offline.exe
|
||||
WINDOWS_DLE_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b177a006-5cd9-4cb2-807f-8199cc73d53c/intel-deep-learning-essentials-2026.2.0.638_offline.exe
|
||||
WINDOWS_ONEDNN_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/8f905666-c85c-43fb-9bd0-2618c7ecd33b/intel-onednn-2026.1.0.622_offline.exe
|
||||
WINDOWS_DPCPP_MKL: intel.oneapi.win.cpp-dpcpp-common:intel.oneapi.win.mkl.devel:intel.oneapi.win.tbb.devel
|
||||
WINDOWS_DNNL: intel.oneapi.win.dnnl
|
||||
|
||||
LEVEL_ZERO_SDK_URL: https://github.com/oneapi-src/level-zero/releases/download/v1.33.1/level-zero-win-sdk-1.33.1.zip
|
||||
LEVEL_ZERO_SDK_URL: https://github.com/oneapi-src/level-zero/releases/download/v1.34.0/level-zero-win-sdk-1.34.0.zip
|
||||
ONEAPI_ROOT: "C:/Program Files (x86)/Intel/oneAPI"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.2"
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@@ -1363,11 +1363,12 @@ jobs:
|
||||
cp "${{ env.ONEAPI_ROOT }}/mkl/latest/bin/mkl_def.3.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/mkl/latest/bin/mkl_avx2.3.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/mkl/latest/bin/mkl_tbb_thread.3.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/mkl/latest/bin/mkl_intel_thread.3.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/mkl/latest/bin/libimalloc.dll" ./build/bin
|
||||
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_adapter_level_zero.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_adapter_level_zero_v2.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_adapter_opencl.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_loader.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_loader_shared.dll" ./build/bin
|
||||
cp "${{ env.ONEAPI_ROOT }}/compiler/latest/bin/ur_win_proxy_loader.dll" ./build/bin
|
||||
ZE_LOADER_DLL=$(find "${{ env.ONEAPI_ROOT }}" "$LEVEL_ZERO_V1_SDK_PATH" -iname ze_loader.dll -print -quit 2>/dev/null || true)
|
||||
if [ -n "$ZE_LOADER_DLL" ]; then
|
||||
@@ -1421,8 +1422,8 @@ jobs:
|
||||
|
||||
env:
|
||||
ONEAPI_ROOT: /opt/intel/oneapi/
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
LEVEL_ZERO_VERSION: "1.33.1"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.2"
|
||||
LEVEL_ZERO_VERSION: "1.34.0"
|
||||
LEVEL_ZERO_UBUNTU_VERSION: "u24.04"
|
||||
|
||||
steps:
|
||||
@@ -1437,12 +1438,12 @@ jobs:
|
||||
run: |
|
||||
cd /tmp
|
||||
echo "Download oneAPI Deep Learning Essentials installation package"
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/b5c100a7-8052-4e88-90e2-63d9916a998b/intel-deep-learning-essentials-2026.1.4.17_offline.sh -O intel-deep-learning-essentials_offline.sh
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/aef232d2-aac5-4cd6-bceb-911019bbb6a8/intel-deep-learning-essentials-2026.2.0.640_offline.sh -O intel-deep-learning-essentials_offline.sh
|
||||
echo "Install oneAPI Deep Learning Essentials components"
|
||||
sudo bash intel-deep-learning-essentials_offline.sh -s -a --silent --eula accept
|
||||
|
||||
echo "Download oneDNN installation package"
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/94c3dbac-0852-45be-a57d-21c204cada3e/intel-onednn-2026.0.2.46_offline.sh -O intel-onednn_offline.sh
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/46452a4a-7b7f-49f2-be4c-602a900180bb/intel-onednn-2026.1.0.625_offline.sh -O intel-onednn_offline.sh
|
||||
echo "Install oneDNN components"
|
||||
sudo bash intel-onednn_offline.sh -s -a --silent --eula accept
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
name: Server (speculative decoding)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
sha:
|
||||
description: 'Commit SHA1 to build'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
cache-mode: none
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
LLAMA_ARG_LOG_COLORS: 1
|
||||
LLAMA_ARG_LOG_PREFIX: 1
|
||||
LLAMA_ARG_LOG_TIMESTAMPS: 1
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
server-speculative:
|
||||
runs-on: [self-hosted, Linux, gb10, perf]
|
||||
container: "nvidia/cuda:13.4.3-devel-ubuntu26.04"
|
||||
timeout-minutes: 60
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install -y cmake libssl-dev python3 python3-venv python3-pip
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc
|
||||
cmake --build build --config Release -j $(nproc) --target llama-server
|
||||
|
||||
- name: Python setup
|
||||
run: |
|
||||
cd tools/server/tests
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Test speculative decoding
|
||||
env:
|
||||
SPEC_METHODS: compact
|
||||
SPEC_MODEL_DIR: ${{ vars.SPEC_METHOD_MODEL_DIR || format('{0}/spec-models', runner.temp) }}
|
||||
N_GPU_LAYERS: 99
|
||||
run: |
|
||||
cd tools/server/tests
|
||||
source venv/bin/activate
|
||||
PYTEST_WORKERS=1 ./tests.sh -s -v unit/test_spec_methods.py
|
||||
@@ -59,7 +59,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Dependencies
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: server-ubuntu-24.04-arm
|
||||
key: server-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: server-ubuntu-24.04-arm
|
||||
key: server-ubuntu-26.04-arm
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
@@ -15,7 +15,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-26.04-arm
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -12,7 +12,7 @@ permissions:
|
||||
jobs:
|
||||
update:
|
||||
name: Update Winget Package
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
if: github.repository_owner == 'ggml-org'
|
||||
|
||||
steps:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# date: Wed Oct 28 12:38:26 EET 2026
|
||||
# date: Wed Nov 4 12:03:31 EET 2026
|
||||
# this file is auto-generated by scripts/gen-authors.sh
|
||||
|
||||
Нияз Гарифзянов <112617865+garrnizon@users.noreply.github.com>
|
||||
@@ -48,6 +48,7 @@ Abheek Gulati <abheekg@hotmail.com>
|
||||
abhijain1204fujitsu <139222713+abhijain1204fujitsu@users.noreply.github.com>
|
||||
Abhijit Ramesh <abhijitramesh2k@gmail.com>
|
||||
abhijitb11 <113058133+abhijitb11@users.noreply.github.com>
|
||||
Abhijith Jajam <129675815+jajamabhijith@users.noreply.github.com>
|
||||
Abhilash Majumder <30946547+abhilash1910@users.noreply.github.com>
|
||||
Abhinay Krishna <abhinaykrishna60@gmail.com>
|
||||
Abhiram <78226909+geckguy@users.noreply.github.com>
|
||||
@@ -203,14 +204,17 @@ Ankit Khandelwal <ankk98@gmail.com>
|
||||
Ankur Verma <31362771+ankurvdev@users.noreply.github.com>
|
||||
anon998 <131767832+anon998@users.noreply.github.com>
|
||||
Anri Lombard <anri.m.lombard@gmail.com>
|
||||
Anthony Maltsev <77708730+anthonymaltsev@users.noreply.github.com>
|
||||
Anthony Umfer <aumfer@gmail.com>
|
||||
Anthony Van de Gejuchte <anthonyvdgent@gmail.com>
|
||||
Anthony Wang <anthonywang903@gmail.com>
|
||||
antichristHater <142441588+antichristHater@users.noreply.github.com>
|
||||
Antoine Viallon <antoine@lesviallon.fr>
|
||||
Anton I <inflyer@hotmail.com>
|
||||
Anton Mitkov <anton_b_mitkov@abv.bg>
|
||||
Anton Mitkov <anton.mitkov@codeplay.com>
|
||||
Antonis Makropoulos <benuix@gmail.com>
|
||||
anubhavfujitsu <Anubhav.Srivastava@fujitsu.com>
|
||||
Anudit Nagar <nagaranudit@gmail.com>
|
||||
Anuj Attri <anujattri01@gmail.com>
|
||||
anujj <ajalota@nvidia.com>
|
||||
@@ -293,6 +297,7 @@ Bernat Vadell <hounter.caza@gmail.com>
|
||||
Bernhard M. Wiedemann <githubbmwprimary@lsmod.de>
|
||||
Bert Wagner <github@bertwagner.com>
|
||||
Bertay Eren <39909689+bertaye@users.noreply.github.com>
|
||||
Bhargav Krish <bkrishnamurt@nvidia.com>
|
||||
Bhavik Sharda <10757940+BLSharda@users.noreply.github.com>
|
||||
bhubbb <79117352+bhubbb@users.noreply.github.com>
|
||||
Bill Sideris <bill88t@feline.gr>
|
||||
@@ -533,6 +538,7 @@ Don Mahurin <dmahurin@users.noreply.github.com>
|
||||
Dong Won Kim <63934649+ddwkim@users.noreply.github.com>
|
||||
Donghyeon Jeong <54725479+djeong20@users.noreply.github.com>
|
||||
Dongliang Wei <121270393+wdl339@users.noreply.github.com>
|
||||
Dongsheng Luo <luods1995@outlook.com>
|
||||
Doomsdayrs <38189170+Doomsdayrs@users.noreply.github.com>
|
||||
DooWoong Lee (David) <manics99@naver.com>
|
||||
DorianRudolph <dorianrudo97@googlemail.com>
|
||||
@@ -612,6 +618,7 @@ Faez Shakil <faez.shakil@gmail.com>
|
||||
fairydreaming <166155368+fairydreaming@users.noreply.github.com>
|
||||
Faisal Zaghloul <faisal.zaghloul@gmail.com>
|
||||
Faisal Zaghloul <quic_fzaghlou@quicinc.com>
|
||||
FamousMonsterr <famousmonster@ya.ru>
|
||||
Fan Shupei <dymarkfan@outlook.com>
|
||||
FantasyGmm <16450052+FantasyGmm@users.noreply.github.com>
|
||||
fanyang <fanyang89@outlook.com>
|
||||
@@ -654,6 +661,7 @@ Frederik Vogel <Schaltfehler@users.noreply.github.com>
|
||||
Fredrik Hultin <noname@nurd.se>
|
||||
fredzillman <fzillman@gmail.com>
|
||||
frob <rick+github@frob.com.au>
|
||||
frogscooper <frogscooper@gmail.com>
|
||||
Frost-54 <91168148+Frost-54@users.noreply.github.com>
|
||||
Frosty40 <newjordan@gmail.com>
|
||||
Funtowicz Morgan <mfuntowicz@users.noreply.github.com>
|
||||
@@ -694,6 +702,7 @@ Gilad S. <7817232+giladgd@users.noreply.github.com>
|
||||
Giovanni Rivera <rivera.giovanni271@gmail.com>
|
||||
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
GittyBurstein <g0534163997@gmail.com>
|
||||
Giulianno <lernexbusiness@gmail.com>
|
||||
Giuseppe Scrivano <giuseppe@scrivano.org>
|
||||
Giuseppe Scrivano <gscrivan@redhat.com>
|
||||
GiviMAD <GiviMAD@users.noreply.github.com>
|
||||
@@ -762,6 +771,7 @@ Henry147147 <44851451+Henry147147@users.noreply.github.com>
|
||||
Herman Semenoff <GermanAizek@yandex.ru>
|
||||
Herman Semenov <GermanAizek@yandex.ru>
|
||||
Hesen Peng <hesen.peng@gmail.com>
|
||||
hesiru <siruhe666@gmail.com>
|
||||
hey-gm <heitorgm@outlook.com>
|
||||
HighDoping <highdoping@gmail.com>
|
||||
HimariO <dsfhe49854@gmail.com>
|
||||
@@ -919,6 +929,7 @@ Jesse Ikonen <jesse.ikonen@gmail.com>
|
||||
Jesse Jojo Johnson <williamsaintgeorge@gmail.com>
|
||||
Jesse LaRose <jesse@taey.ai>
|
||||
Jesse Posner <jesse.posner@gmail.com>
|
||||
Jesse Symons <155509533+Torchit1@users.noreply.github.com>
|
||||
Jesus Gulfo <jesdga95@gmail.com>
|
||||
Jesus Talavera <145992175+jesus-talavera-ibm@users.noreply.github.com>
|
||||
Jetson Tan <tanzongyouyi@outlook.com>
|
||||
@@ -1071,6 +1082,7 @@ Kevin Liu <4396kevinliu@gmail.com>
|
||||
Kevin Lo <kevlo@kevlo.org>
|
||||
Kevin Pouget <kpouget@redhat.com>
|
||||
Kevin Wang <kevmo314@gmail.com>
|
||||
Keyu Zhu <inuebisu@outlook.com>
|
||||
Khashayar Ghafouri <43180261+khashayarghafouri@users.noreply.github.com>
|
||||
khimaros <me@khimaros.com>
|
||||
Kilian Hu <90606809+kilian-hu@users.noreply.github.com>
|
||||
@@ -1078,6 +1090,7 @@ Kilian Krampf <kilian@krampf.de>
|
||||
kiltyj <kiltyj@gmail.com>
|
||||
Kim S. <polydecay@users.noreply.github.com>
|
||||
kimminsu <80271594+kimminsu38oo@users.noreply.github.com>
|
||||
Kirill A. Korinsky <kirill@korins.ky>
|
||||
KITAITI Makoto <KitaitiMakoto@gmail.com>
|
||||
kiwi <122582483+kiwi142857@users.noreply.github.com>
|
||||
klosax <131523366+klosax@users.noreply.github.com>
|
||||
@@ -1136,6 +1149,7 @@ leejet <leejet714@gmail.com>
|
||||
Leng Yue <lengyue@lengyue.me>
|
||||
Lennart Austenfeld <53152202+l-austenfeld@users.noreply.github.com>
|
||||
leo-pony <nengjunma@outlook.com>
|
||||
Leon <118652405+LeON-Nie-code@users.noreply.github.com>
|
||||
Leon Knauer <git@leonknauer.com>
|
||||
Leonard Mosescu <tlemo@users.noreply.github.com>
|
||||
leonardHONG <2695316095@qq.com>
|
||||
@@ -1146,8 +1160,10 @@ lexasub <lexakopp2212@gmail.com>
|
||||
lgai-exaone <exaonemodels@lgresearch.ai>
|
||||
lhez <lih@qti.qualcomm.com>
|
||||
lhez <quic_lih@quicinc.com>
|
||||
Li Chuyang <1418587903@qq.com>
|
||||
Li Pengzhan <151381994+Lpzhan931@users.noreply.github.com>
|
||||
Li Tan <tanliboy@gmail.com>
|
||||
li-lizhe <82501526+li-lizhe@users.noreply.github.com>
|
||||
liangliangchang <lichang@amd.com>
|
||||
liminfei-amd <91481003+liminfei-amd@users.noreply.github.com>
|
||||
limitedAtonement <limitedAtonement@users.noreply.github.com>
|
||||
@@ -1166,6 +1182,7 @@ LoganDark <github@logandark.mozmail.com>
|
||||
Loïc Carrère <loic.carrere@gmail.com>
|
||||
lon <114724657+longregen@users.noreply.github.com>
|
||||
loonerin <132926317+loonerin@users.noreply.github.com>
|
||||
Lorenzo Bona <lorenzobona@tuta.com>
|
||||
losewayy <1134285612@qq.com>
|
||||
LostRuins <39025047+LostRuins@users.noreply.github.com>
|
||||
LostRuins Concedo <39025047+LostRuins@users.noreply.github.com>
|
||||
@@ -1253,6 +1270,7 @@ Masaya, Kato <62578291+msy-kato@users.noreply.github.com>
|
||||
mashdragon <122402293+mashdragon@users.noreply.github.com>
|
||||
Mason Milburn <masonmilby@gmail.com>
|
||||
MasterYi1024 <39848311+MasterYi1024@users.noreply.github.com>
|
||||
Maternion <98753158+maternion@users.noreply.github.com>
|
||||
Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com>
|
||||
Matheus C. França <matheus-catarino@hotmail.com>
|
||||
Matheus Gabriel Alves Silva <matheusgasource@gmail.com>
|
||||
@@ -1350,6 +1368,7 @@ Miwa / Ensan <63481257+ensan-hcl@users.noreply.github.com>
|
||||
miyan <1138989048@qq.com>
|
||||
mj-shifu <77107165+mj-shifu@users.noreply.github.com>
|
||||
mkoker <132301062+mkoker@users.noreply.github.com>
|
||||
mmalpe-nv <mmalpe@nvidia.com>
|
||||
mmyjona <jonathan.gonse@gmail.com>
|
||||
mnehete32 <33429707+mnehete32@users.noreply.github.com>
|
||||
Mohamed Elashri <git@elashri.com>
|
||||
@@ -1368,6 +1387,7 @@ mtmcp <141645996+mtmcp@users.noreply.github.com>
|
||||
muggle-stack <promuggle@qq.com>
|
||||
Muhammad Saad <saadydev@gmail.com>
|
||||
Muhammad Salem <salem.ebo@gmail.com>
|
||||
MuoDoo <36472129+MuoDoo@users.noreply.github.com>
|
||||
Murilo Santana <mvrilo@gmail.com>
|
||||
Musab Gultekin <musabgultekin@users.noreply.github.com>
|
||||
musoles <135031143+musoles@users.noreply.github.com>
|
||||
@@ -1416,10 +1436,12 @@ Nicolas Budyn <16596345+nbud@users.noreply.github.com>
|
||||
Nicolas Mowen <nickmowen213@gmail.com>
|
||||
Nicolás Pérez <nicolas_perez@brown.edu>
|
||||
Nicolò Scipione <nicolo.scipione@codeplay.com>
|
||||
Nidhi <nidhisharma16635@gmail.com>
|
||||
Nigel Bosch <pnigelb@gmail.com>
|
||||
Nigel Jones <nigel.l.jones+git@gmail.com>
|
||||
Nik Bogatyrev <50734723+notforu777@users.noreply.github.com>
|
||||
Nikhil Jain <nikhil.jain0987@gmail.com>
|
||||
Nikhil Sharma <150424968+nikhilfujitsu@users.noreply.github.com>
|
||||
Nikita Gordeev <iamfobey@gmail.com>
|
||||
Nikita Sarychev <42014488+sARY77@users.noreply.github.com>
|
||||
Niklas Korz <niklas@niklaskorz.de>
|
||||
@@ -1592,6 +1614,7 @@ Raman Shinde <raman.shinde15@gmail.com>
|
||||
Rand Xie <randxiexyy29@gmail.com>
|
||||
Randall Fitzgerald <randall@dasaku.net>
|
||||
Random Fly <renfei8@live.cn>
|
||||
randomized <286843573@qq.com>
|
||||
rankaiyx <rankaiyx@foxmail.com>
|
||||
rankaiyx <rankaiyx@rankaiyx.com>
|
||||
RapidMark <32768622+RapidMark@users.noreply.github.com>
|
||||
@@ -2009,6 +2032,7 @@ Tungsten842 <quantmint@protonmail.com>
|
||||
Tushar <ditsuke@protonmail.com>
|
||||
tv1wnd <55383215+tv1wnd@users.noreply.github.com>
|
||||
tyronecai <tyronecai@163.com>
|
||||
Uber <20715993+YagUber@users.noreply.github.com>
|
||||
ubergarm <leimgrub@gmail.com>
|
||||
ubik2 <ubik2@users.noreply.github.com>
|
||||
UEXTM.com <84163508+uextm@users.noreply.github.com>
|
||||
@@ -2242,4 +2266,5 @@ Zijun Yu <zijun.yu@intel.com>
|
||||
zql <37731799+zqlcode@users.noreply.github.com>
|
||||
zrm <trustiosity.zrm@gmail.com>
|
||||
Zsapi <martin1.zsapka@gmail.com>
|
||||
zyang-dev <267119621+zyang-dev@users.noreply.github.com>
|
||||
zzzzwc <tiddar@foxmail.com>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ include(CheckIncludeFileCXX)
|
||||
|
||||
### llama.cpp version
|
||||
set(LLAMA_VERSION_MAJOR 0)
|
||||
set(LLAMA_VERSION_MINOR 8)
|
||||
set(LLAMA_VERSION_MINOR 9)
|
||||
set(LLAMA_VERSION_PATCH 0)
|
||||
set(LLAMA_VERSION_BASE "${LLAMA_VERSION_MAJOR}.${LLAMA_VERSION_MINOR}.${LLAMA_VERSION_PATCH}")
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
/src/llama-graph.* @CISC
|
||||
/src/llama-model.* @CISC
|
||||
/src/llama-vocab.* @CISC
|
||||
/src/llama-moe-cache.* @am17an
|
||||
/src/models/ @ggml-org/llama-models
|
||||
/tests/ @ggerganov
|
||||
/tests/test-chat.* @pwilkin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# llama.cpp
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -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%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)
|
||||
[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/pulls?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>
|
||||
|
||||
@@ -79,7 +79,7 @@ The `llama.cpp` project is build on top of the [ggml](https://github.com/ggml-or
|
||||
| --- | --- |
|
||||
| [BLAS](docs/build.md#blas-build) | All |
|
||||
| [BLIS](docs/backend/BLIS.md) | All |
|
||||
| [CANN](docs/build.md#cann) | Ascend NPU |
|
||||
| [CANN](docs/build.md#cann) | Huawei Ascend NPU |
|
||||
| [CUDA](docs/build.md#cuda) | Nvidia GPU |
|
||||
| [HIP](docs/build.md#hip) | AMD GPU |
|
||||
| [Hexagon](docs/backend/snapdragon/README.md) | Snapdragon |
|
||||
|
||||
@@ -180,7 +180,7 @@ if [ ! -z ${GG_BUILD_OPENVINO} ]; then
|
||||
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_OPENVINO=ON"
|
||||
|
||||
# TODO: fix failing tests on OpenVINO backend
|
||||
CTEST_EXTRA="-E test-llama-archs|test-llama-context"
|
||||
CTEST_EXTRA="-E test-llama-archs|test-llama-context|test-fp8-kv"
|
||||
fi
|
||||
|
||||
## helpers
|
||||
@@ -296,25 +296,6 @@ function gg_run_test_llama_archs_tensor_split {
|
||||
set +e
|
||||
}
|
||||
|
||||
# test_llama_archs_models
|
||||
|
||||
function gg_run_test_llama_archs_models {
|
||||
cd ${SRC}
|
||||
|
||||
set -e
|
||||
|
||||
# TODO: fix and re-enable `test-llama-archs` on OpenVINO
|
||||
# TODO: the `test-llama-archs` currently does not build on Windows, so we check if the binary exists
|
||||
if [ -z ${GG_BUILD_OPENVINO} ] && [ -f ./build-ci-release/bin/test-llama-archs ]; then
|
||||
rm -rf build-ci-models && mkdir -p build-ci-models
|
||||
|
||||
# generate the dummy models used by the model-dependent tests
|
||||
./build-ci-release/bin/test-llama-archs -o build-ci-models 2>&1
|
||||
fi
|
||||
|
||||
set +e
|
||||
}
|
||||
|
||||
# test_scripts
|
||||
|
||||
function gg_run_test_scripts {
|
||||
@@ -456,12 +437,8 @@ function gg_run_qwen3_0_6b {
|
||||
|
||||
(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 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
|
||||
(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
|
||||
|
||||
@@ -701,7 +678,6 @@ test $ret -eq 0 && gg_run ctest_release
|
||||
|
||||
test $ret -eq 0 && gg_run test_backend_ops
|
||||
|
||||
test $ret -eq 0 && gg_run test_llama_archs_models
|
||||
test $ret -eq 0 && gg_run test_llama_archs_tensor_split
|
||||
|
||||
if [ -z ${GG_BUILD_LOW_PERF} ]; then
|
||||
|
||||
@@ -2,11 +2,11 @@ get_filename_component(DEST_DIR "${DEST}" DIRECTORY)
|
||||
file(MAKE_DIRECTORY "${DEST_DIR}")
|
||||
|
||||
if(NOT EXISTS "${DEST}")
|
||||
message(STATUS "Downloading ${NAME} from ggml-org/models...")
|
||||
message(STATUS "Downloading ${NAME} from ${REPO}...")
|
||||
endif()
|
||||
|
||||
file(DOWNLOAD
|
||||
"https://huggingface.co/ggml-org/models/resolve/main/${NAME}?download=true"
|
||||
"https://huggingface.co/${REPO}/resolve/main/${NAME}?download=true"
|
||||
"${DEST}"
|
||||
TLS_VERIFY ON
|
||||
EXPECTED_HASH ${HASH}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ if(WIN32)
|
||||
endif()
|
||||
enable_language(RC)
|
||||
set(LLAMA_WIN_APP_MANIFEST_RC "${CMAKE_CURRENT_BINARY_DIR}/win-app-manifest.rc")
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/win-app.rc.in" "${LLAMA_WIN_APP_MANIFEST_RC}" @ONLY)
|
||||
file(CONFIGURE OUTPUT "${LLAMA_WIN_APP_MANIFEST_RC}" CONTENT "1 24 \"@LLAMA_WIN_APP_MANIFEST@\"\n" @ONLY)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${LLAMA_WIN_APP_MANIFEST}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// generated from cmake/win-app.rc.in - do not edit
|
||||
// RT_MANIFEST (24), ID 1: the application manifest shared by all llama.cpp executables
|
||||
1 24 "@LLAMA_WIN_APP_MANIFEST@"
|
||||
@@ -66,6 +66,8 @@ add_library(${TARGET}
|
||||
chat-diff-analyzer.cpp
|
||||
chat-peg-parser.cpp
|
||||
chat-peg-parser.h
|
||||
chat-tts-norm.cpp
|
||||
chat-tts-norm.h
|
||||
chat.cpp
|
||||
chat.h
|
||||
common.cpp
|
||||
|
||||
+66
-9
@@ -4,6 +4,7 @@
|
||||
#include "chat.h"
|
||||
#include "common.h"
|
||||
#include "download.h"
|
||||
#include "http.h"
|
||||
#include "json-schema-to-grammar.h"
|
||||
#include "json.h"
|
||||
#include "llama.h"
|
||||
@@ -281,6 +282,7 @@ const std::vector<ggml_type> kv_cache_types = {
|
||||
GGML_TYPE_F32,
|
||||
GGML_TYPE_F16,
|
||||
GGML_TYPE_BF16,
|
||||
GGML_TYPE_F8_E4M3,
|
||||
GGML_TYPE_Q8_0,
|
||||
GGML_TYPE_Q4_0,
|
||||
GGML_TYPE_Q4_1,
|
||||
@@ -399,7 +401,26 @@ bool common_models_handler_is_preset_repo(const common_models_handler & handler)
|
||||
return !handler.plan.preset.url.empty();
|
||||
}
|
||||
|
||||
static common_download_opts download_opts_for_url(const common_download_opts & opts, const std::string & url) {
|
||||
common_download_opts result = opts;
|
||||
try {
|
||||
const auto hf = common_http_parse_url(common_get_model_endpoint());
|
||||
const auto parts = common_http_parse_url(url);
|
||||
|
||||
if (parts.scheme == hf.scheme &&
|
||||
parts.host == hf.host &&
|
||||
parts.port == hf.port &&
|
||||
string_starts_with(result.bearer_token, "hf_")) {
|
||||
return result;
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
result.bearer_token.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<common_download_task> build_url_tasks(const common_params_model & model, common_download_opts opts) {
|
||||
opts = download_opts_for_url(opts, model.url);
|
||||
auto parts = common_download_get_all_parts(model.url);
|
||||
std::vector<common_download_task> tasks;
|
||||
|
||||
@@ -488,7 +509,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
common_download_task task;
|
||||
task.url = params.mmproj.url;
|
||||
task.local_path = params.mmproj.path;
|
||||
task.opts = opts;
|
||||
task.opts = download_opts_for_url(opts, task.url);
|
||||
tasks.push_back(task);
|
||||
}
|
||||
bool had_spec_url = false;
|
||||
@@ -496,7 +517,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
common_download_task task;
|
||||
task.url = params.speculative.draft.mparams.url;
|
||||
task.local_path = params.speculative.draft.mparams.path;
|
||||
task.opts = opts;
|
||||
task.opts = download_opts_for_url(opts, task.url);
|
||||
tasks.push_back(task);
|
||||
had_spec_url = true;
|
||||
}
|
||||
@@ -710,7 +731,7 @@ static void common_params_apply_system_config(common_params & params, llama_exam
|
||||
std::vector<std::filesystem::path> paths;
|
||||
|
||||
#if defined(_WIN32)
|
||||
const std::filesystem::path program_data = common_get_path_from_env("PROGRAMDATA");
|
||||
const std::filesystem::path program_data = common_get_env("PROGRAMDATA");
|
||||
if (!program_data.empty()) {
|
||||
paths.push_back(program_data / "llama.cpp" / "config.ini");
|
||||
}
|
||||
@@ -922,8 +943,8 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context
|
||||
}
|
||||
|
||||
const bool mcp_enabled = !params.mcp_servers_config.empty() || !params.mcp_servers_json.empty();
|
||||
if ((!params.server_tools.empty() || mcp_enabled) && !params.cors_origins_explicit) {
|
||||
LOG_WRN("server tools or MCP servers are enabled, using localhost as default CORS origin (change via --cors-origins)\n");
|
||||
if ((!params.server_tools.empty() || mcp_enabled || params.ui_mcp_proxy) && !params.cors_origins_explicit) {
|
||||
LOG_WRN("server tools, MCP servers or MCP proxy are enabled, using localhost as default CORS origin (change via --cors-origins)\n");
|
||||
params.cors_origins = "localhost";
|
||||
}
|
||||
|
||||
@@ -2734,6 +2755,17 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.moe_cache_size = (size_t) value*1024*1024;
|
||||
}
|
||||
).set_env("LLAMA_ARG_MOE_CACHE_MIB"));
|
||||
add_opt(common_arg(
|
||||
{"--moe-cache-cpu-share"}, "F",
|
||||
string_format("share of the MoE cache misses that the CPU computes from the host experts instead of uploading them to the GPU once the cache is full, from 0 to 1 (default: %.2f)", (double) params.moe_cache_cpu_share),
|
||||
[](common_params & params, const std::string & value) {
|
||||
const float share = std::stof(value);
|
||||
if (share < 0.0f || share > 1.0f) {
|
||||
throw std::invalid_argument("invalid value");
|
||||
}
|
||||
params.moe_cache_cpu_share = share;
|
||||
}
|
||||
).set_env("LLAMA_ARG_MOE_CACHE_CPU_SHARE"));
|
||||
add_opt(common_arg(
|
||||
{"-ncffn", "--n-cpu-ffn"}, "N",
|
||||
"keep the dense FFN weights of the first N layers in the CPU\n"
|
||||
@@ -3001,6 +3033,16 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.model.path = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_EXPORT_LORA, LLAMA_EXAMPLE_DOWNLOAD, LLAMA_EXAMPLE_TOKENIZE}).set_env("LLAMA_ARG_MODEL"));
|
||||
add_opt(common_arg(
|
||||
{"--ple", "--shard"}, "FNAME,...",
|
||||
"extra GGUF shards for the model, comma separated; may be given more than once\n"
|
||||
"paths may live anywhere, including on different drives; --ple is an alias for --shard",
|
||||
[](common_params & params, const std::string & value) {
|
||||
for (const auto & item : parse_csv_row(value)) {
|
||||
params.model.shards.push_back(item);
|
||||
}
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_COMMON}).set_env("LLAMA_ARG_SHARD"));
|
||||
add_opt(common_arg(
|
||||
{"-mu", "--model-url"}, "MODEL_URL",
|
||||
"model download url (default: unused)",
|
||||
@@ -3319,7 +3361,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
{"--cors-origins"}, "ORIGINS",
|
||||
string_format(
|
||||
"comma-separated list of allowed origins for CORS (default: %s)\n"
|
||||
"if set to special value 'localhost', reflect the Origin header only if it is localhost",
|
||||
"if set to special value 'localhost', only allow same-origin requests (e.g. the built-in UI)",
|
||||
params.cors_origins.c_str()),
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.cors_origins = value;
|
||||
@@ -3375,7 +3417,8 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
add_opt(common_arg(
|
||||
{"--ui-mcp-proxy", "--webui-mcp-proxy"},
|
||||
{"--no-ui-mcp-proxy", "--no-webui-mcp-proxy"},
|
||||
"experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)",
|
||||
"experimental: whether to enable MCP CORS proxy, for the Web UI to reach public MCP servers without CORS headers; local and private addresses are refused - do not enable in untrusted environments (default: disabled)\n"
|
||||
"note: for security reasons, this will limit --cors-origins to localhost by default",
|
||||
[](common_params & params, bool value) {
|
||||
params.ui_mcp_proxy = value;
|
||||
}
|
||||
@@ -3401,6 +3444,13 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.server_tools_runtime = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_TOOLS_RUNTIME"));
|
||||
add_opt(common_arg(
|
||||
{"--allow-unauthenticated-tools"},
|
||||
"allow server tools, MCP servers or MCP proxy on a non-loopback address without an API key - only for trusted networks (default: disabled)",
|
||||
[](common_params & params) {
|
||||
params.server_tools_allow_unauth = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_ALLOW_UNAUTHENTICATED_TOOLS"));
|
||||
add_opt(common_arg(
|
||||
{"--mcp-servers-config"}, "PATH",
|
||||
"experimental: path to JSON file with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)\n"
|
||||
@@ -3626,6 +3676,13 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
}
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}));
|
||||
add_opt(common_arg(
|
||||
{"--media-allow-private-urls"},
|
||||
"allow http(s) media URLs (e.g. image_url) that point to local or private addresses (default: disabled, only public addresses)",
|
||||
[](common_params & params) {
|
||||
params.media_allow_private_urls = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_MEDIA_ALLOW_PRIVATE_URLS"));
|
||||
add_opt(common_arg(
|
||||
{"--models-dir"}, "PATH",
|
||||
"directory containing models for the router server (default: disabled)",
|
||||
@@ -4450,7 +4507,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.tts_lang = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TTS}));
|
||||
).set_examples({LLAMA_EXAMPLE_TTS, LLAMA_EXAMPLE_SERVER}));
|
||||
add_opt(common_arg(
|
||||
{"--tts-voice"}, "VOICE",
|
||||
"voice for audio generation (model-dependent)\n"
|
||||
@@ -4458,7 +4515,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
[](common_params & params, const std::string & value) {
|
||||
params.tts_voice = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_TTS}));
|
||||
).set_examples({LLAMA_EXAMPLE_TTS, LLAMA_EXAMPLE_SERVER}));
|
||||
add_opt(common_arg(
|
||||
{"--tts-speaker-file"}, "FNAME",
|
||||
"speaker file path for audio generation",
|
||||
|
||||
+53
-48
@@ -131,6 +131,12 @@ static std::string normalize_quotes_to_json(const std::string & input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Models can emit raw control characters inside string literals; escape them to keep the JSON valid.
|
||||
if ((in_single_quoted || in_double_quoted) && static_cast<unsigned char>(c) < 0x20) {
|
||||
result += escape_json_string_inner(std::string(1, c));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle quote characters
|
||||
if (c == '"') {
|
||||
if (in_single_quoted) {
|
||||
@@ -348,7 +354,8 @@ void common_chat_peg_mapper::map(const common_peg_ast_node & node) {
|
||||
}
|
||||
|
||||
if (is_tool_id && current_tool) {
|
||||
auto text = trim_trailing_space(node.text);
|
||||
auto sanitized = node.sanitized_text();
|
||||
auto text = trim_trailing_space(sanitized);
|
||||
if (text.size() >= 2 && text.front() == '"' && text.back() == '"') {
|
||||
text = text.substr(1, text.size() - 2);
|
||||
}
|
||||
@@ -401,7 +408,7 @@ void common_chat_peg_mapper::map(const common_peg_ast_node & node) {
|
||||
}
|
||||
|
||||
if ((is_arg_value || is_arg_string_value) && current_tool) {
|
||||
std::string value_content = std::string(node.text);
|
||||
std::string value_content = node.sanitized_text();
|
||||
|
||||
std::string value_to_add;
|
||||
if (value_content.empty() && is_arg_string_value) {
|
||||
@@ -966,6 +973,21 @@ void common_chat_peg_gemma4_mapper::from_ast(const common_peg_ast_arena & arena,
|
||||
}
|
||||
}
|
||||
|
||||
using gemma4 = common_chat_peg_gemma4_mapper;
|
||||
|
||||
// Collects the nearest descendants carrying tag, looking through the rules the parser
|
||||
// wraps around the tagged nodes.
|
||||
static void gemma4_collect(const common_peg_ast_arena & arena, const common_peg_ast_node & node, const std::string & tag, std::vector<common_peg_ast_id> & out) {
|
||||
for (auto child_id : node.children) {
|
||||
const auto & child = arena.get(child_id);
|
||||
if (child.tag == tag) {
|
||||
out.push_back(child_id);
|
||||
} else {
|
||||
gemma4_collect(arena, child, tag, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string gemma4_to_json(const common_peg_ast_arena & arena, common_peg_ast_id id) {
|
||||
const auto & node = arena.get(id);
|
||||
|
||||
@@ -973,51 +995,45 @@ static std::string gemma4_to_json(const common_peg_ast_arena & arena, common_peg
|
||||
return "";
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-number" || node.rule == "gemma4-bool" || node.rule == "gemma4-null") {
|
||||
if (node.tag == gemma4::NUMBER || node.tag == gemma4::BOOL || node.tag == gemma4::NULL_) {
|
||||
return std::string(node.text);
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-string-content") {
|
||||
return escape_json_string_inner(std::string(node.text));
|
||||
if (node.tag == gemma4::STRING_CONTENT) {
|
||||
return escape_json_string_inner(node.sanitized_text());
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-string") {
|
||||
if (node.tag == gemma4::STRING) {
|
||||
std::string result = "\"";
|
||||
if (!node.children.empty()) {
|
||||
result += gemma4_to_json(arena, node.children[0]);
|
||||
if (!node.is_partial) {
|
||||
result += "\"";
|
||||
}
|
||||
for (auto child_id : node.children) {
|
||||
result += gemma4_to_json(arena, child_id);
|
||||
}
|
||||
if (!node.is_partial) {
|
||||
result += "\"";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-array") {
|
||||
if (node.tag == gemma4::ARRAY) {
|
||||
std::string result = "[";
|
||||
|
||||
bool add_comma = false;
|
||||
for (auto child_id : node.children) {
|
||||
if (add_comma) {
|
||||
for (size_t i = 0; i < node.children.size(); i++) {
|
||||
if (i > 0) {
|
||||
result += ',';
|
||||
}
|
||||
add_comma = true;
|
||||
result += gemma4_to_json(arena, child_id);
|
||||
result += gemma4_to_json(arena, node.children[i]);
|
||||
}
|
||||
|
||||
if (!node.is_partial) {
|
||||
result += ']';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-dict-key-name") {
|
||||
return std::string(node.text);
|
||||
}
|
||||
if (node.tag == gemma4::DICT_KEY) {
|
||||
auto name_id = arena.find_by_tag(node, gemma4::DICT_KEY_NAME);
|
||||
|
||||
if (node.rule == "gemma4-dict-key") {
|
||||
std::string result = "\"";
|
||||
if (!node.children.empty()) {
|
||||
result += escape_json_string_inner(gemma4_to_json(arena, node.children[0]));
|
||||
if (name_id != COMMON_PEG_INVALID_AST_ID) {
|
||||
result += escape_json_string_inner(std::string(arena.get(name_id).text));
|
||||
}
|
||||
if (!node.is_partial) {
|
||||
result += "\":";
|
||||
@@ -1025,40 +1041,29 @@ static std::string gemma4_to_json(const common_peg_ast_arena & arena, common_peg
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-dict-kv") {
|
||||
std::string result;
|
||||
for (auto child_id : node.children) {
|
||||
result += gemma4_to_json(arena, child_id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (node.tag == gemma4::DICT) {
|
||||
std::vector<common_peg_ast_id> members;
|
||||
gemma4_collect(arena, node, gemma4::DICT_KV, members);
|
||||
|
||||
if (node.rule == "gemma4-dict") {
|
||||
std::string result = "{";
|
||||
|
||||
bool add_comma = false;
|
||||
for (auto child_id : node.children) {
|
||||
if (add_comma) {
|
||||
for (size_t i = 0; i < members.size(); i++) {
|
||||
if (i > 0) {
|
||||
result += ',';
|
||||
}
|
||||
add_comma = true;
|
||||
result += gemma4_to_json(arena, child_id);
|
||||
result += gemma4_to_json(arena, members[i]);
|
||||
}
|
||||
|
||||
if (!node.is_partial) {
|
||||
result += '}';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node.rule == "gemma4-value") {
|
||||
if (!node.children.empty()) {
|
||||
return gemma4_to_json(arena, node.children[0]);
|
||||
}
|
||||
return "";
|
||||
// members, rules and other wrappers
|
||||
std::string result;
|
||||
for (auto child_id : node.children) {
|
||||
result += gemma4_to_json(arena, child_id);
|
||||
}
|
||||
|
||||
return "";
|
||||
return result;
|
||||
}
|
||||
|
||||
void common_chat_peg_gemma4_mapper::visit(const common_peg_ast_arena & arena, common_peg_ast_id id) {
|
||||
@@ -1195,7 +1200,7 @@ static std::string minimax_m3_value_to_json(const common_peg_ast_arena & arena,
|
||||
}
|
||||
|
||||
if (node.tag == common_chat_peg_builder::TOOL_ARG_STRING_VALUE) {
|
||||
return "\"" + escape_json_string_inner(std::string(node.text)) + (closed ? "\"" : "");
|
||||
return "\"" + escape_json_string_inner(node.sanitized_text()) + (closed ? "\"" : "");
|
||||
}
|
||||
|
||||
// Numbers and booleans are written verbatim by the template
|
||||
|
||||
@@ -34,6 +34,18 @@ class common_chat_peg_mapper {
|
||||
|
||||
class common_chat_peg_gemma4_mapper : public common_chat_peg_mapper {
|
||||
public:
|
||||
// Tags of the Gemma 4 argument syntax. The parser may wrap them in any rules.
|
||||
static constexpr const char * STRING = "gemma4-string";
|
||||
static constexpr const char * STRING_CONTENT = "gemma4-string-content";
|
||||
static constexpr const char * NUMBER = "gemma4-number";
|
||||
static constexpr const char * BOOL = "gemma4-bool";
|
||||
static constexpr const char * NULL_ = "gemma4-null";
|
||||
static constexpr const char * ARRAY = "gemma4-array";
|
||||
static constexpr const char * DICT = "gemma4-dict";
|
||||
static constexpr const char * DICT_KV = "gemma4-dict-kv";
|
||||
static constexpr const char * DICT_KEY = "gemma4-dict-key";
|
||||
static constexpr const char * DICT_KEY_NAME = "gemma4-dict-key-name";
|
||||
|
||||
common_chat_peg_gemma4_mapper(common_chat_msg & msg) : common_chat_peg_mapper(msg) {}
|
||||
virtual void from_ast(const common_peg_ast_arena & arena, const common_peg_parse_result & result);
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
#include "chat-tts-norm.h"
|
||||
|
||||
#include "jinja/runtime.h"
|
||||
#include "jinja/value.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// TTS normalization patterns, see docs/tts-norm-patterns.md for the specification
|
||||
// keep this file in sync with the python reference: scripts/chat_tts_norm.py
|
||||
|
||||
using namespace jinja;
|
||||
|
||||
//
|
||||
// character classes
|
||||
// they are defined with explicit codepoint ranges, independent of any unicode database version,
|
||||
// so that the C++ and python implementations agree on every codepoint
|
||||
//
|
||||
|
||||
struct tts_norm_range {
|
||||
uint32_t lo;
|
||||
uint32_t hi;
|
||||
};
|
||||
|
||||
static const tts_norm_range TTS_NORM_SPACE[] = {
|
||||
{0x0009, 0x000D}, {0x0020, 0x0020}, {0x0085, 0x0085}, {0x00A0, 0x00A0}, {0x1680, 0x1680},
|
||||
{0x2000, 0x200A}, {0x2028, 0x2029}, {0x202F, 0x202F}, {0x205F, 0x205F}, {0x3000, 0x3000},
|
||||
};
|
||||
|
||||
static const tts_norm_range TTS_NORM_PUNCT[] = {
|
||||
{0x0021, 0x002F}, {0x003A, 0x0040}, {0x005B, 0x0060}, {0x007B, 0x007E},
|
||||
{0x00A1, 0x00A9}, {0x00AB, 0x00B4}, {0x00B6, 0x00B9}, {0x00BB, 0x00BF}, {0x00D7, 0x00D7}, {0x00F7, 0x00F7},
|
||||
{0x2010, 0x2027}, {0x2030, 0x205E}, {0x20A0, 0x20CF}, {0x2100, 0x218F}, {0x2190, 0x2BFF},
|
||||
{0x3001, 0x3003}, {0x3008, 0x3020}, {0x3030, 0x3030}, {0x303D, 0x303D}, {0x30FB, 0x30FB},
|
||||
{0xFE10, 0xFE19}, {0xFE30, 0xFE4F}, {0xFE50, 0xFE6B},
|
||||
{0xFF01, 0xFF0F}, {0xFF1A, 0xFF20}, {0xFF3B, 0xFF40}, {0xFF5B, 0xFF65},
|
||||
{0x1F000, 0x1FAFF},
|
||||
};
|
||||
|
||||
static const tts_norm_range TTS_NORM_CONTROL[] = {
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x200B, 0x200F}, {0x2060, 0x206F}, {0xFEFF, 0xFEFF},
|
||||
};
|
||||
|
||||
static const tts_norm_range TTS_NORM_HAN[] = {
|
||||
{0x2E80, 0x2FDF}, {0x3005, 0x3005}, {0x3007, 0x3007}, {0x3021, 0x3029}, {0x3038, 0x303B},
|
||||
{0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, {0xF900, 0xFAFF}, {0x20000, 0x323AF},
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
static bool tts_norm_in(const tts_norm_range (&ranges)[N], uint32_t c) {
|
||||
for (const auto & r : ranges) {
|
||||
if (c >= r.lo && c <= r.hi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool tts_norm_is_alpha(uint32_t c) {
|
||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
|
||||
return true;
|
||||
}
|
||||
if (c == 0xAA || c == 0xB5 || c == 0xBA) {
|
||||
return true;
|
||||
}
|
||||
return c >= 0xC0 && !tts_norm_in(TTS_NORM_SPACE, c) && !tts_norm_in(TTS_NORM_PUNCT, c) && !tts_norm_in(TTS_NORM_CONTROL, c);
|
||||
}
|
||||
|
||||
// kind is one of "dspawulhc"
|
||||
static bool tts_norm_class_match(char kind, uint32_t c) {
|
||||
switch (kind) {
|
||||
case 'd': return c >= '0' && c <= '9';
|
||||
case 's': return tts_norm_in(TTS_NORM_SPACE, c);
|
||||
case 'p': return tts_norm_in(TTS_NORM_PUNCT, c);
|
||||
case 'a': return tts_norm_is_alpha(c);
|
||||
case 'w': return tts_norm_is_alpha(c) || (c >= '0' && c <= '9');
|
||||
case 'u': return c >= 'A' && c <= 'Z';
|
||||
case 'l': return c >= 'a' && c <= 'z';
|
||||
case 'h': return tts_norm_in(TTS_NORM_HAN, c);
|
||||
case 'c': return tts_norm_in(TTS_NORM_CONTROL, c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool tts_norm_is_class_letter(uint32_t c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
return c < 0x80 && std::string("dspawulhc").find(static_cast<char>(c)) != std::string::npos;
|
||||
}
|
||||
|
||||
static uint32_t tts_norm_ascii_lower(uint32_t c) {
|
||||
return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
|
||||
}
|
||||
|
||||
static uint32_t tts_norm_ascii_swapcase(uint32_t c) {
|
||||
if (c >= 'A' && c <= 'Z') return c + ('a' - 'A');
|
||||
if (c >= 'a' && c <= 'z') return c - ('a' - 'A');
|
||||
return c;
|
||||
}
|
||||
|
||||
static bool tts_norm_is_ascii_alnum(uint32_t c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
// invalid bytes are decoded as U+FFFD, one per byte; offs[i] is the byte offset of codepoint i, offs[n] = text size
|
||||
static void tts_norm_decode_utf8(const std::string & s, std::vector<uint32_t> & cpts, std::vector<size_t> & offs) {
|
||||
cpts.clear();
|
||||
offs.clear();
|
||||
size_t i = 0;
|
||||
const size_t n = s.size();
|
||||
while (i < n) {
|
||||
offs.push_back(i);
|
||||
const unsigned char c = static_cast<unsigned char>(s[i]);
|
||||
uint32_t cp = 0;
|
||||
size_t len = 0;
|
||||
if (c < 0x80) {
|
||||
cp = c; len = 1;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
cp = c & 0x1F; len = 2;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
cp = c & 0x0F; len = 3;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
cp = c & 0x07; len = 4;
|
||||
}
|
||||
bool ok = len > 0 && i + len <= n;
|
||||
for (size_t k = 1; ok && k < len; k++) {
|
||||
const unsigned char cc = static_cast<unsigned char>(s[i + k]);
|
||||
if ((cc & 0xC0) != 0x80) {
|
||||
ok = false;
|
||||
} else {
|
||||
cp = (cp << 6) | (cc & 0x3F);
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
cpts.push_back(0xFFFD);
|
||||
i += 1;
|
||||
} else {
|
||||
cpts.push_back(cp);
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
offs.push_back(n);
|
||||
}
|
||||
|
||||
//
|
||||
// AST
|
||||
//
|
||||
|
||||
enum class tts_norm_op {
|
||||
LITERAL, // one codepoint
|
||||
SET, // one codepoint from a set; class escapes like %d are sets with a single class
|
||||
ANY, // any codepoint
|
||||
START, // ^
|
||||
END, // $
|
||||
BOUNDARY, // %b
|
||||
GROUP, // (...), (?:...), (?<name>...)
|
||||
LOOKAHEAD, // (?=...), (?!...)
|
||||
LOOKBEHIND, // (?<=c), (?<!c) - exactly one codepoint
|
||||
};
|
||||
|
||||
struct tts_norm_class {
|
||||
char kind;
|
||||
bool neg;
|
||||
};
|
||||
|
||||
struct tts_norm_set {
|
||||
bool neg = false;
|
||||
std::vector<std::pair<uint32_t, uint32_t>> ranges;
|
||||
std::vector<tts_norm_class> classes;
|
||||
};
|
||||
|
||||
struct tts_norm_item;
|
||||
using tts_norm_seq = std::vector<tts_norm_item>;
|
||||
|
||||
struct tts_norm_pattern_node {
|
||||
tts_norm_op op = tts_norm_op::GROUP;
|
||||
uint32_t cp = 0; // LITERAL
|
||||
tts_norm_set set; // SET
|
||||
std::vector<tts_norm_seq> alts; // GROUP, LOOKAHEAD; LOOKBEHIND holds a single item
|
||||
int capture = -1; // GROUP: capture index, -1 for non-capturing
|
||||
bool neg = false; // LOOKAHEAD, LOOKBEHIND
|
||||
};
|
||||
|
||||
static constexpr int TTS_NORM_INF = INT_MAX;
|
||||
static constexpr int TTS_NORM_MAX_REPEAT = 1000;
|
||||
static constexpr size_t TTS_NORM_MAX_PATTERN_LEN = 4096;
|
||||
|
||||
struct tts_norm_item {
|
||||
tts_norm_pattern_node atom;
|
||||
int min = 1;
|
||||
int max = 1;
|
||||
};
|
||||
|
||||
static bool tts_norm_is_zero_width(tts_norm_op op) {
|
||||
return op == tts_norm_op::START || op == tts_norm_op::END || op == tts_norm_op::BOUNDARY
|
||||
|| op == tts_norm_op::LOOKAHEAD || op == tts_norm_op::LOOKBEHIND;
|
||||
}
|
||||
|
||||
//
|
||||
// parser
|
||||
//
|
||||
|
||||
struct tts_norm_parser {
|
||||
std::vector<uint32_t> s;
|
||||
size_t p = 0;
|
||||
size_t & n_caps;
|
||||
|
||||
tts_norm_parser(std::vector<uint32_t> src, size_t & n_caps) : s(std::move(src)), n_caps(n_caps) {}
|
||||
|
||||
[[noreturn]] void fail(const std::string & msg) const {
|
||||
throw std::runtime_error("invalid TTS norm pattern: " + msg + " at position " + std::to_string(p));
|
||||
}
|
||||
|
||||
bool eof() const { return p >= s.size(); }
|
||||
uint32_t peek() const { return s[p]; }
|
||||
bool peek_is(uint32_t c) const { return !eof() && s[p] == c; }
|
||||
|
||||
void expect(uint32_t c, const char * what) {
|
||||
if (!peek_is(c)) {
|
||||
fail(std::string("expected ") + what);
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
std::vector<tts_norm_seq> parse_alts() {
|
||||
std::vector<tts_norm_seq> alts;
|
||||
alts.push_back(parse_seq());
|
||||
while (peek_is('|')) {
|
||||
p++;
|
||||
alts.push_back(parse_seq());
|
||||
}
|
||||
return alts;
|
||||
}
|
||||
|
||||
tts_norm_seq parse_seq() {
|
||||
tts_norm_seq seq;
|
||||
while (!eof() && peek() != '|' && peek() != ')') {
|
||||
tts_norm_item item;
|
||||
item.atom = parse_atom();
|
||||
parse_quantifier(item);
|
||||
seq.push_back(std::move(item));
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
|
||||
int parse_int() {
|
||||
if (eof() || peek() < '0' || peek() > '9') {
|
||||
fail("expected number in quantifier");
|
||||
}
|
||||
int v = 0;
|
||||
while (!eof() && peek() >= '0' && peek() <= '9') {
|
||||
v = v * 10 + static_cast<int>(peek() - '0');
|
||||
if (v > TTS_NORM_MAX_REPEAT) {
|
||||
fail("repetition count exceeds " + std::to_string(TTS_NORM_MAX_REPEAT));
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void parse_quantifier(tts_norm_item & item) {
|
||||
if (eof()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t c = peek();
|
||||
if (c != '?' && c != '*' && c != '+' && c != '{') {
|
||||
return;
|
||||
}
|
||||
if (tts_norm_is_zero_width(item.atom.op)) {
|
||||
fail("zero-width assertion cannot be repeated");
|
||||
}
|
||||
p++;
|
||||
if (c == '?') {
|
||||
item.min = 0; item.max = 1;
|
||||
} else if (c == '*') {
|
||||
item.min = 0; item.max = TTS_NORM_INF;
|
||||
} else if (c == '+') {
|
||||
item.min = 1; item.max = TTS_NORM_INF;
|
||||
} else {
|
||||
item.min = parse_int();
|
||||
item.max = item.min;
|
||||
if (peek_is(',')) {
|
||||
p++;
|
||||
item.max = peek_is('}') ? TTS_NORM_INF : parse_int();
|
||||
}
|
||||
expect('}', "'}' to close quantifier");
|
||||
if (item.max < item.min) {
|
||||
fail("quantifier max is smaller than min");
|
||||
}
|
||||
}
|
||||
if (!eof() && (peek() == '?' || peek() == '*' || peek() == '+' || peek() == '{')) {
|
||||
fail("multiple quantifiers");
|
||||
}
|
||||
}
|
||||
|
||||
tts_norm_pattern_node parse_group() {
|
||||
// '(' already consumed
|
||||
tts_norm_pattern_node node;
|
||||
node.op = tts_norm_op::GROUP;
|
||||
if (peek_is('?')) {
|
||||
p++;
|
||||
if (eof()) {
|
||||
fail("unterminated group");
|
||||
}
|
||||
const uint32_t c = peek();
|
||||
if (c == ':') {
|
||||
p++;
|
||||
} else if (c == '=' || c == '!') {
|
||||
p++;
|
||||
node.op = tts_norm_op::LOOKAHEAD;
|
||||
node.neg = c == '!';
|
||||
} else if (c == '<' && p + 1 < s.size() && (s[p + 1] == '=' || s[p + 1] == '!')) {
|
||||
node.op = tts_norm_op::LOOKBEHIND;
|
||||
node.neg = s[p + 1] == '!';
|
||||
p += 2;
|
||||
tts_norm_item item;
|
||||
if (eof() || peek() == ')') {
|
||||
fail("lookbehind must contain exactly one codepoint matcher");
|
||||
}
|
||||
item.atom = parse_atom();
|
||||
if (item.atom.op != tts_norm_op::LITERAL && item.atom.op != tts_norm_op::SET && item.atom.op != tts_norm_op::ANY) {
|
||||
fail("lookbehind must contain exactly one codepoint matcher");
|
||||
}
|
||||
if (!peek_is(')')) {
|
||||
fail("lookbehind must contain exactly one codepoint matcher");
|
||||
}
|
||||
p++;
|
||||
node.alts.push_back(tts_norm_seq{});
|
||||
node.alts[0].push_back(std::move(item));
|
||||
return node;
|
||||
} else if (c == 'i') {
|
||||
fail("(?i) is only allowed at the start of the pattern");
|
||||
} else {
|
||||
fail("unknown group type");
|
||||
}
|
||||
} else {
|
||||
node.capture = static_cast<int>(n_caps++);
|
||||
}
|
||||
node.alts = parse_alts();
|
||||
expect(')', "')' to close group");
|
||||
return node;
|
||||
}
|
||||
|
||||
// after '%' inside or outside of a set
|
||||
// returns true if it is a class (stored in cls), false if it is a literal (stored in lit)
|
||||
bool parse_escape(bool in_set, tts_norm_class & cls, uint32_t & lit, bool & boundary) {
|
||||
boundary = false;
|
||||
if (eof()) {
|
||||
fail("dangling '%'");
|
||||
}
|
||||
const uint32_t e = s[p++];
|
||||
if (tts_norm_is_class_letter(e)) {
|
||||
cls.kind = static_cast<char>(tts_norm_ascii_lower(e));
|
||||
cls.neg = e >= 'A' && e <= 'Z';
|
||||
return true;
|
||||
}
|
||||
if (e == 'b' && !in_set) {
|
||||
boundary = true;
|
||||
return false;
|
||||
}
|
||||
if (tts_norm_is_ascii_alnum(e)) {
|
||||
p--;
|
||||
fail(std::string("unknown escape '%") + static_cast<char>(e) + "'");
|
||||
}
|
||||
lit = e;
|
||||
return false;
|
||||
}
|
||||
|
||||
tts_norm_pattern_node parse_set() {
|
||||
// '[' already consumed
|
||||
tts_norm_pattern_node node;
|
||||
node.op = tts_norm_op::SET;
|
||||
if (peek_is('^')) {
|
||||
node.set.neg = true;
|
||||
p++;
|
||||
}
|
||||
bool empty = true;
|
||||
while (true) {
|
||||
if (eof()) {
|
||||
fail("unterminated set");
|
||||
}
|
||||
if (peek() == ']') {
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
empty = false;
|
||||
uint32_t lo = 0;
|
||||
if (peek() == '%') {
|
||||
p++;
|
||||
tts_norm_class cls{};
|
||||
bool boundary = false;
|
||||
if (parse_escape(true, cls, lo, boundary)) {
|
||||
node.set.classes.push_back(cls);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
lo = s[p++];
|
||||
}
|
||||
uint32_t hi = lo;
|
||||
if (peek_is('-') && p + 1 < s.size() && s[p + 1] != ']') {
|
||||
p++;
|
||||
if (peek() == '%') {
|
||||
p++;
|
||||
tts_norm_class cls{};
|
||||
bool boundary = false;
|
||||
if (parse_escape(true, cls, hi, boundary)) {
|
||||
fail("character class cannot be used in a range");
|
||||
}
|
||||
} else {
|
||||
hi = s[p++];
|
||||
}
|
||||
if (hi < lo) {
|
||||
fail("invalid range in set");
|
||||
}
|
||||
}
|
||||
node.set.ranges.emplace_back(lo, hi);
|
||||
}
|
||||
if (empty) {
|
||||
fail("empty set");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
tts_norm_pattern_node parse_atom() {
|
||||
const uint32_t c = s[p++];
|
||||
tts_norm_pattern_node node;
|
||||
switch (c) {
|
||||
case '(':
|
||||
return parse_group();
|
||||
case '[':
|
||||
return parse_set();
|
||||
case '.':
|
||||
node.op = tts_norm_op::ANY;
|
||||
return node;
|
||||
case '^':
|
||||
node.op = tts_norm_op::START;
|
||||
return node;
|
||||
case '$':
|
||||
node.op = tts_norm_op::END;
|
||||
return node;
|
||||
case '%': {
|
||||
tts_norm_class cls{};
|
||||
uint32_t lit = 0;
|
||||
bool boundary = false;
|
||||
if (parse_escape(false, cls, lit, boundary)) {
|
||||
node.op = tts_norm_op::SET;
|
||||
node.set.classes.push_back(cls);
|
||||
} else if (boundary) {
|
||||
node.op = tts_norm_op::BOUNDARY;
|
||||
} else {
|
||||
node.op = tts_norm_op::LITERAL;
|
||||
node.cp = lit;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '{':
|
||||
p--;
|
||||
fail("nothing to repeat");
|
||||
case ']':
|
||||
case '}':
|
||||
p--;
|
||||
fail(std::string("unescaped '") + static_cast<char>(c) + "', use '%" + static_cast<char>(c) + "'");
|
||||
default:
|
||||
node.op = tts_norm_op::LITERAL;
|
||||
node.cp = c;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// matcher
|
||||
// PEG-style: alternatives are ordered (the first one that matches wins) and quantifiers are greedy and possessive
|
||||
// (they never give back what they consumed), so there is no backtracking
|
||||
//
|
||||
|
||||
struct tts_norm_matcher {
|
||||
const std::vector<uint32_t> & t;
|
||||
bool icase;
|
||||
std::vector<std::pair<int64_t, int64_t>> caps;
|
||||
|
||||
bool is_w(size_t i) const {
|
||||
return i < t.size() && tts_norm_class_match('w', t[i]);
|
||||
}
|
||||
|
||||
bool set_match(const tts_norm_set & st, uint32_t c) const {
|
||||
auto test = [&](uint32_t x) {
|
||||
for (const auto & r : st.ranges) {
|
||||
if (x >= r.first && x <= r.second) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto & k : st.classes) {
|
||||
if (tts_norm_class_match(k.kind, x) != k.neg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
bool m = test(c);
|
||||
if (!m && icase) {
|
||||
const uint32_t o = tts_norm_ascii_swapcase(c);
|
||||
if (o != c) {
|
||||
m = test(o);
|
||||
}
|
||||
}
|
||||
return m != st.neg;
|
||||
}
|
||||
|
||||
bool match_alts(const std::vector<tts_norm_seq> & alts, size_t i, size_t & end) {
|
||||
for (const auto & seq : alts) {
|
||||
auto saved = caps;
|
||||
if (match_seq(seq, i, end)) {
|
||||
return true;
|
||||
}
|
||||
caps = std::move(saved);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match_seq(const tts_norm_seq & seq, size_t i, size_t & end) {
|
||||
size_t pos = i;
|
||||
for (const auto & it : seq) {
|
||||
int count = 0;
|
||||
while (count < it.max) {
|
||||
const bool is_group = it.atom.op == tts_norm_op::GROUP;
|
||||
std::vector<std::pair<int64_t, int64_t>> saved;
|
||||
if (is_group) {
|
||||
saved = caps;
|
||||
}
|
||||
size_t e = pos;
|
||||
if (!match_once(it.atom, pos, e)) {
|
||||
if (is_group) {
|
||||
caps = std::move(saved);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (e == pos) {
|
||||
// an empty iteration would repeat forever, it satisfies any remaining minimum
|
||||
count = std::max(count + 1, it.min);
|
||||
break;
|
||||
}
|
||||
pos = e;
|
||||
count++;
|
||||
}
|
||||
if (count < it.min) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
end = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match_once(const tts_norm_pattern_node & node, size_t i, size_t & end) {
|
||||
const size_t n = t.size();
|
||||
switch (node.op) {
|
||||
case tts_norm_op::LITERAL:
|
||||
if (i < n && (t[i] == node.cp || (icase && tts_norm_ascii_lower(t[i]) == tts_norm_ascii_lower(node.cp)))) {
|
||||
end = i + 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case tts_norm_op::SET:
|
||||
if (i < n && set_match(node.set, t[i])) {
|
||||
end = i + 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case tts_norm_op::ANY:
|
||||
if (i < n) {
|
||||
end = i + 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case tts_norm_op::START:
|
||||
end = i;
|
||||
return i == 0;
|
||||
case tts_norm_op::END:
|
||||
end = i;
|
||||
return i == n;
|
||||
case tts_norm_op::BOUNDARY:
|
||||
end = i;
|
||||
return (i > 0 && is_w(i - 1)) != is_w(i);
|
||||
case tts_norm_op::GROUP: {
|
||||
size_t e = i;
|
||||
if (!match_alts(node.alts, i, e)) {
|
||||
return false;
|
||||
}
|
||||
if (node.capture >= 0) {
|
||||
caps[node.capture] = {static_cast<int64_t>(i), static_cast<int64_t>(e)};
|
||||
}
|
||||
end = e;
|
||||
return true;
|
||||
}
|
||||
case tts_norm_op::LOOKAHEAD: {
|
||||
// captures made inside a lookahead are discarded
|
||||
auto saved = caps;
|
||||
size_t e = i;
|
||||
const bool ok = match_alts(node.alts, i, e);
|
||||
caps = std::move(saved);
|
||||
end = i;
|
||||
return ok != node.neg;
|
||||
}
|
||||
case tts_norm_op::LOOKBEHIND: {
|
||||
size_t e = 0;
|
||||
const bool ok = i > 0 && match_once(node.alts[0][0].atom, i - 1, e) && e == i;
|
||||
end = i;
|
||||
return ok != node.neg;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// common_tts_norm_pattern
|
||||
//
|
||||
|
||||
common_tts_norm_pattern::common_tts_norm_pattern(const std::string & src) {
|
||||
if (src.size() > TTS_NORM_MAX_PATTERN_LEN) {
|
||||
throw std::runtime_error("invalid TTS norm pattern: pattern is longer than " + std::to_string(TTS_NORM_MAX_PATTERN_LEN) + " bytes");
|
||||
}
|
||||
std::vector<uint32_t> cpts;
|
||||
std::vector<size_t> offs;
|
||||
tts_norm_decode_utf8(src, cpts, offs);
|
||||
|
||||
tts_norm_parser parser(std::move(cpts), n_caps);
|
||||
static const std::vector<uint32_t> icase_prefix = {'(', '?', 'i', ')'};
|
||||
if (parser.s.size() >= 4 && std::equal(icase_prefix.begin(), icase_prefix.end(), parser.s.begin())) {
|
||||
icase = true;
|
||||
parser.p = 4;
|
||||
}
|
||||
|
||||
root = std::make_unique<tts_norm_pattern_node>();
|
||||
root->op = tts_norm_op::GROUP;
|
||||
root->alts = parser.parse_alts();
|
||||
if (!parser.eof()) {
|
||||
parser.fail("unbalanced ')'");
|
||||
}
|
||||
}
|
||||
|
||||
common_tts_norm_pattern::~common_tts_norm_pattern() = default;
|
||||
common_tts_norm_pattern::common_tts_norm_pattern(common_tts_norm_pattern &&) noexcept = default;
|
||||
common_tts_norm_pattern & common_tts_norm_pattern::operator=(common_tts_norm_pattern &&) noexcept = default;
|
||||
|
||||
bool common_tts_norm_pattern::match_at(const std::vector<uint32_t> & cpts, size_t pos, size_t & end, std::vector<std::pair<int64_t, int64_t>> & groups) const {
|
||||
tts_norm_matcher m{cpts, icase, std::vector<std::pair<int64_t, int64_t>>(n_caps, {-1, -1})};
|
||||
size_t e = pos;
|
||||
if (!m.match_alts(root->alts, pos, e)) {
|
||||
return false;
|
||||
}
|
||||
end = e;
|
||||
groups = std::move(m.caps);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t common_tts_norm_pattern::n_groups() const {
|
||||
return n_caps;
|
||||
}
|
||||
|
||||
//
|
||||
// scan / sub
|
||||
//
|
||||
|
||||
std::vector<common_tts_norm_segment> common_tts_norm_scan(const std::string & text, const std::vector<const common_tts_norm_pattern *> & rules) {
|
||||
std::vector<uint32_t> cpts;
|
||||
std::vector<size_t> offs;
|
||||
tts_norm_decode_utf8(text, cpts, offs);
|
||||
|
||||
std::vector<common_tts_norm_segment> out;
|
||||
const size_t n = cpts.size();
|
||||
size_t i = 0;
|
||||
size_t pending = 0; // start of the current unmatched run
|
||||
std::vector<std::pair<int64_t, int64_t>> groups;
|
||||
|
||||
while (i < n) {
|
||||
bool matched = false;
|
||||
for (size_t r = 0; r < rules.size(); r++) {
|
||||
size_t end = i;
|
||||
if (rules[r]->match_at(cpts, i, end, groups) && end > i) {
|
||||
if (pending < i) {
|
||||
out.push_back({-1, offs[pending], offs[i], {}});
|
||||
}
|
||||
common_tts_norm_segment seg;
|
||||
seg.rule = static_cast<int>(r);
|
||||
seg.start = offs[i];
|
||||
seg.end = offs[end];
|
||||
for (const auto & g : groups) {
|
||||
if (g.first < 0) {
|
||||
seg.groups.emplace_back(std::string::npos, std::string::npos);
|
||||
} else {
|
||||
seg.groups.emplace_back(offs[g.first], offs[g.second]);
|
||||
}
|
||||
}
|
||||
out.push_back(std::move(seg));
|
||||
i = end;
|
||||
pending = i;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (pending < n) {
|
||||
out.push_back({-1, offs[pending], offs[n], {}});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
struct tts_norm_repl_piece {
|
||||
std::string literal;
|
||||
int group = -1; // 0 = whole match, 1.. = groups
|
||||
};
|
||||
|
||||
static std::vector<tts_norm_repl_piece> tts_norm_parse_repl(const std::string & repl, const common_tts_norm_pattern & pattern) {
|
||||
std::vector<tts_norm_repl_piece> pieces;
|
||||
std::string lit;
|
||||
auto flush = [&]() {
|
||||
if (!lit.empty()) {
|
||||
pieces.push_back({lit, -1});
|
||||
lit.clear();
|
||||
}
|
||||
};
|
||||
for (size_t i = 0; i < repl.size(); i++) {
|
||||
if (repl[i] != '%') {
|
||||
lit += repl[i];
|
||||
continue;
|
||||
}
|
||||
if (i + 1 >= repl.size()) {
|
||||
throw std::runtime_error("invalid TTS norm replacement: dangling '%'");
|
||||
}
|
||||
const char c = repl[++i];
|
||||
if (c == '%') {
|
||||
lit += '%';
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
const int g = c - '0';
|
||||
if (static_cast<size_t>(g) > pattern.n_groups()) {
|
||||
throw std::runtime_error("invalid TTS norm replacement: group %" + std::string(1, c) + " does not exist");
|
||||
}
|
||||
flush();
|
||||
pieces.push_back({"", g});
|
||||
} else {
|
||||
throw std::runtime_error("invalid TTS norm replacement: unknown escape '%" + std::string(1, c) + "'");
|
||||
}
|
||||
}
|
||||
flush();
|
||||
return pieces;
|
||||
}
|
||||
|
||||
std::string common_tts_norm_sub(const std::string & text, const common_tts_norm_pattern & pattern, const std::string & repl) {
|
||||
const auto pieces = tts_norm_parse_repl(repl, pattern);
|
||||
std::string out;
|
||||
for (const auto & seg : common_tts_norm_scan(text, {&pattern})) {
|
||||
if (seg.rule < 0) {
|
||||
out.append(text, seg.start, seg.end - seg.start);
|
||||
continue;
|
||||
}
|
||||
for (const auto & piece : pieces) {
|
||||
if (piece.group < 0) {
|
||||
out += piece.literal;
|
||||
} else if (piece.group == 0) {
|
||||
out.append(text, seg.start, seg.end - seg.start);
|
||||
} else {
|
||||
const auto & g = seg.groups[piece.group - 1];
|
||||
if (g.first != std::string::npos) {
|
||||
out.append(text, g.first, g.second - g.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//
|
||||
// template builtins
|
||||
//
|
||||
|
||||
// rules can be: a pattern string (rule name "match"), a dict {name: pattern}, or a list of [name, pattern] pairs
|
||||
static std::vector<std::pair<std::string, std::string>> tts_norm_rules_from_value(const value & v) {
|
||||
std::vector<std::pair<std::string, std::string>> rules;
|
||||
if (is_val<value_string>(v)) {
|
||||
rules.emplace_back("match", v->as_string().str());
|
||||
} else if (is_val<value_object>(v)) {
|
||||
for (const auto & kv : v->as_ordered_object()) {
|
||||
if (!is_val<value_string>(kv.second)) {
|
||||
throw std::runtime_error("tts_norm_scan: rule pattern must be a string");
|
||||
}
|
||||
rules.emplace_back(kv.first->as_string().str(), kv.second->as_string().str());
|
||||
}
|
||||
} else if (is_val<value_array>(v)) {
|
||||
for (const auto & item : v->as_array()) {
|
||||
if (!is_val<value_array>(item) || item->as_array().size() != 2
|
||||
|| !is_val<value_string>(item->as_array()[0]) || !is_val<value_string>(item->as_array()[1])) {
|
||||
throw std::runtime_error("tts_norm_scan: each rule must be a [name, pattern] pair of strings");
|
||||
}
|
||||
rules.emplace_back(item->as_array()[0]->as_string().str(), item->as_array()[1]->as_string().str());
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("tts_norm_scan: rules must be a string, a dict or a list of [name, pattern] pairs, got " + v->type());
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
static value tts_norm_builtin_scan(const func_args & args) {
|
||||
args.ensure_count(2, 2);
|
||||
args.ensure_val<value_string>(args.get_pos(0));
|
||||
const string text_val = args.get_pos(0)->as_string();
|
||||
const std::string text = text_val.str();
|
||||
const bool is_input = text_val.all_parts_are_input();
|
||||
|
||||
const auto rule_srcs = tts_norm_rules_from_value(args.get_pos(1));
|
||||
std::vector<common_tts_norm_pattern> patterns;
|
||||
patterns.reserve(rule_srcs.size());
|
||||
std::vector<const common_tts_norm_pattern *> ptrs;
|
||||
for (const auto & r : rule_srcs) {
|
||||
patterns.emplace_back(r.second);
|
||||
}
|
||||
for (const auto & p : patterns) {
|
||||
ptrs.push_back(&p);
|
||||
}
|
||||
|
||||
auto mk_str = [&](size_t start, size_t end) -> value {
|
||||
return mk_val<value_string>(string(text.substr(start, end - start), is_input));
|
||||
};
|
||||
|
||||
auto out = mk_val<value_array>();
|
||||
for (const auto & seg : common_tts_norm_scan(text, ptrs)) {
|
||||
auto groups = mk_val<value_array>();
|
||||
for (const auto & g : seg.groups) {
|
||||
// groups that did not participate are empty strings, so they render as nothing and are falsy
|
||||
groups->push_back(g.first == std::string::npos ? mk_str(0, 0) : mk_str(g.first, g.second));
|
||||
}
|
||||
auto obj = mk_val<value_object>();
|
||||
obj->insert("rule", seg.rule < 0 ? value(mk_val<value_none>()) : value(mk_val<value_string>(rule_srcs[seg.rule].first)));
|
||||
obj->insert("text", mk_str(seg.start, seg.end));
|
||||
obj->insert("groups", groups);
|
||||
out->push_back(obj);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static value tts_norm_builtin_sub(const func_args & args) {
|
||||
args.ensure_count(3, 3);
|
||||
args.ensure_vals<value_string, value_string, value_string>();
|
||||
const string text_val = args.get_pos(0)->as_string();
|
||||
const common_tts_norm_pattern pattern(args.get_pos(1)->as_string().str());
|
||||
const std::string res = common_tts_norm_sub(text_val.str(), pattern, args.get_pos(2)->as_string().str());
|
||||
return mk_val<value_string>(string(res, text_val.all_parts_are_input()));
|
||||
}
|
||||
|
||||
void common_chat_tts_norm_add_builtins(context & ctx) {
|
||||
ctx.set_val("tts_norm_scan", mk_val<value_func>("tts_norm_scan", tts_norm_builtin_scan));
|
||||
ctx.set_val("tts_norm_sub", mk_val<value_func>("tts_norm_sub", tts_norm_builtin_sub));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// TTS normalization patterns: a small, fully specified matching language for TTS text normalization templates
|
||||
//
|
||||
// it is NOT a regex engine: the semantics are defined by llama.cpp (see docs/tts-norm-patterns.md), so that this
|
||||
// implementation and the python reference (scripts/chat_tts_norm.py) give the same result for every input
|
||||
//
|
||||
// two functions are exposed to task templates (e.g. "tts") via common_chat_tts_norm_add_builtins():
|
||||
// tts_norm_scan(text, rules) -> list of segments {rule, text, groups}
|
||||
// tts_norm_sub(text, pattern, repl) -> string
|
||||
|
||||
namespace jinja {
|
||||
struct context;
|
||||
}
|
||||
|
||||
struct tts_norm_pattern_node;
|
||||
|
||||
class common_tts_norm_pattern {
|
||||
public:
|
||||
// throws std::runtime_error on invalid pattern
|
||||
explicit common_tts_norm_pattern(const std::string & src);
|
||||
~common_tts_norm_pattern();
|
||||
common_tts_norm_pattern(common_tts_norm_pattern &&) noexcept;
|
||||
common_tts_norm_pattern & operator=(common_tts_norm_pattern &&) noexcept;
|
||||
|
||||
// match anchored at codepoint index pos; on success, end is the codepoint index after the match
|
||||
// groups[i] holds [start, end) codepoint indices of capture i+1, or {-1, -1} if the group did not participate
|
||||
bool match_at(const std::vector<uint32_t> & cpts, size_t pos, size_t & end, std::vector<std::pair<int64_t, int64_t>> & groups) const;
|
||||
|
||||
size_t n_groups() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<tts_norm_pattern_node> root;
|
||||
size_t n_caps = 0;
|
||||
bool icase = false;
|
||||
};
|
||||
|
||||
// segment produced by common_tts_norm_scan(); byte offsets into the input text
|
||||
struct common_tts_norm_segment {
|
||||
int rule = -1; // index of the matched rule, -1 for unmatched text
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
std::vector<std::pair<size_t, size_t>> groups; // byte offsets; {npos, npos} for groups that did not participate
|
||||
};
|
||||
|
||||
// split text into matched and unmatched segments
|
||||
// at each position, rules are tried in order and the first non-empty match wins; otherwise one codepoint is skipped
|
||||
std::vector<common_tts_norm_segment> common_tts_norm_scan(const std::string & text, const std::vector<const common_tts_norm_pattern *> & rules);
|
||||
|
||||
// replace every non-overlapping match of pattern with repl
|
||||
// repl references: %0 (whole match), %1..%9 (groups), %% (literal percent)
|
||||
std::string common_tts_norm_sub(const std::string & text, const common_tts_norm_pattern & pattern, const std::string & repl);
|
||||
|
||||
// add tts_norm_scan() and tts_norm_sub() to the context of a task template
|
||||
// they are not jinja global builtins on purpose: chat templates are not affected
|
||||
void common_chat_tts_norm_add_builtins(jinja::context & ctx);
|
||||
+38
-18
@@ -3,6 +3,7 @@
|
||||
#include "chat-auto-parser-helpers.h"
|
||||
#include "chat-auto-parser.h"
|
||||
#include "chat-peg-parser.h"
|
||||
#include "chat-tts-norm.h"
|
||||
#include "common.h"
|
||||
#include "ggml.h"
|
||||
#include "json-schema-to-grammar.h"
|
||||
@@ -807,6 +808,8 @@ bool common_chat_templates_was_explicit(const struct common_chat_templates * tmp
|
||||
// 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());
|
||||
// text normalization helpers: tts_norm_scan() and tts_norm_sub(), see docs/tts-norm-patterns.md
|
||||
common_chat_tts_norm_add_builtins(ctx);
|
||||
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()));
|
||||
@@ -819,21 +822,29 @@ 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 std::string string_lower(std::string s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return (char) std::tolower(c); });
|
||||
return s;
|
||||
}
|
||||
|
||||
// LFM3-Audio: language code -> name
|
||||
static std::string lfm3_language_name(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" },
|
||||
};
|
||||
const auto it = names.find(string_lower(language));
|
||||
return it != names.end() ? it->second : language;
|
||||
}
|
||||
|
||||
// LFM3-Audio: no language = auto detect
|
||||
static std::string lfm3_asr_system_prompt(const std::string & language) {
|
||||
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) + ".";
|
||||
return "Perform ASR in " + lfm3_language_name(language) + ".";
|
||||
}
|
||||
|
||||
common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates * chat_templates, const std::string & language) {
|
||||
@@ -868,6 +879,27 @@ std::string common_chat_get_tts_prompt(const common_chat_templates * chat_templa
|
||||
});
|
||||
}
|
||||
|
||||
std::string common_chat_get_speech_system_prompt(const common_chat_templates * chat_templates,
|
||||
const std::string & language, const std::string & voice) {
|
||||
if (!chat_templates || !chat_templates->template_default || !is_lfm3_template(chat_templates->template_default->source())) {
|
||||
return "";
|
||||
}
|
||||
// same voices as the "tts" template
|
||||
const std::string v = string_lower(voice);
|
||||
const bool has_voice = v == "female" || v == "male";
|
||||
if (language.empty() && !has_voice) {
|
||||
return "";
|
||||
}
|
||||
std::string out = "Speak";
|
||||
if (!language.empty()) {
|
||||
out += " in " + lfm3_language_name(language);
|
||||
}
|
||||
if (has_voice) {
|
||||
out += " using a " + v + " voice";
|
||||
}
|
||||
return out + ".";
|
||||
}
|
||||
|
||||
std::string common_chat_templates_source(const struct common_chat_templates * tmpls, const std::string & variant) {
|
||||
if (!variant.empty()) {
|
||||
if (variant == "tool_use") {
|
||||
@@ -1245,13 +1277,6 @@ static common_chat_params common_chat_params_init_lfm2_5(const common_chat_templ
|
||||
return common_chat_params_init_lfm2(tmpl, inputs, /* tool_list_tokens = */ false);
|
||||
}
|
||||
|
||||
// Older gemma4 templates need their tool responses rewritten before rendering
|
||||
static common_chat_params common_chat_params_init_gemma4_legacy(const common_chat_template & tmpl, const autoparser::generation_params & inputs) {
|
||||
auto adjusted = inputs;
|
||||
workaround::convert_tool_responses_gemma4(adjusted.messages);
|
||||
return common_chat_params_init_gemma4(tmpl, adjusted);
|
||||
}
|
||||
|
||||
// Pick the dedicated handler for a template from its source, or null for the autoparser.
|
||||
// Order matters: the first match wins, and later checks assume the earlier ones did not match.
|
||||
static common_chat_params_init_fn common_chat_template_detect_params_init(const std::string & src) {
|
||||
@@ -1386,11 +1411,6 @@ static common_chat_params_init_fn common_chat_template_detect_params_init(const
|
||||
// Gemma4 format detection
|
||||
if (src.find("'<|tool_call>call:'") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: Gemma4\n");
|
||||
if (src.find("{#- OpenAI Chat Completions:") == std::string::npos) {
|
||||
LOG_WRN("%s: detected an outdated gemma4 chat template, applying compatibility workarounds. "
|
||||
"Consider updating to the official template.\n", __func__);
|
||||
return common_chat_params_init_gemma4_legacy;
|
||||
}
|
||||
return common_chat_params_init_gemma4;
|
||||
}
|
||||
|
||||
|
||||
@@ -500,3 +500,7 @@ common_chat_prompt_preset common_chat_get_asr_prompt(const common_chat_templates
|
||||
// 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 = "");
|
||||
|
||||
// system prompt line for the speech language and voice (e.g. LFM3-Audio), empty if not used
|
||||
std::string common_chat_get_speech_system_prompt(const common_chat_templates * chat_templates,
|
||||
const std::string & language, const std::string & voice);
|
||||
|
||||
+58
-42
@@ -895,27 +895,7 @@ bool fs_validate_filename(const std::string & filename, bool allow_subdirs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
std::wstring utf8_to_wstring(const std::string & str) {
|
||||
if (str.empty()) {
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0);
|
||||
|
||||
if (size <= 0) {
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
std::wstring wstr(size, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstr[0], size);
|
||||
|
||||
return wstr;
|
||||
}
|
||||
|
||||
std::string wstring_to_utf8(const std::wstring & str) {
|
||||
if (str.empty()) {
|
||||
return std::string();
|
||||
@@ -983,20 +963,9 @@ void common_set_env(const std::string & name, const std::string & value) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path common_get_path_from_env(const std::string & name) {
|
||||
#if defined(_WIN32)
|
||||
const std::wstring wname = utf8_to_wstring(name);
|
||||
const wchar_t * wvalue = _wgetenv(wname.c_str());
|
||||
return wvalue ? std::filesystem::path(wvalue) : std::filesystem::path();
|
||||
#else
|
||||
const char * value = std::getenv(name.c_str());
|
||||
return value ? std::filesystem::path(value) : std::filesystem::path();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
static std::filesystem::path get_home_directory() {
|
||||
std::filesystem::path home = common_get_path_from_env("HOME");
|
||||
std::filesystem::path home = common_get_env("HOME");
|
||||
if (!home.empty()) {
|
||||
return home;
|
||||
}
|
||||
@@ -1009,19 +978,19 @@ static std::filesystem::path get_home_directory() {
|
||||
#endif
|
||||
|
||||
std::filesystem::path fs_get_cache_directory() {
|
||||
std::filesystem::path cache_directory = common_get_path_from_env("LLAMA_CACHE");
|
||||
std::filesystem::path cache_directory = common_get_env("LLAMA_CACHE");
|
||||
if (!cache_directory.empty()) {
|
||||
return cache_directory;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
cache_directory = common_get_path_from_env("LOCALAPPDATA");
|
||||
cache_directory = common_get_env("LOCALAPPDATA");
|
||||
if (cache_directory.empty()) {
|
||||
throw std::runtime_error("Failed to find %LOCALAPPDATA% directory");
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
cache_directory = get_home_directory() / "Library/Caches";
|
||||
#else
|
||||
cache_directory = common_get_path_from_env("XDG_CACHE_HOME");
|
||||
cache_directory = common_get_env("XDG_CACHE_HOME");
|
||||
if (cache_directory.empty()) {
|
||||
cache_directory = get_home_directory() / ".cache";
|
||||
}
|
||||
@@ -1032,12 +1001,12 @@ std::filesystem::path fs_get_cache_directory() {
|
||||
std::filesystem::path fs_get_config_directory() {
|
||||
std::filesystem::path config_directory;
|
||||
#if defined(_WIN32)
|
||||
config_directory = common_get_path_from_env("APPDATA");
|
||||
config_directory = common_get_env("APPDATA");
|
||||
if (config_directory.empty()) {
|
||||
throw std::runtime_error("Failed to find %APPDATA% directory");
|
||||
}
|
||||
#else
|
||||
config_directory = common_get_path_from_env("XDG_CONFIG_HOME");
|
||||
config_directory = common_get_env("XDG_CONFIG_HOME");
|
||||
if (config_directory.empty()) {
|
||||
config_directory = get_home_directory() / ".config";
|
||||
}
|
||||
@@ -1053,7 +1022,7 @@ std::filesystem::path fs_get_cache_file(const std::string & filename) {
|
||||
if (ec) {
|
||||
throw std::runtime_error("failed to create cache directory: " + cache_directory.string());
|
||||
}
|
||||
return cache_directory / std::filesystem::u8path(filename);
|
||||
return cache_directory / filename;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1289,13 +1258,13 @@ void common_fit_apply(common_params & params, llama_model_params & mparams, llam
|
||||
cparams_dft.n_rs_seq = 0;
|
||||
|
||||
const common_fit_extra_model extra = {
|
||||
/*.path_model =*/ params_dft.model.path.c_str(),
|
||||
/*.paths_model =*/ params_dft.model.get_paths(),
|
||||
/*.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,
|
||||
common_fit_params(params.model.get_paths(), &mparams, &cparams,
|
||||
params.tensor_split,
|
||||
params.tensor_buft_overrides.data(),
|
||||
params.fit_params_target.data(),
|
||||
@@ -1311,7 +1280,7 @@ common_init_result::common_init_result(common_params & params, bool model_only)
|
||||
|
||||
common_fit_apply(params, mparams, cparams);
|
||||
|
||||
llama_model * model = llama_model_load_from_file(params.model.path.c_str(), mparams);
|
||||
llama_model * model = common_model_load(params.model.get_paths(), mparams);
|
||||
if (model == NULL) {
|
||||
return;
|
||||
}
|
||||
@@ -1344,6 +1313,12 @@ common_init_result::common_init_result(common_params & params, bool model_only)
|
||||
LOG_INF("%s", "decision model reads the embeddings output, enabling embedding mode\n");
|
||||
}
|
||||
|
||||
if (llama_model_classifier_free_guidance(model)) {
|
||||
params.kv_unified = true;
|
||||
cparams.kv_unified = true;
|
||||
cparams.n_seq_max *= 2;
|
||||
}
|
||||
|
||||
// embeddings need the whole batch in one ubatch, so n_batch must not be larger than n_ubatch
|
||||
// (server.cpp does this check for --embedding, but before the model is loaded)
|
||||
if (cparams.embeddings && cparams.n_batch > cparams.n_ubatch) {
|
||||
@@ -1747,6 +1722,19 @@ struct llama_model_params common_model_params_to_llama(common_params & params) {
|
||||
return mparams;
|
||||
}
|
||||
|
||||
struct llama_model * common_model_load(const std::vector<std::string> & paths, const struct llama_model_params & mparams) {
|
||||
if (paths.size() == 1) {
|
||||
return llama_model_load_from_file(paths[0].c_str(), mparams);
|
||||
}
|
||||
|
||||
std::vector<const char *> c_paths;
|
||||
c_paths.reserve(paths.size());
|
||||
for (const auto & path : paths) {
|
||||
c_paths.push_back(path.c_str());
|
||||
}
|
||||
return llama_model_load_from_splits(c_paths.data(), c_paths.size(), mparams);
|
||||
}
|
||||
|
||||
struct llama_context_params common_context_params_to_llama(const common_params & params) {
|
||||
auto cparams = llama_context_default_params();
|
||||
|
||||
@@ -1783,7 +1771,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
||||
cparams.type_k = params.cache_type_k;
|
||||
cparams.type_v = params.cache_type_v;
|
||||
|
||||
cparams.moe_cache_size = params.moe_cache_size;
|
||||
cparams.moe_cache_size = params.moe_cache_size;
|
||||
cparams.moe_cache_cpu_share = params.moe_cache_cpu_share;
|
||||
|
||||
return cparams;
|
||||
}
|
||||
@@ -2266,6 +2255,14 @@ bool common_batch::set_embd_state(int32_t idx, llama_embd state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool common_batch::set_embd_state_row(int32_t idx, int32_t row) {
|
||||
if (idx < 0 || idx >= size() || row < 0 || tokens[idx].state.data != nullptr || tokens[idx].state_row >= 0) {
|
||||
return false;
|
||||
}
|
||||
tokens[idx].state_row = row;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t common_batch::add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output) {
|
||||
token t = { LLAMA_TOKEN_NULL, { 0, 0, 0, 0 }, seq_id, output, embd, { nullptr, 0, 0 }, {} };
|
||||
for (int32_t j = 0; j < n_pos; ++j) {
|
||||
@@ -2275,6 +2272,16 @@ int32_t common_batch::add_embd(llama_embd embd, const llama_pos * pos, llama_seq
|
||||
return size() - 1;
|
||||
}
|
||||
|
||||
int32_t common_batch::add_embd_row(int32_t row, const llama_pos * pos, llama_seq_id seq_id, bool output) {
|
||||
token t = { LLAMA_TOKEN_NULL, { 0, 0, 0, 0 }, seq_id, output, { nullptr, 0, 0 }, { nullptr, 0, 0 }, {} };
|
||||
t.embd_row = row;
|
||||
for (int32_t j = 0; j < n_pos; ++j) {
|
||||
t.pos[j] = pos[j];
|
||||
}
|
||||
tokens.push_back(t);
|
||||
return size() - 1;
|
||||
}
|
||||
|
||||
llama_batch_ext * common_batch::get_sub_batch(int32_t off, int32_t n) {
|
||||
GGML_ASSERT(batch && "common_batch was not initialized with a context");
|
||||
GGML_ASSERT(off >= 0 && n >= 0 && off + n <= size());
|
||||
@@ -2295,6 +2302,12 @@ llama_batch_ext * common_batch::get_sub_batch(int32_t off, int32_t n) {
|
||||
if (t.embd.data && !llama_batch_ext_set_embd_token(res, idx, t.embd)) {
|
||||
GGML_ABORT("%s: failed to set the embedding of token %d at index %d\n", __func__, t.id, i);
|
||||
}
|
||||
} else if (t.embd_row >= 0) {
|
||||
idx = llama_batch_ext_add_embd_row(res, t.seq_id, t.embd_row);
|
||||
if (idx < 0) {
|
||||
GGML_ABORT("%s: failed to add embd row %d at index %d (error %d, n = %d)\n", __func__, t.embd_row, i, idx, n);
|
||||
}
|
||||
llama_batch_ext_set_pos(res, idx, t.pos.data());
|
||||
} else {
|
||||
idx = llama_batch_ext_add_embd(res, t.seq_id, t.embd);
|
||||
if (idx < 0) {
|
||||
@@ -2315,6 +2328,9 @@ llama_batch_ext * common_batch::get_sub_batch(int32_t off, int32_t n) {
|
||||
if (t.state.data) {
|
||||
llama_batch_ext_set_embd_state(res, idx, t.state); // contexts without a state input ignore it
|
||||
}
|
||||
if (t.state_row >= 0 && !llama_batch_ext_set_embd_state_row(res, idx, t.state_row)) {
|
||||
GGML_ABORT("%s: failed to set the state row of the entry at index %d\n", __func__, i);
|
||||
}
|
||||
if (t.decision_order != 0) {
|
||||
llama_batch_ext_set_decision_order(res, idx, (llama_decision_order) t.decision_order);
|
||||
}
|
||||
|
||||
+44
-8
@@ -311,6 +311,18 @@ struct common_params_model {
|
||||
std::string hf_file = ""; // HF file
|
||||
std::string docker_repo = ""; // Docker repo
|
||||
|
||||
std::vector<std::string> shards;
|
||||
|
||||
std::vector<std::string> get_paths() const {
|
||||
std::vector<std::string> paths = { path };
|
||||
for (const auto & shard : shards) {
|
||||
if (std::find(paths.begin(), paths.end(), shard) == paths.end()) {
|
||||
paths.push_back(shard);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::string get_name() const {
|
||||
if (!hf_repo.empty()) {
|
||||
return hf_repo;
|
||||
@@ -597,7 +609,8 @@ struct common_params {
|
||||
ggml_type cache_type_k = GGML_TYPE_F16; // KV cache data type for the K
|
||||
ggml_type cache_type_v = GGML_TYPE_F16; // KV cache data type for the V
|
||||
|
||||
size_t moe_cache_size = 0; // GPU cache size in bytes for the MoE experts kept in the CPU, split among the GPUs like the layers
|
||||
size_t moe_cache_size = 0; // GPU cache size in bytes for the MoE experts kept in the CPU, split among the GPUs like the layers
|
||||
float moe_cache_cpu_share = 0.0f; // share of the MoE cache misses computed on the CPU instead of uploaded
|
||||
|
||||
common_conversation_mode conversation_mode = COMMON_CONVERSATION_MODE_AUTO;
|
||||
|
||||
@@ -692,6 +705,7 @@ struct common_params {
|
||||
// enable built-in tools
|
||||
std::vector<std::string> server_tools;
|
||||
std::string server_tools_runtime;
|
||||
bool server_tools_allow_unauth = false; // allow tools/MCP on a non-loopback address without API key
|
||||
|
||||
// MCP server configs (Cursor-compatible JSON)
|
||||
std::string mcp_servers_config; // path to JSON file with MCP server definitions
|
||||
@@ -709,6 +723,7 @@ struct common_params {
|
||||
|
||||
std::string slot_save_path;
|
||||
std::string media_path; // path to directory for loading media files
|
||||
bool media_allow_private_urls = false; // allow http(s) media URLs on local or private addresses
|
||||
|
||||
float slot_prompt_similarity = 0.1f;
|
||||
|
||||
@@ -906,8 +921,7 @@ bool glob_match(const std::string & pattern, const std::string & str);
|
||||
//
|
||||
|
||||
#ifdef _WIN32
|
||||
std::wstring utf8_to_wstring(const std::string & str);
|
||||
std::string wstring_to_utf8(const std::wstring & str);
|
||||
std::string wstring_to_utf8(const std::wstring & str);
|
||||
#endif
|
||||
|
||||
//
|
||||
@@ -919,9 +933,6 @@ std::string wstring_to_utf8(const std::wstring & str);
|
||||
std::string common_get_env(const std::string & name);
|
||||
void common_set_env(const std::string & name, const std::string & value);
|
||||
|
||||
// reads a path from the environment, an unset variable gives an empty path
|
||||
std::filesystem::path common_get_path_from_env(const std::string & name);
|
||||
|
||||
//
|
||||
// Filesystem utils
|
||||
//
|
||||
@@ -1022,6 +1033,8 @@ struct llama_context_params common_context_params_to_llama(const common_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);
|
||||
|
||||
struct llama_model * common_model_load(const std::vector<std::string> & paths, const struct llama_model_params & mparams);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -1096,6 +1109,8 @@ struct common_batch {
|
||||
llama_embd state; // non-owning view of the data passed to set_embd_state(), data == NULL if none
|
||||
std::vector<llama_seq_id> seq_ids_extra; // see add_seq()
|
||||
int32_t decision_order = 0; // see llama_batch_ext_set_decision_order()
|
||||
int32_t embd_row = -1; // see add_embd_row(), -1 if none
|
||||
int32_t state_row = -1; // see set_embd_state_row(), -1 if none
|
||||
};
|
||||
|
||||
std::vector<token> tokens; // mirror of the entries, tokens[i] describes batch index i
|
||||
@@ -1134,10 +1149,17 @@ struct common_batch {
|
||||
// attach a state embedding (e.g. the target hidden state for MTP) to the entry at idx, can only be set once per entry
|
||||
bool set_embd_state(int32_t idx, llama_embd state);
|
||||
|
||||
// the state of entry idx is row `row` of the h_nextn device buffer of the state source context, see llama_set_embd_state_src()
|
||||
bool set_embd_state_row(int32_t idx, int32_t row);
|
||||
|
||||
// add an embedding-only entry (no token id)
|
||||
// pos points to n_pos positions
|
||||
int32_t add_embd(llama_embd embd, const llama_pos * pos, llama_seq_id seq_id, bool output);
|
||||
|
||||
// add an entry whose embedding is row `row` of the layer input device buffer of the context's ctx_other
|
||||
// pos points to n_pos positions
|
||||
int32_t add_embd_row(int32_t row, const llama_pos * pos, llama_seq_id seq_id, bool output);
|
||||
|
||||
int32_t size() const { return (int32_t) tokens.size(); }
|
||||
};
|
||||
|
||||
@@ -1288,6 +1310,20 @@ enum ggml_opt_optimizer_type common_opt_get_optimizer(const char *);
|
||||
// prompt utils
|
||||
//
|
||||
|
||||
// leave default-inserted elements uninitialized; callers must fill them before use
|
||||
template <typename T>
|
||||
struct common_no_init_allocator : std::allocator<T> {
|
||||
template <typename U> struct rebind { using other = common_no_init_allocator<U>; };
|
||||
|
||||
common_no_init_allocator() = default;
|
||||
template <typename U> common_no_init_allocator(const common_no_init_allocator<U> &) {}
|
||||
|
||||
template <typename U> void construct(U * p) { ::new ((void *) p) U; }
|
||||
};
|
||||
|
||||
// sequence state data, filled by llama_state_seq_get_data_ext()
|
||||
using common_state_data = std::vector<uint8_t, common_no_init_allocator<uint8_t>>;
|
||||
|
||||
struct common_prompt_checkpoint {
|
||||
int64_t n_tokens;
|
||||
|
||||
@@ -1297,8 +1333,8 @@ struct common_prompt_checkpoint {
|
||||
llama_pos pos_min;
|
||||
llama_pos pos_max;
|
||||
|
||||
std::vector<uint8_t> data_tgt;
|
||||
std::vector<uint8_t> data_dft;
|
||||
common_state_data data_tgt;
|
||||
common_state_data data_dft;
|
||||
|
||||
// (optional) speculative-decoding implementation state stashed with the checkpoint
|
||||
// (e.g. eagle3's deferred-boundary g_embd row)
|
||||
|
||||
+52
-35
@@ -45,7 +45,7 @@ static const std::string PARTIAL_SUFFIX = ".downloadInProgress";
|
||||
|
||||
static void write_etag(const std::string & path, const std::string & etag) {
|
||||
const std::string etag_path = path + ".etag";
|
||||
fs_write_atomic(std::filesystem::u8path(etag_path), etag);
|
||||
fs_write_atomic(etag_path, etag);
|
||||
LOG_DBG("%s: file etag saved: %s\n", __func__, etag_path.c_str());
|
||||
}
|
||||
|
||||
@@ -293,8 +293,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
|
||||
// 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)));
|
||||
return std::filesystem::exists(path) || (!alt_path.empty() && std::filesystem::exists(alt_path));
|
||||
};
|
||||
|
||||
if (skip_etag && cache_hit()) {
|
||||
@@ -462,38 +461,55 @@ static int common_download_file_single_online(const std::string & url,
|
||||
return finish(success ? head->status : -1);
|
||||
}
|
||||
|
||||
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url,
|
||||
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url_in,
|
||||
const common_remote_params & params) {
|
||||
auto [cli, parts] = common_http_client(url);
|
||||
static constexpr int max_redirects = 5;
|
||||
std::string url = url_in;
|
||||
for (int n_redirects = 0; ; n_redirects++) {
|
||||
auto [cli, parts] = common_http_client(url);
|
||||
|
||||
httplib::Headers headers;
|
||||
for (const auto & h : params.headers) {
|
||||
headers.emplace(h.first, h.second);
|
||||
httplib::Headers headers;
|
||||
for (const auto & h : params.headers) {
|
||||
headers.emplace(h.first, h.second);
|
||||
}
|
||||
if (headers.find("User-Agent") == headers.end()) {
|
||||
headers.emplace("User-Agent", "llama-cpp/" + std::string(llama_build_info()));
|
||||
}
|
||||
|
||||
if (params.timeout > 0) {
|
||||
cli.set_read_timeout(params.timeout, 0);
|
||||
cli.set_write_timeout(params.timeout, 0);
|
||||
}
|
||||
if (params.resolve_host) {
|
||||
// pin checked address, follow redirects manually to check each hop
|
||||
cli.set_direct_hostname_addr_map({{parts.host, params.resolve_host(parts.host)}});
|
||||
cli.set_follow_location(false);
|
||||
}
|
||||
|
||||
std::vector<char> buf;
|
||||
auto res = cli.Get(parts.path, headers,
|
||||
[&](const char *data, size_t len) {
|
||||
buf.insert(buf.end(), data, data + len);
|
||||
return params.max_size == 0 ||
|
||||
buf.size() <= static_cast<size_t>(params.max_size);
|
||||
},
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (!res) {
|
||||
throw std::runtime_error("error: cannot make GET request");
|
||||
}
|
||||
|
||||
if (params.resolve_host && res->status >= 300 && res->status < 400 && res->has_header("Location")) {
|
||||
if (n_redirects >= max_redirects) {
|
||||
throw std::runtime_error("error: too many redirects");
|
||||
}
|
||||
url = common_http_resolve_location(url, res->get_header_value("Location"));
|
||||
continue;
|
||||
}
|
||||
|
||||
return { res->status, std::move(buf) };
|
||||
}
|
||||
if (headers.find("User-Agent") == headers.end()) {
|
||||
headers.emplace("User-Agent", "llama-cpp/" + std::string(llama_build_info()));
|
||||
}
|
||||
|
||||
if (params.timeout > 0) {
|
||||
cli.set_read_timeout(params.timeout, 0);
|
||||
cli.set_write_timeout(params.timeout, 0);
|
||||
}
|
||||
|
||||
std::vector<char> buf;
|
||||
auto res = cli.Get(parts.path, headers,
|
||||
[&](const char *data, size_t len) {
|
||||
buf.insert(buf.end(), data, data + len);
|
||||
return params.max_size == 0 ||
|
||||
buf.size() <= static_cast<size_t>(params.max_size);
|
||||
},
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (!res) {
|
||||
throw std::runtime_error("error: cannot make GET request");
|
||||
}
|
||||
|
||||
return { res->status, std::move(buf) };
|
||||
}
|
||||
|
||||
int common_download_file_single(const std::string & url,
|
||||
@@ -511,7 +527,7 @@ int common_download_file_single(const std::string & url,
|
||||
return common_download_file_single_online(url, path, online_opts, skip_etag, lock_path, alt_path);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(std::filesystem::u8path(path))) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
LOG_ERR("%s: required file is not available in cache (offline mode): %s\n", __func__, path.c_str());
|
||||
return -1;
|
||||
}
|
||||
@@ -748,7 +764,8 @@ static hf_cache::hf_file find_best_model(const hf_cache::hf_files & files,
|
||||
}
|
||||
|
||||
for (const auto & t : tags) {
|
||||
std::regex pattern(t + "[.-]", std::regex::icase);
|
||||
// match the tag as a full token, so ":F16" does not match a BF16 file
|
||||
std::regex pattern("(^|[-./])" + t + "[.-]", std::regex::icase);
|
||||
for (const auto & f : files) {
|
||||
if (gguf_filename_is_model(f.path) &&
|
||||
std::regex_search(f.path, pattern)) {
|
||||
@@ -1211,7 +1228,7 @@ bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const
|
||||
for (const auto & s : sidecars) {
|
||||
const auto & f = plan.*s.file;
|
||||
if (!f.local_path.empty()) {
|
||||
to_remove.emplace_back(std::filesystem::u8path(f.local_path));
|
||||
to_remove.emplace_back(f.local_path);
|
||||
}
|
||||
}
|
||||
if (to_remove.empty()) {
|
||||
|
||||
@@ -31,6 +31,8 @@ struct common_remote_params {
|
||||
common_header_list headers;
|
||||
long timeout = 0; // in seconds, 0 means no timeout
|
||||
long max_size = 0; // unlimited if 0
|
||||
// if set: called per hop (redirects too), returns the address to connect to, throws to refuse
|
||||
std::function<std::string(const std::string & host)> resolve_host;
|
||||
};
|
||||
|
||||
// get remote file content, returns <http_code, raw_response_body>
|
||||
|
||||
+41
-20
@@ -56,11 +56,12 @@ private:
|
||||
};
|
||||
|
||||
static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
std::vector<ggml_backend_dev_t> & devs,
|
||||
uint32_t & hp_ngl,
|
||||
uint32_t & hp_ngl_skip,
|
||||
uint32_t & hp_n_ctx_train,
|
||||
uint32_t & hp_n_expert,
|
||||
ggml_log_level log_level) {
|
||||
@@ -70,7 +71,7 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
mparams_copy.no_alloc = true;
|
||||
mparams_copy.load_mode = LLAMA_LOAD_MODE_NONE;
|
||||
|
||||
llama_model * model = llama_model_load_from_file(path_model, mparams_copy);
|
||||
llama_model * model = common_model_load(paths_model, mparams_copy);
|
||||
if (model == nullptr) {
|
||||
throw std::runtime_error("failed to load model");
|
||||
}
|
||||
@@ -152,6 +153,8 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
if (mparams->load_mtp) {
|
||||
hp_ngl += llama_model_n_layer_nextn(model);
|
||||
}
|
||||
// without MTP the NextN layers hold no weights but the model loader still counts them for n_gpu_layers
|
||||
hp_ngl_skip = mparams->load_mtp ? 0 : llama_model_n_layer_nextn(model);
|
||||
hp_n_ctx_train = llama_model_n_ctx_train(model);
|
||||
hp_n_expert = llama_model_n_expert(model);
|
||||
|
||||
@@ -164,7 +167,7 @@ static std::vector<llama_device_memory_data> common_get_device_memory_data_impl(
|
||||
}
|
||||
|
||||
std::vector<common_memory_breakdown_row> common_fit_measure(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
uint32_t * n_ctx_train,
|
||||
@@ -176,7 +179,7 @@ std::vector<common_memory_breakdown_row> common_fit_measure(
|
||||
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));
|
||||
llama_model_ptr model(common_model_load(paths_model, mparams_copy));
|
||||
if (!model) {
|
||||
throw std::runtime_error("failed to load model");
|
||||
}
|
||||
@@ -198,7 +201,7 @@ std::vector<common_memory_breakdown_row> common_fit_measure(
|
||||
|
||||
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();
|
||||
const std::vector<std::string> paths_model = params.model.get_paths();
|
||||
|
||||
common_memory_reports res;
|
||||
|
||||
@@ -210,7 +213,7 @@ common_memory_reports common_measure_memory(common_params & params) {
|
||||
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);
|
||||
res.info_max = common_fit_measure(paths_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)
|
||||
@@ -228,23 +231,24 @@ common_memory_reports common_measure_memory(common_params & params) {
|
||||
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);
|
||||
res.info_min = common_fit_measure(paths_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 std::vector<std::string> & paths_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
std::vector<ggml_backend_dev_t> & devs,
|
||||
uint32_t & hp_ngl,
|
||||
uint32_t & hp_ngl_skip,
|
||||
uint32_t & hp_n_ctx_train,
|
||||
uint32_t & hp_n_expert,
|
||||
ggml_log_level log_level) {
|
||||
std::vector<llama_device_memory_data> impl = common_get_device_memory_data_impl(
|
||||
path_model, mparams, cparams, devs, hp_ngl, hp_n_ctx_train, hp_n_expert, log_level);
|
||||
paths_model, mparams, cparams, devs, hp_ngl, hp_ngl_skip, hp_n_ctx_train, hp_n_expert, log_level);
|
||||
|
||||
common_device_memory_data_vec ret(impl.size());
|
||||
for (size_t i = 0; i < impl.size(); i++) {
|
||||
@@ -258,7 +262,7 @@ common_device_memory_data_vec common_get_device_memory_data(
|
||||
}
|
||||
|
||||
static void common_params_fit_impl(
|
||||
const char * path_model, struct llama_model_params * mparams, struct llama_context_params * cparams,
|
||||
const std::vector<std::string> & paths_model, struct llama_model_params * mparams, struct llama_context_params * cparams,
|
||||
float * tensor_split, struct llama_model_tensor_buft_override * tensor_buft_overrides,
|
||||
size_t * margins_s, uint32_t n_ctx_min, const common_fit_extra_model * extra, enum ggml_log_level log_level) {
|
||||
if (mparams->split_mode == LLAMA_SPLIT_MODE_TENSOR) {
|
||||
@@ -270,6 +274,7 @@ static void common_params_fit_impl(
|
||||
|
||||
std::vector<ggml_backend_dev_t> devs;
|
||||
uint32_t hp_ngl = 0; // hparams.n_gpu_layers
|
||||
uint32_t hp_nsk = 0; // number of skipped NextN layers, counted by the model loader but not by hp_ngl
|
||||
uint32_t hp_nct = 0; // hparams.n_ctx_train
|
||||
uint32_t hp_nex = 0; // hparams.n_expert
|
||||
|
||||
@@ -291,6 +296,7 @@ static void common_params_fit_impl(
|
||||
if (dmds_extra.empty() || n_ctx_extra != cparams->n_ctx) {
|
||||
std::vector<ggml_backend_dev_t> devs_extra;
|
||||
uint32_t ngl_extra = 0;
|
||||
uint32_t nsk_extra = 0;
|
||||
uint32_t nct_extra = 0;
|
||||
uint32_t nex_extra = 0;
|
||||
|
||||
@@ -302,7 +308,7 @@ static void common_params_fit_impl(
|
||||
dmds_t measured;
|
||||
try {
|
||||
measured = common_get_device_memory_data_impl(
|
||||
extra->path_model, extra->mparams, extra->cparams, devs_extra, ngl_extra, nct_extra, nex_extra, log_level);
|
||||
extra->paths_model, extra->mparams, extra->cparams, devs_extra, ngl_extra, nsk_extra, nct_extra, nex_extra, log_level);
|
||||
} catch (const std::runtime_error & e) {
|
||||
// the extra model is optional, fit the main model alone rather than giving up
|
||||
LOG_WRN("%s: failed to measure the memory of the extra model, fitting without it: %s\n", __func__, e.what());
|
||||
@@ -342,7 +348,7 @@ static void common_params_fit_impl(
|
||||
// step 1: get data for default parameters and check whether any changes are necessary in the first place
|
||||
|
||||
LOG_TRC("%s: getting device memory data for initial parameters:\n", __func__);
|
||||
dmds_t dmds_full = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
|
||||
dmds_t dmds_full = common_get_device_memory_data_impl(paths_model, mparams, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, log_level);
|
||||
|
||||
// saturate instead of overflowing, this also preserves the UINT32_MAX sentinel of n_ctx_min:
|
||||
const uint32_t n_ctx_max = (uint32_t) std::min<uint64_t>(uint64_t(hp_nct) * n_streams, UINT32_MAX);
|
||||
@@ -354,7 +360,7 @@ static void common_params_fit_impl(
|
||||
if (n_streams > 1) {
|
||||
LOG_TRC("%s: context size unset and KV cache not unified -> using %" PRIu32 " for %" PRIu32 " sequences:\n",
|
||||
__func__, n_ctx_max, n_streams);
|
||||
dmds_full = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
|
||||
dmds_full = common_get_device_memory_data_impl(paths_model, mparams, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, log_level);
|
||||
}
|
||||
}
|
||||
add_extra_memory(dmds_full);
|
||||
@@ -493,7 +499,7 @@ static void common_params_fit_impl(
|
||||
|
||||
int64_t sum_projected_used_min_ctx = 0;
|
||||
cparams->n_ctx = n_ctx_min_total;
|
||||
dmds_t dmds_min_ctx = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
|
||||
dmds_t dmds_min_ctx = common_get_device_memory_data_impl(paths_model, mparams, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, log_level);
|
||||
add_extra_memory(dmds_min_ctx);
|
||||
if (nd == 0) {
|
||||
sum_projected_used_min_ctx = dmds_min_ctx.back().mb.total();
|
||||
@@ -640,6 +646,20 @@ static void common_params_fit_impl(
|
||||
assert(uint32_t(mparams.n_gpu_layers) <= hp_ngl + 1);
|
||||
uint32_t il0 = hp_ngl + 1 - mparams.n_gpu_layers; // start index for tensor buft overrides
|
||||
|
||||
// the skipped NextN layers come right before the output layer, put them on the same device as the output layer
|
||||
// so that the layers assigned by the model loader match the layers above
|
||||
if (hp_nsk > 0 && mparams.n_gpu_layers > 0) {
|
||||
mparams.n_gpu_layers += hp_nsk;
|
||||
if (nd > 1) {
|
||||
// the last devices have no layers if not even the output layer fits
|
||||
size_t id_last = nd - 1;
|
||||
while (ngl_per_device[id_last].n_layer == 0) {
|
||||
id_last--;
|
||||
}
|
||||
tensor_split[id_last] += hp_nsk;
|
||||
}
|
||||
}
|
||||
|
||||
mparams.tensor_split = tensor_split;
|
||||
|
||||
size_t itbo = 0;
|
||||
@@ -675,7 +695,7 @@ static void common_params_fit_impl(
|
||||
set_ngl_tensor_split_tbo(ngl_per_device, overflow_bufts, mparams_copy);
|
||||
|
||||
dmds_t dmd_nl = common_get_device_memory_data_impl(
|
||||
path_model, &mparams_copy, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
|
||||
paths_model, &mparams_copy, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, log_level);
|
||||
add_extra_memory(dmd_nl);
|
||||
|
||||
LOG_TRC("%s: memory for test allocation by device:\n", func_name);
|
||||
@@ -704,7 +724,7 @@ static void common_params_fit_impl(
|
||||
|
||||
LOG_TRC("%s: getting device memory data with all MoE tensors moved to system memory:\n", __func__);
|
||||
dmds_t dmds_cpu_moe = common_get_device_memory_data_impl(
|
||||
path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, log_level);
|
||||
paths_model, mparams, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, log_level);
|
||||
add_extra_memory(dmds_cpu_moe);
|
||||
|
||||
for (size_t id = 0; id < nd; id++) {
|
||||
@@ -958,7 +978,7 @@ static void common_params_fit_impl(
|
||||
}
|
||||
|
||||
enum common_params_fit_status common_fit_params(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
llama_model_params * mparams,
|
||||
llama_context_params * cparams,
|
||||
float * tensor_split,
|
||||
@@ -970,7 +990,7 @@ enum common_params_fit_status common_fit_params(
|
||||
const int64_t t0_us = llama_time_us();
|
||||
common_params_fit_status status = COMMON_PARAMS_FIT_STATUS_SUCCESS;
|
||||
try {
|
||||
common_params_fit_impl(path_model, mparams, cparams, tensor_split, tensor_buft_overrides, margins, n_ctx_min, extra, log_level);
|
||||
common_params_fit_impl(paths_model, mparams, cparams, tensor_split, tensor_buft_overrides, margins, n_ctx_min, extra, log_level);
|
||||
LOG_TRC("%s: successfully fit params to free device memory\n", __func__);
|
||||
} catch (const common_params_fit_exception & e) {
|
||||
LOG_WRN("%s: failed to fit params to free device memory: %s\n", __func__, e.what());
|
||||
@@ -1180,15 +1200,16 @@ void common_memory_breakdown_print(const struct llama_context * ctx) {
|
||||
}
|
||||
|
||||
void common_fit_print(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
llama_model_params * mparams,
|
||||
llama_context_params * cparams) {
|
||||
std::vector<ggml_backend_dev_t> devs;
|
||||
uint32_t hp_ngl = 0; // hparams.n_gpu_layers
|
||||
uint32_t hp_nsk = 0; // number of skipped NextN layers, counted by the model loader but not by hp_ngl
|
||||
uint32_t hp_nct = 0; // hparams.n_ctx_train
|
||||
uint32_t hp_nex = 0; // hparams.n_expert
|
||||
|
||||
auto dmd = common_get_device_memory_data_impl(path_model, mparams, cparams, devs, hp_ngl, hp_nct, hp_nex, GGML_LOG_LEVEL_ERROR);
|
||||
auto dmd = common_get_device_memory_data_impl(paths_model, mparams, cparams, devs, hp_ngl, hp_nsk, hp_nct, hp_nex, GGML_LOG_LEVEL_ERROR);
|
||||
GGML_ASSERT(dmd.size() == devs.size() + 1);
|
||||
|
||||
for (size_t id = 0; id < devs.size(); id++) {
|
||||
|
||||
+7
-6
@@ -16,7 +16,7 @@ enum common_params_fit_status {
|
||||
// - its context follows the context of the main model, so its memory is measured again whenever that context changes
|
||||
// - shares_model tells the fit that the weights are already counted in the main model, as for an MTP context
|
||||
struct common_fit_extra_model {
|
||||
const char * path_model;
|
||||
std::vector<std::string> paths_model;
|
||||
llama_model_params * mparams;
|
||||
llama_context_params * cparams;
|
||||
bool shares_model;
|
||||
@@ -28,19 +28,19 @@ struct common_fit_extra_model {
|
||||
// - only parameters that have the same value as in llama_default_model_params are modified
|
||||
// with the exception of the context size which is modified if and only if equal to 0
|
||||
common_params_fit_status common_fit_params(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
llama_model_params * mparams,
|
||||
llama_context_params * cparams,
|
||||
float * tensor_split, // writable buffer for tensor split, needs at least llama_max_devices elements
|
||||
llama_model_tensor_buft_override * tensor_buft_overrides, // writable buffer for overrides, needs at least llama_max_tensor_buft_overrides elements
|
||||
size_t * margins, // margins of memory to leave per device in bytes
|
||||
uint32_t n_ctx_min, // minimum context size to set when trying to reduce memory use
|
||||
const common_fit_extra_model * extra, // model to fit alongside the main one, nullptr if there is none
|
||||
const common_fit_extra_model * extra, // model to fit alongside the main one, nullptr if there is none
|
||||
ggml_log_level log_level); // minimum log level to print during fitting, lower levels go to debug log
|
||||
|
||||
// print estimated memory to stdout
|
||||
void common_fit_print(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
llama_model_params * mparams,
|
||||
llama_context_params * cparams);
|
||||
|
||||
@@ -75,7 +75,7 @@ std::vector<common_memory_breakdown_row> common_memory_breakdown_rows(const llam
|
||||
// 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 std::vector<std::string> & paths_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
uint32_t * n_ctx_train,
|
||||
@@ -99,11 +99,12 @@ using common_device_memory_data_vec = std::vector<common_device_memory_data>;
|
||||
|
||||
// Load a model + context with no_alloc and return the per-device memory breakdown.
|
||||
common_device_memory_data_vec common_get_device_memory_data(
|
||||
const char * path_model,
|
||||
const std::vector<std::string> & paths_model,
|
||||
const llama_model_params * mparams,
|
||||
const llama_context_params * cparams,
|
||||
std::vector<ggml_backend_dev_t> & devs,
|
||||
uint32_t & hp_ngl,
|
||||
uint32_t & hp_ngl_skip,
|
||||
uint32_t & hp_n_ctx_train,
|
||||
uint32_t & hp_n_expert,
|
||||
ggml_log_level log_level);
|
||||
|
||||
+6
-7
@@ -45,7 +45,7 @@ static fs::path get_cache_directory() {
|
||||
{HOME_DIR, fs::path(".cache") / "huggingface" / "hub"}
|
||||
};
|
||||
for (const auto & entry : entries) {
|
||||
if (fs::path base = common_get_path_from_env(entry.var); !base.empty()) {
|
||||
if (fs::path base = common_get_env(entry.var); !base.empty()) {
|
||||
return entry.path.empty() ? base : base / entry.path;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ static std::string get_repo_commit(const std::string & repo_id,
|
||||
LOG_WRN("%s: skip invalid commit: %s\n", __func__, _commit.c_str());
|
||||
continue;
|
||||
}
|
||||
const fs::path candidate = fs::u8path(_name);
|
||||
const fs::path candidate = _name;
|
||||
|
||||
if (!is_valid_subpath(refs_path, candidate)) {
|
||||
LOG_WRN("%s: skip invalid branch: %s\n", __func__, _name.c_str());
|
||||
@@ -317,7 +317,7 @@ hf_files get_repo_files(const std::string & repo_id,
|
||||
file.repo_id = repo_id;
|
||||
file.path = item["path"].get<std::string>();
|
||||
|
||||
const fs::path subpath = fs::u8path(file.path);
|
||||
const fs::path subpath = file.path;
|
||||
|
||||
if (!is_valid_subpath(commit_path, subpath)) {
|
||||
LOG_WRN("%s: skip invalid path: %s\n", __func__, file.path.c_str());
|
||||
@@ -434,8 +434,7 @@ hf_files get_cached_files(const std::string & repo_id) {
|
||||
if (!path.empty()) {
|
||||
hf_file file;
|
||||
file.repo_id = _repo_id;
|
||||
const auto generic_path = path.generic_u8string();
|
||||
file.path = std::string(generic_path.begin(), generic_path.end());
|
||||
file.path = path.generic_string();
|
||||
file.local_path = entry.path().string();
|
||||
file.final_path = file.local_path;
|
||||
files.push_back(std::move(file));
|
||||
@@ -450,8 +449,8 @@ std::string finalize_file(const hf_file & file, const std::function<bool()> & ke
|
||||
static std::atomic<bool> symlinks_disabled{false};
|
||||
|
||||
std::error_code ec;
|
||||
fs::path local_path = fs::u8path(file.local_path);
|
||||
fs::path final_path = fs::u8path(file.final_path);
|
||||
fs::path local_path = file.local_path;
|
||||
fs::path final_path = file.final_path;
|
||||
|
||||
if (local_path == final_path || fs::exists(final_path, ec)) {
|
||||
return file.final_path;
|
||||
|
||||
@@ -99,6 +99,26 @@ static common_http_url common_http_parse_url(const std::string & url) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
// resolve a redirect Location against the URL it came from
|
||||
static std::string common_http_resolve_location(const std::string & base_url, const std::string & location) {
|
||||
if (location.find("://") != std::string::npos) {
|
||||
return location;
|
||||
}
|
||||
const common_http_url base = common_http_parse_url(base_url);
|
||||
if (location.compare(0, 2, "//") == 0) {
|
||||
return base.scheme + ":" + location;
|
||||
}
|
||||
const std::string origin = base.scheme + "://" + common_http_format_host(base.host) + ":" + std::to_string(base.port);
|
||||
const std::string path = base.path.substr(0, base.path.find('?'));
|
||||
if (!location.empty() && location[0] == '/') {
|
||||
return origin + location;
|
||||
}
|
||||
if (!location.empty() && location[0] == '?') {
|
||||
return origin + path + location;
|
||||
}
|
||||
return origin + path.substr(0, path.rfind('/') + 1) + location;
|
||||
}
|
||||
|
||||
// match a loopback destination: a remote proxy can never reach the client machine's own
|
||||
// loopback, so these hosts are always contacted directly, before NO_PROXY is consulted
|
||||
// (similar to Go's net/http)
|
||||
|
||||
@@ -37,6 +37,7 @@ struct token {
|
||||
call_operator, // ()
|
||||
additive_binary_operator, // + - ~
|
||||
multiplicative_binary_operator, // * / %
|
||||
power_operator, // **
|
||||
comparison_binary_operator, // < > <= >= == !=
|
||||
unary_operator, // ! - +
|
||||
comment, // {# ... #}
|
||||
@@ -71,6 +72,7 @@ static std::string type_to_string(token::type t) {
|
||||
case token::call_operator: return "call_operator";
|
||||
case token::additive_binary_operator: return "additive_binary_operator";
|
||||
case token::multiplicative_binary_operator: return "multiplicative_binary_operator";
|
||||
case token::power_operator: return "power_operator";
|
||||
case token::comparison_binary_operator: return "comparison_binary_operator";
|
||||
case token::unary_operator: return "unary_operator";
|
||||
case token::comment: return "comment";
|
||||
@@ -137,6 +139,7 @@ struct lexer {
|
||||
{"+", token::additive_binary_operator},
|
||||
{"-", token::additive_binary_operator},
|
||||
{"~", token::additive_binary_operator},
|
||||
{"**", token::power_operator},
|
||||
{"*", token::multiplicative_binary_operator},
|
||||
{"/", token::multiplicative_binary_operator},
|
||||
{"%", token::multiplicative_binary_operator},
|
||||
|
||||
+27
-12
@@ -178,7 +178,7 @@ private:
|
||||
// bool has_caller_args = false;
|
||||
if (is(token::open_paren)) {
|
||||
// Optional caller arguments, e.g. {% call(user) dump_users(...) %}
|
||||
caller_args = parse_args();
|
||||
caller_args = parse_args(false);
|
||||
// has_caller_args = true;
|
||||
}
|
||||
auto callee = parse_primary_expression();
|
||||
@@ -283,7 +283,7 @@ private:
|
||||
|
||||
statement_ptr parse_macro_statement(size_t start_pos) {
|
||||
auto name = parse_primary_expression();
|
||||
auto args = parse_args();
|
||||
auto args = parse_args(false);
|
||||
expect(token::close_statement, "Expected %}");
|
||||
statements body;
|
||||
// Keep going until we hit {% endmacro
|
||||
@@ -397,11 +397,13 @@ private:
|
||||
|
||||
statement_ptr parse_comparison_expression() {
|
||||
// NOTE: membership has same precedence as comparison
|
||||
// e.g., ('a' in 'apple' == 'b' in 'banana') evaluates as ('a' in ('apple' == ('b' in 'banana')))
|
||||
auto left = parse_additive_expression();
|
||||
// e.g., ('a' in 'apple' == 'b' in 'banana') evaluates as ('a' in 'apple' and 'apple' == 'b' and 'b' in 'banana')
|
||||
statements operands;
|
||||
std::vector<token> ops;
|
||||
operands.push_back(parse_additive_expression());
|
||||
size_t pos = current;
|
||||
while (true) {
|
||||
token op;
|
||||
size_t start_pos = current;
|
||||
if (is_identifier("not") && peek(1).t == token::identifier && peek(1).value == "in") {
|
||||
op = {token::identifier, "not in", tokens[current].pos};
|
||||
++current; // consume 'not'
|
||||
@@ -411,9 +413,17 @@ private:
|
||||
} else if (is(token::comparison_binary_operator)) {
|
||||
op = next();
|
||||
} else break;
|
||||
left = mk_stmt<binary_expression>(start_pos, op, std::move(left), parse_additive_expression());
|
||||
ops.push_back(std::move(op));
|
||||
operands.push_back(parse_additive_expression());
|
||||
}
|
||||
return left;
|
||||
if (ops.empty()) {
|
||||
return std::move(operands[0]);
|
||||
}
|
||||
if (ops.size() == 1) {
|
||||
return mk_stmt<binary_expression>(pos, ops[0], std::move(operands[0]), std::move(operands[1]));
|
||||
}
|
||||
// chained comparison, e.g. 1 <= x <= 100
|
||||
return mk_stmt<comparison_chain_expression>(pos, std::move(operands), std::move(ops));
|
||||
}
|
||||
|
||||
statement_ptr parse_additive_expression() {
|
||||
@@ -500,17 +510,22 @@ private:
|
||||
return parse_call_expression(std::move(member)); // foo.x()()
|
||||
}
|
||||
|
||||
statements parse_args() {
|
||||
statements parse_args(bool allow_spread = true) {
|
||||
// comma-separated arguments list
|
||||
expect(token::open_paren, "Expected (");
|
||||
statements args;
|
||||
while (!is(token::close_paren)) {
|
||||
statement_ptr arg;
|
||||
// unpacking: *expr
|
||||
if (peek().t == token::multiplicative_binary_operator && peek().value == "*") {
|
||||
// unpacking: *expr or **expr
|
||||
const auto & t = peek();
|
||||
if (t.t == token::power_operator || (t.t == token::multiplicative_binary_operator && t.value == "*")) {
|
||||
if (!allow_spread) {
|
||||
throw std::runtime_error("Unexpected token: " + t.value + " of type " + std::to_string(t.t));
|
||||
}
|
||||
size_t start_pos = current;
|
||||
++current; // consume *
|
||||
arg = mk_stmt<spread_expression>(start_pos, parse_expression());
|
||||
++current; // consume * or **
|
||||
bool is_kwargs = t.t == token::power_operator;
|
||||
arg = mk_stmt<spread_expression>(start_pos, parse_expression(), is_kwargs);
|
||||
} else {
|
||||
arg = parse_expression();
|
||||
if (is(token::equals)) {
|
||||
|
||||
+63
-73
@@ -117,20 +117,8 @@ value object_literal::execute_impl(context & ctx) const {
|
||||
return obj;
|
||||
}
|
||||
|
||||
value binary_expression::execute_impl(context & ctx) const {
|
||||
value left_val = left->execute(ctx);
|
||||
|
||||
// Logical operators
|
||||
if (op.value == "and") {
|
||||
JJ_DEBUG("Executing logical test: %s AND %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? right->execute(ctx) : std::move(left_val);
|
||||
} else if (op.value == "or") {
|
||||
JJ_DEBUG("Executing logical test: %s OR %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? std::move(left_val) : right->execute(ctx);
|
||||
}
|
||||
|
||||
static value exec_binary_op(context & ctx, const token & op, value left_val, value right_val) {
|
||||
// Equality operators
|
||||
value right_val = right->execute(ctx);
|
||||
JJ_DEBUG("Executing binary expression %s '%s' %s", left_val->type().c_str(), op.value.c_str(), right_val->type().c_str());
|
||||
if (op.value == "==") {
|
||||
return mk_val<value_bool>(*left_val == *right_val);
|
||||
@@ -307,58 +295,74 @@ value binary_expression::execute_impl(context & ctx) const {
|
||||
throw std::runtime_error("Unknown operator \"" + op.value + "\" between " + left_val->type() + " and " + right_val->type());
|
||||
}
|
||||
|
||||
static value try_builtin_func(context & ctx, const std::string & name, value & input, bool undef_on_missing = false) {
|
||||
JJ_DEBUG("Trying built-in function '%s' for type %s", name.c_str(), input->type().c_str());
|
||||
if (ctx.is_get_stats) {
|
||||
value_t::stats_t::mark_used(input);
|
||||
input->stats.ops.insert(name);
|
||||
value binary_expression::execute_impl(context & ctx) const {
|
||||
value left_val = left->execute(ctx);
|
||||
|
||||
// Logical operators
|
||||
if (op.value == "and") {
|
||||
JJ_DEBUG("Executing logical test: %s AND %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? right->execute(ctx) : std::move(left_val);
|
||||
} else if (op.value == "or") {
|
||||
JJ_DEBUG("Executing logical test: %s OR %s", left->type().c_str(), right->type().c_str());
|
||||
return left_val->as_bool() ? std::move(left_val) : right->execute(ctx);
|
||||
}
|
||||
auto builtins = input->get_builtins();
|
||||
auto it = builtins.find(name);
|
||||
if (it != builtins.end()) {
|
||||
JJ_DEBUG("Binding built-in '%s'", name.c_str());
|
||||
return mk_val<value_func>(name, it->second, input);
|
||||
|
||||
return exec_binary_op(ctx, op, std::move(left_val), right->execute(ctx));
|
||||
}
|
||||
|
||||
value comparison_chain_expression::execute_impl(context & ctx) const {
|
||||
value left_val = operands[0]->execute(ctx);
|
||||
value res;
|
||||
for (size_t i = 0; i < ops.size(); ++i) {
|
||||
value right_val = operands[i + 1]->execute(ctx);
|
||||
res = exec_binary_op(ctx, ops[i], left_val, right_val);
|
||||
if (!res->as_bool()) {
|
||||
break; // same as "and", the next operands are not executed
|
||||
}
|
||||
left_val = std::move(right_val);
|
||||
}
|
||||
if (undef_on_missing) {
|
||||
return mk_val<value_undefined>(name);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void exec_call_args(context & ctx, const statements & arg_stmts, func_args & args) {
|
||||
for (const auto & arg_stmt : arg_stmts) {
|
||||
if (!is_stmt<spread_expression>(arg_stmt)) {
|
||||
auto arg_val = arg_stmt->execute(ctx);
|
||||
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
||||
args.push_back(arg_val);
|
||||
continue;
|
||||
}
|
||||
auto spread = cast_stmt<spread_expression>(arg_stmt);
|
||||
value val = spread->argument->execute(ctx);
|
||||
JJ_DEBUG(" Unpacked argument type: %s", val->type().c_str());
|
||||
if (spread->is_kwargs) {
|
||||
if (!is_val<value_object>(val)) {
|
||||
throw std::runtime_error("Argument after ** must be an object, got " + val->type());
|
||||
}
|
||||
for (const auto & pair : val->as_ordered_object()) {
|
||||
if (!is_val<value_string>(pair.first)) {
|
||||
throw std::runtime_error("Keyword argument key must be a string, got " + pair.first->type());
|
||||
}
|
||||
args.push_back(mk_val<value_kwarg>(pair.first->as_string().str(), pair.second));
|
||||
}
|
||||
} else {
|
||||
if (!is_val<value_array>(val)) {
|
||||
throw std::runtime_error("Argument after * must be an array, got " + val->type());
|
||||
}
|
||||
for (const auto & item : val->as_array()) {
|
||||
args.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Unknown (built-in) filter '" + name + "' for type " + input->type());
|
||||
}
|
||||
|
||||
static value apply_filter(context & ctx, const statement_ptr & filter, value input) {
|
||||
JJ_DEBUG("Applying filter to %s", input->type().c_str());
|
||||
|
||||
auto set_filter_alias = [](auto & filter_id) {
|
||||
if (filter_id == "count") {
|
||||
filter_id = "length";
|
||||
} else if (filter_id == "d") {
|
||||
filter_id = "default";
|
||||
} else if (filter_id == "e") {
|
||||
filter_id = "escape";
|
||||
} else if (filter_id == "trim") {
|
||||
filter_id = "strip";
|
||||
}
|
||||
};
|
||||
|
||||
if (is_stmt<identifier>(filter)) {
|
||||
auto filter_id = cast_stmt<identifier>(filter)->val;
|
||||
|
||||
set_filter_alias(filter_id);
|
||||
JJ_DEBUG("Applying filter '%s' to %s", filter_id.c_str(), input->type().c_str());
|
||||
// TODO: Refactor filters so this coercion can be done automatically
|
||||
if (!input->is_undefined() && !is_val<value_string>(input) && (
|
||||
filter_id == "capitalize" ||
|
||||
filter_id == "lower" ||
|
||||
filter_id == "replace" ||
|
||||
filter_id == "strip" ||
|
||||
filter_id == "title" ||
|
||||
filter_id == "upper" ||
|
||||
filter_id == "wordcount"
|
||||
)) {
|
||||
JJ_DEBUG("Coercing %s to String for '%s' filter", input->type().c_str(), filter_id.c_str());
|
||||
input = mk_val<value_string>(input->as_string());
|
||||
}
|
||||
return try_builtin_func(ctx, filter_id, input)->invoke(func_args(ctx));
|
||||
return apply_filter_by_name(ctx, filter_id, input, func_args(ctx));
|
||||
|
||||
} else if (is_stmt<call_expression>(filter)) {
|
||||
auto call = cast_stmt<call_expression>(filter);
|
||||
@@ -367,14 +371,11 @@ static value apply_filter(context & ctx, const statement_ptr & filter, value inp
|
||||
}
|
||||
auto filter_id = cast_stmt<identifier>(call->callee)->val;
|
||||
|
||||
set_filter_alias(filter_id);
|
||||
JJ_DEBUG("Applying filter '%s' with arguments to %s", filter_id.c_str(), input->type().c_str());
|
||||
func_args args(ctx);
|
||||
for (const auto & arg_expr : call->args) {
|
||||
args.push_back(arg_expr->execute(ctx));
|
||||
}
|
||||
exec_call_args(ctx, call->args, args);
|
||||
|
||||
return try_builtin_func(ctx, filter_id, input)->invoke(args);
|
||||
return apply_filter_by_name(ctx, filter_id, input, args);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Invalid filter expression");
|
||||
@@ -415,9 +416,7 @@ value test_expression::execute_impl(context & ctx) const {
|
||||
test_id = cast_stmt<identifier>(call->callee)->val;
|
||||
|
||||
JJ_DEBUG("Applying test '%s' with arguments to %s", test_id.c_str(), input->type().c_str());
|
||||
for (const auto & arg_expr : call->args) {
|
||||
args.push_back(arg_expr->execute(ctx));
|
||||
}
|
||||
exec_call_args(ctx, call->args, args);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Invalid test expression");
|
||||
@@ -808,12 +807,7 @@ value call_statement::execute_impl(context & ctx) const {
|
||||
call_ctx.set_val("caller", mk_val<value_func>("caller", func));
|
||||
|
||||
func_args args(call_ctx);
|
||||
|
||||
for (const auto & arg_expr : call_expr->args) {
|
||||
auto arg_val = arg_expr->execute(ctx);
|
||||
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
||||
args.push_back(arg_val);
|
||||
}
|
||||
exec_call_args(ctx, call_expr->args, args);
|
||||
|
||||
JJ_DEBUG("Calling macro '%s' with %zu arguments", callee_func->name.c_str(), args.count());
|
||||
return callee_func->invoke(args);
|
||||
@@ -953,11 +947,7 @@ value member_expression::execute_impl(context & ctx) const {
|
||||
value call_expression::execute_impl(context & ctx) const {
|
||||
// gather arguments
|
||||
func_args args(ctx);
|
||||
for (auto & arg_stmt : this->args) {
|
||||
auto arg_val = arg_stmt->execute(ctx);
|
||||
JJ_DEBUG(" Argument type: %s", arg_val->type().c_str());
|
||||
args.push_back(arg_val);
|
||||
}
|
||||
exec_call_args(ctx, this->args, args);
|
||||
// execute callee
|
||||
value callee_val = callee->execute(ctx);
|
||||
if (!is_val<value_func>(callee_val)) {
|
||||
|
||||
+25
-1
@@ -471,6 +471,28 @@ struct binary_expression : public expression {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Two or more comparisons in a row, e.g. `a < b <= c`
|
||||
* It is the same as `a < b and b <= c`, but each operand is executed only one time.
|
||||
*/
|
||||
struct comparison_chain_expression : public expression {
|
||||
statements operands;
|
||||
std::vector<token> ops;
|
||||
|
||||
comparison_chain_expression(statements && operands, std::vector<token> && ops)
|
||||
: operands(std::move(operands)), ops(std::move(ops)) {
|
||||
assert(this->operands.size() == this->ops.size() + 1);
|
||||
for (const auto & operand : this->operands) chk_type<expression>(operand);
|
||||
}
|
||||
std::string type() const override { return "ComparisonChainExpression"; }
|
||||
value execute_impl(context & ctx) const override;
|
||||
void visit(context & ctx) const override {
|
||||
ctx.visitor(false, this, {
|
||||
{"operands", stmts_to_ptr(operands)}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An operation with two sides, separated by the | operator.
|
||||
* Operator precedence: https://github.com/pallets/jinja/issues/379#issuecomment-168076202
|
||||
@@ -633,7 +655,9 @@ struct keyword_argument_expression : public expression {
|
||||
|
||||
struct spread_expression : public expression {
|
||||
statement_ptr argument;
|
||||
explicit spread_expression(statement_ptr && argument) : argument(std::move(argument)) {
|
||||
bool is_kwargs;
|
||||
spread_expression(statement_ptr && argument, bool is_kwargs)
|
||||
: argument(std::move(argument)), is_kwargs(is_kwargs) {
|
||||
chk_type<expression>(this->argument);
|
||||
}
|
||||
std::string type() const override { return "SpreadExpression"; }
|
||||
|
||||
+114
-10
@@ -72,6 +72,17 @@ const std::vector<value> & func_args::get_args() const {
|
||||
return args;
|
||||
}
|
||||
|
||||
void func_args::ensure_kwargs(std::initializer_list<const char *> names) const {
|
||||
for (const auto & arg : args) {
|
||||
if (is_val<value_kwarg>(arg)) {
|
||||
auto * kwarg = cast_val<value_kwarg>(arg);
|
||||
if (std::find(names.begin(), names.end(), kwarg->key) == names.end()) {
|
||||
throw raised_exception("Function '" + func_name + "' got an unexpected keyword argument '" + kwarg->key + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that mimics Python's array slicing.
|
||||
*/
|
||||
@@ -134,6 +145,14 @@ static value empty_value_fn(const func_args &) {
|
||||
return mk_val<T>();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static value empty_value_no_args_fn(const func_args & args) {
|
||||
args.ensure_kwargs();
|
||||
args.ensure_count(1, 1);
|
||||
return empty_value_fn<T>(args);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static value test_type_fn(const func_args & args) {
|
||||
args.ensure_count(1);
|
||||
@@ -594,6 +613,52 @@ const func_builtins & global_builtins() {
|
||||
return builtins;
|
||||
}
|
||||
|
||||
value try_builtin_func(context & ctx, const std::string & name, value & input, bool undef_on_missing) {
|
||||
JJ_DEBUG("Trying built-in function '%s' for type %s", name.c_str(), input->type().c_str());
|
||||
if (ctx.is_get_stats) {
|
||||
value_t::stats_t::mark_used(input);
|
||||
input->stats.ops.insert(name);
|
||||
}
|
||||
auto builtins = input->get_builtins();
|
||||
auto it = builtins.find(name);
|
||||
if (it != builtins.end()) {
|
||||
JJ_DEBUG("Binding built-in '%s'", name.c_str());
|
||||
return mk_val<value_func>(name, it->second, input);
|
||||
}
|
||||
if (undef_on_missing) {
|
||||
return mk_val<value_undefined>(name);
|
||||
}
|
||||
throw std::runtime_error("Unknown (built-in) filter '" + name + "' for type " + input->type());
|
||||
}
|
||||
|
||||
value apply_filter_by_name(context & ctx, std::string filter_id, value input, const func_args & args) {
|
||||
if (filter_id == "count") {
|
||||
filter_id = "length";
|
||||
} else if (filter_id == "d") {
|
||||
filter_id = "default";
|
||||
} else if (filter_id == "e") {
|
||||
filter_id = "escape";
|
||||
} else if (filter_id == "trim") {
|
||||
filter_id = "strip";
|
||||
}
|
||||
|
||||
// TODO: Refactor filters so this coercion can be done automatically
|
||||
if (!input->is_undefined() && !is_val<value_string>(input) && (
|
||||
filter_id == "capitalize" ||
|
||||
filter_id == "lower" ||
|
||||
filter_id == "replace" ||
|
||||
filter_id == "strip" ||
|
||||
filter_id == "title" ||
|
||||
filter_id == "upper" ||
|
||||
filter_id == "wordcount"
|
||||
)) {
|
||||
JJ_DEBUG("Coercing %s to String for '%s' filter", input->type().c_str(), filter_id.c_str());
|
||||
input = mk_val<value_string>(input->as_string());
|
||||
}
|
||||
|
||||
return try_builtin_func(ctx, filter_id, input)->invoke(args);
|
||||
}
|
||||
|
||||
|
||||
const func_builtins & value_int_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
@@ -667,16 +732,22 @@ const func_builtins & value_string_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
{"upper", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs();
|
||||
args.ensure_count(1, 1);
|
||||
args.ensure_vals<value_string>();
|
||||
jinja::string str = args.get_pos(0)->as_string().uppercase();
|
||||
return mk_val<value_string>(str);
|
||||
}},
|
||||
{"lower", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs();
|
||||
args.ensure_count(1, 1);
|
||||
args.ensure_vals<value_string>();
|
||||
jinja::string str = args.get_pos(0)->as_string().lowercase();
|
||||
return mk_val<value_string>(str);
|
||||
}},
|
||||
{"strip", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs({"chars"});
|
||||
args.ensure_count(1, 2);
|
||||
value val_input = args.get_pos(0);
|
||||
if (!is_val<value_string>(val_input)) {
|
||||
throw raised_exception("strip() first argument must be a string");
|
||||
@@ -707,11 +778,15 @@ const func_builtins & value_string_t::get_builtins() const {
|
||||
}
|
||||
}},
|
||||
{"title", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs();
|
||||
args.ensure_count(1, 1);
|
||||
args.ensure_vals<value_string>();
|
||||
jinja::string str = args.get_pos(0)->as_string().titlecase();
|
||||
return mk_val<value_string>(str);
|
||||
}},
|
||||
{"capitalize", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs();
|
||||
args.ensure_count(1, 1);
|
||||
args.ensure_vals<value_string>();
|
||||
jinja::string str = args.get_pos(0)->as_string().capitalize();
|
||||
return mk_val<value_string>(str);
|
||||
@@ -1013,6 +1088,10 @@ const func_builtins & value_bool_t::get_builtins() const {
|
||||
return builtins;
|
||||
}
|
||||
|
||||
[[noreturn]] static value array_sum_not_implemented(const func_args &) {
|
||||
throw not_implemented_exception("Array sum builtin not implemented");
|
||||
}
|
||||
|
||||
const func_builtins & value_array_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
@@ -1079,7 +1158,9 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
{"select", select_or_reject<false, false>},
|
||||
{"rejectattr", select_or_reject<true, true>},
|
||||
{"reject", select_or_reject<true, false>},
|
||||
{"sum", array_sum_not_implemented},
|
||||
{"join", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs({"d", "attribute"});
|
||||
args.ensure_count(1, 3);
|
||||
if (!is_val<value_array>(args.get_pos(0))) {
|
||||
throw raised_exception("join() first argument must be an array");
|
||||
@@ -1117,13 +1198,28 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
{"tojson", tojson},
|
||||
{"map", [](const func_args & args) -> value {
|
||||
args.ensure_count(2);
|
||||
if (!is_val<value_array>(args.get_pos(0))) {
|
||||
value val = args.get_pos(0);
|
||||
if (!is_val<value_array>(val)) {
|
||||
throw raised_exception("map: first argument must be an array");
|
||||
}
|
||||
if (!is_val<value_kwarg>(args.get_args().at(1))) {
|
||||
throw not_implemented_exception("map: filter-mapping not implemented");
|
||||
value filter = args.get_pos(1);
|
||||
if (!is_val<value_string>(filter)) {
|
||||
throw raised_exception("map: filter name must be a string");
|
||||
}
|
||||
auto out = mk_val<value_array>();
|
||||
func_args filter_args(args.ctx);
|
||||
const auto & all_args = args.get_args();
|
||||
for (size_t i = 2; i < all_args.size(); ++i) {
|
||||
filter_args.push_back(all_args[i]);
|
||||
}
|
||||
const std::string filter_id = filter->as_string().str();
|
||||
auto arr = val->as_array();
|
||||
for (const auto & item : arr) {
|
||||
out->push_back(apply_filter_by_name(args.ctx, filter_id, item, filter_args));
|
||||
}
|
||||
return is_val<value_tuple>(val) ? mk_val<value_tuple>(out->as_array()) : out;
|
||||
}
|
||||
value val = args.get_pos(0);
|
||||
value attribute = args.get_kwarg_or_pos("attribute", 1);
|
||||
value default_val = args.get_kwarg("default", mk_val<value_undefined>());
|
||||
auto out = mk_val<value_array>();
|
||||
@@ -1132,7 +1228,7 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
value attr_val = get_attribute(item, attribute, default_val);
|
||||
out->push_back(attr_val);
|
||||
}
|
||||
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(out->as_array())) : out;
|
||||
return is_val<value_tuple>(val) ? mk_val<value_tuple>(out->as_array()) : out;
|
||||
}},
|
||||
{"append", [](const func_args & args) -> value {
|
||||
args.ensure_count(2);
|
||||
@@ -1405,14 +1501,18 @@ const func_builtins & value_none_t::get_builtins() const {
|
||||
const func_builtins & value_undefined_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
{"capitalize", empty_value_fn<value_string>},
|
||||
{"capitalize", empty_value_no_args_fn<value_string>},
|
||||
{"first", empty_value_fn<value_undefined>},
|
||||
{"items", empty_value_fn<value_array>},
|
||||
{"join", empty_value_fn<value_string>},
|
||||
{"join", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs({"d", "attribute"});
|
||||
args.ensure_count(1, 3);
|
||||
return empty_value_fn<value_string>(args);
|
||||
}},
|
||||
{"last", empty_value_fn<value_undefined>},
|
||||
{"length", empty_value_fn<value_int>},
|
||||
{"list", empty_value_fn<value_array>},
|
||||
{"lower", empty_value_fn<value_string>},
|
||||
{"lower", empty_value_no_args_fn<value_string>},
|
||||
{"map", empty_value_fn<value_array>},
|
||||
{"max", empty_value_fn<value_undefined>},
|
||||
{"min", empty_value_fn<value_undefined>},
|
||||
@@ -1425,12 +1525,16 @@ const func_builtins & value_undefined_t::get_builtins() const {
|
||||
{"selectattr", empty_value_fn<value_array>},
|
||||
{"sort", empty_value_fn<value_array>},
|
||||
{"string", empty_value_fn<value_string>},
|
||||
{"strip", empty_value_fn<value_string>},
|
||||
{"strip", [](const func_args & args) -> value {
|
||||
args.ensure_kwargs({"chars"});
|
||||
args.ensure_count(1, 2);
|
||||
return empty_value_fn<value_string>(args);
|
||||
}},
|
||||
{"sum", empty_value_fn<value_int>},
|
||||
{"title", empty_value_fn<value_string>},
|
||||
{"title", empty_value_no_args_fn<value_string>},
|
||||
{"truncate", empty_value_fn<value_string>},
|
||||
{"unique", empty_value_fn<value_array>},
|
||||
{"upper", empty_value_fn<value_string>},
|
||||
{"upper", empty_value_no_args_fn<value_string>},
|
||||
{"wordcount", empty_value_fn<value_int>},
|
||||
};
|
||||
return builtins;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -176,6 +177,8 @@ protected:
|
||||
//
|
||||
|
||||
const func_builtins & global_builtins();
|
||||
value try_builtin_func(context & ctx, const std::string & name, value & input, bool undef_on_missing = false);
|
||||
value apply_filter_by_name(context & ctx, std::string filter_id, value input, const func_args & args);
|
||||
|
||||
std::string value_to_json(const value & val, int indent = -1, const std::string_view item_sep = ", ", const std::string_view key_sep = ": ");
|
||||
|
||||
@@ -658,6 +661,7 @@ public:
|
||||
size_t count() const { return args.size(); }
|
||||
void push_back(const value & val);
|
||||
void push_front(const value & val);
|
||||
void ensure_kwargs(std::initializer_list<const char *> names = {}) const;
|
||||
void ensure_count(size_t min, size_t max = 999) const {
|
||||
size_t n = args.size();
|
||||
if (n < min || n > max) {
|
||||
|
||||
@@ -860,6 +860,7 @@ 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::vector<const common_chat_schema *> leaves;
|
||||
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");
|
||||
@@ -879,6 +880,10 @@ public:
|
||||
for (const auto & v : as<common_chat_schema_enum>(comp).values) {
|
||||
enum_values[_generate_constant_rule(v)] += 1;
|
||||
}
|
||||
} else if (is_required && comp.kind() != common_chat_schema::KIND_ANY) {
|
||||
// Direct non-structural children are used for merging. anyOf alternatives are not merged.
|
||||
// Empty schemas are skipped.
|
||||
leaves.push_back(&comp);
|
||||
}
|
||||
};
|
||||
for (const auto & child : schema.children) {
|
||||
@@ -901,6 +906,38 @@ public:
|
||||
return _add_rule(rule_name, "(" + string_join(enum_intersection, " | ") + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.empty() && !leaves.empty()) {
|
||||
if (leaves.size() == 1) {
|
||||
return visit(*leaves[0], name);
|
||||
}
|
||||
bool all_numeric = true;
|
||||
bool any_integer = false;
|
||||
int64_t lo = std::numeric_limits<int64_t>::min();
|
||||
int64_t hi = std::numeric_limits<int64_t>::max();
|
||||
for (const auto * leaf : leaves) {
|
||||
if (leaf->kind() == common_chat_schema::KIND_INTEGER) {
|
||||
const auto & i = as<common_chat_schema_integer>(*leaf);
|
||||
any_integer = true;
|
||||
lo = std::max(lo, i.minimum);
|
||||
hi = std::min(hi, i.maximum);
|
||||
} else if (leaf->kind() != common_chat_schema::KIND_NUMBER) {
|
||||
all_numeric = false;
|
||||
}
|
||||
}
|
||||
if (all_numeric) {
|
||||
if (lo > hi) {
|
||||
throw std::invalid_argument("allOf has an empty numeric range");
|
||||
}
|
||||
if (any_integer) {
|
||||
common_chat_schema_integer merged;
|
||||
merged.minimum = lo;
|
||||
merged.maximum = hi;
|
||||
return visit(merged, name);
|
||||
}
|
||||
return visit(*leaves[0], name);
|
||||
}
|
||||
}
|
||||
return _add_rule(rule_name, _build_object_rule(properties, required, name, nullptr));
|
||||
}
|
||||
|
||||
|
||||
+48
-14
@@ -85,92 +85,124 @@ struct common_chat_schema {
|
||||
|
||||
static const char * kind_name(node_kind kind);
|
||||
static const char * type_name(value_type type);
|
||||
|
||||
template <typename T>
|
||||
const T & as() const {
|
||||
GGML_ASSERT(kind() == T::KIND);
|
||||
return static_cast<const T &>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
using common_chat_schema_ptr = std::unique_ptr<common_chat_schema>;
|
||||
|
||||
struct common_chat_schema_any : common_chat_schema {
|
||||
node_kind kind() const override { return KIND_ANY; }
|
||||
static constexpr node_kind KIND = KIND_ANY;
|
||||
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
// {"$ref": "#/..."}, only references into the same document are supported
|
||||
struct common_chat_schema_ref : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_REF;
|
||||
|
||||
std::string ref;
|
||||
const common_chat_schema * target = nullptr; // owned by common_chat_schema_document::refs
|
||||
|
||||
explicit common_chat_schema_ref(std::string ref) : ref(std::move(ref)) {}
|
||||
|
||||
node_kind kind() const override { return KIND_REF; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
// oneOf / anyOf, or a "type" array expanded to one alternative per type
|
||||
struct common_chat_schema_any_of : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_ANY_OF;
|
||||
|
||||
std::vector<common_chat_schema_ptr> children;
|
||||
|
||||
node_kind kind() const override { return KIND_ANY_OF; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_all_of : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_ALL_OF;
|
||||
|
||||
std::vector<common_chat_schema_ptr> children;
|
||||
|
||||
node_kind kind() const override { return KIND_ALL_OF; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_const : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_CONST;
|
||||
|
||||
common_json value;
|
||||
|
||||
explicit common_chat_schema_const(common_json value) : value(std::move(value)) {}
|
||||
|
||||
node_kind kind() const override { return KIND_CONST; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_enum : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_ENUM;
|
||||
|
||||
std::vector<common_json> values;
|
||||
|
||||
node_kind kind() const override { return KIND_ENUM; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_null : common_chat_schema {
|
||||
node_kind kind() const override { return KIND_NULL; }
|
||||
static constexpr node_kind KIND = KIND_NULL;
|
||||
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_boolean : common_chat_schema {
|
||||
node_kind kind() const override { return KIND_BOOLEAN; }
|
||||
static constexpr node_kind KIND = KIND_BOOLEAN;
|
||||
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_number : common_chat_schema {
|
||||
node_kind kind() const override { return KIND_NUMBER; }
|
||||
static constexpr node_kind KIND = KIND_NUMBER;
|
||||
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
// bounds are inclusive, exclusiveMinimum / exclusiveMaximum are folded in
|
||||
struct common_chat_schema_integer : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_INTEGER;
|
||||
|
||||
int64_t minimum = INT64_MIN; // INT64_MIN for unbounded
|
||||
int64_t maximum = INT64_MAX; // INT64_MAX for unbounded
|
||||
|
||||
node_kind kind() const override { return KIND_INTEGER; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_string : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_STRING;
|
||||
|
||||
std::string pattern; // empty when absent
|
||||
string_format format = FORMAT_NONE;
|
||||
int min_length = 0;
|
||||
int max_length = -1; // -1 for unbounded
|
||||
|
||||
node_kind kind() const override { return KIND_STRING; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_array : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_ARRAY;
|
||||
|
||||
common_chat_schema_ptr items; // a common_chat_schema_any when "items" is absent
|
||||
int min_items = 0;
|
||||
int max_items = -1; // -1 for unbounded
|
||||
|
||||
node_kind kind() const override { return KIND_ARRAY; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_tuple : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_TUPLE;
|
||||
|
||||
std::vector<common_chat_schema_ptr> items;
|
||||
|
||||
node_kind kind() const override { return KIND_TUPLE; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_property {
|
||||
@@ -180,10 +212,12 @@ struct common_chat_schema_property {
|
||||
};
|
||||
|
||||
struct common_chat_schema_object : common_chat_schema {
|
||||
static constexpr node_kind KIND = KIND_OBJECT;
|
||||
|
||||
std::vector<common_chat_schema_property> properties; // in schema order
|
||||
common_chat_schema_ptr additional_properties; // null when not allowed
|
||||
|
||||
node_kind kind() const override { return KIND_OBJECT; }
|
||||
node_kind kind() const override { return KIND; }
|
||||
};
|
||||
|
||||
struct common_chat_schema_document {
|
||||
|
||||
@@ -300,7 +300,7 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = has_tools && !require_tools;
|
||||
data.grammar_lazy = !has_response_format && has_tools && !require_tools;
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
});
|
||||
|
||||
+269
-159
@@ -1,152 +1,284 @@
|
||||
#include "parsers.h"
|
||||
|
||||
namespace workaround {
|
||||
#include "log.h"
|
||||
|
||||
// Gemma4 uses a custom tool_responses field instead of role:tool messages.
|
||||
//
|
||||
// This will transform a sequence of messages:
|
||||
// assistant(tool_call+) -> tool+ -> assistant(content)
|
||||
//
|
||||
// Into a single assistant message containing a tool_responses field:
|
||||
// assistant(content + tool_call + tool_responses)
|
||||
//
|
||||
// This is necessary for the Gemma4 chat template to properly format the prompt.
|
||||
// See https://ai.google.dev/gemma/docs/core/prompt-formatting-gemma4
|
||||
struct gemma4_model_turn_builder {
|
||||
json & messages;
|
||||
size_t pos;
|
||||
json tool_calls = json::array();
|
||||
json tool_responses = json::array();
|
||||
json content;
|
||||
json reasoning_content;
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
gemma4_model_turn_builder(json & msgs, size_t pos) : messages(msgs), pos(pos) {}
|
||||
namespace {
|
||||
|
||||
void collect() {
|
||||
// Collect the first assistant message
|
||||
auto & msg = messages[pos];
|
||||
if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
|
||||
// According to the prompt formatting guide, we need to preserve reasoning_content
|
||||
// between function calls. The current chat templates do not support this, but we will do it anyway.
|
||||
reasoning_content = msg.at("reasoning_content");
|
||||
using gemma4 = common_chat_peg_gemma4_mapper;
|
||||
|
||||
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");
|
||||
}
|
||||
for (auto & tc : msg.at("tool_calls")) {
|
||||
tool_calls.push_back(tc);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
~depth_guard() { depth--; }
|
||||
};
|
||||
|
||||
// Collect tool call results
|
||||
while (pos < messages.size() && messages[pos].value("role", "") == "tool") {
|
||||
collect_result(messages[pos]);
|
||||
pos++;
|
||||
}
|
||||
// Gemma 4 value parsers
|
||||
void gemma4_value_rules(common_chat_peg_builder & p) {
|
||||
// The tags are what gemma4_to_json() in the mapper reads, the rules may wrap them freely.
|
||||
p.rule("gemma4-string", p.tag(gemma4::STRING, p.literal("<|\"|>") + p.tag(gemma4::STRING_CONTENT, p.until("<|\"|>")) + p.literal("<|\"|>")));
|
||||
|
||||
// Check if the next assistant message is the final message
|
||||
if (pos < messages.size() && messages[pos].value("role", "") == "assistant") {
|
||||
auto & next = messages[pos];
|
||||
if (!has_tool_calls(next) && has_content(next)) {
|
||||
content = next.at("content");
|
||||
pos++;
|
||||
}
|
||||
p.rule("gemma4-bool", p.tag(gemma4::BOOL, p.json_bool()));
|
||||
|
||||
p.rule("gemma4-null", p.tag(gemma4::NULL_, p.json_null()));
|
||||
|
||||
p.rule("gemma4-number", p.tag(gemma4::NUMBER, p.json_number()));
|
||||
|
||||
// Keys are bare in the template, but Gemma 4 occasionally wraps them in quotes
|
||||
p.rule("gemma4-dict-key", p.tag(gemma4::DICT_KEY, p.choice({
|
||||
p.literal("<|\"|>") + p.tag(gemma4::DICT_KEY_NAME, p.until("<|\"|>")) + p.literal("<|\"|>"),
|
||||
p.literal("\"") + p.tag(gemma4::DICT_KEY_NAME, p.chars("[^\"]", 1, -1)) + p.literal("\""),
|
||||
p.literal("'") + p.tag(gemma4::DICT_KEY_NAME, p.chars("[^']", 1, -1)) + p.literal("'"),
|
||||
p.tag(gemma4::DICT_KEY_NAME, p.chars("[^:}]", 1, -1)),
|
||||
}) + p.literal(":")));
|
||||
|
||||
p.rule("gemma4-dict-kv", p.tag(gemma4::DICT_KV, p.ref("gemma4-dict-key") + p.space() + p.ref("gemma4-value")));
|
||||
|
||||
p.rule("gemma4-dict", [&]() {
|
||||
auto ws = p.space();
|
||||
auto member = p.ref("gemma4-dict-kv");
|
||||
auto members = p.sequence({member, p.zero_or_more(p.sequence({p.literal(","), ws, member}))});
|
||||
return p.tag(gemma4::DICT, p.sequence({
|
||||
p.literal("{"), ws,
|
||||
p.choice({p.literal("}"), p.sequence({members, ws, p.literal("}")})})
|
||||
}));
|
||||
});
|
||||
|
||||
p.rule("gemma4-array", [&]() {
|
||||
auto ws = p.space();
|
||||
auto value = p.ref("gemma4-value");
|
||||
auto elements = p.sequence({value, p.zero_or_more(p.sequence({p.literal(","), ws, value}))});
|
||||
return p.tag(gemma4::ARRAY, p.sequence({
|
||||
p.literal("["), ws,
|
||||
p.choice({p.literal("]"), p.sequence({elements, ws, p.literal("]")})})
|
||||
}));
|
||||
});
|
||||
|
||||
p.rule("gemma4-value", [&]() {
|
||||
return p.choice({
|
||||
p.ref("gemma4-string"), p.ref("gemma4-dict"), p.ref("gemma4-array"),
|
||||
p.ref("gemma4-number"), p.ref("gemma4-bool"), p.ref("gemma4-null")
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Gemma 4 uses different delimiters and cannot leverage the json-schema-to-grammar implementation.
|
||||
// This implements a subset to enforce properties.
|
||||
class gemma4_schema_parser {
|
||||
common_chat_peg_builder & p_;
|
||||
std::string root_;
|
||||
int depth_ = 0;
|
||||
|
||||
std::map<const common_chat_schema *, std::string> refs_;
|
||||
|
||||
public:
|
||||
gemma4_schema_parser(common_chat_peg_builder & p, std::string root) : p_(p), root_(std::move(root)) {}
|
||||
|
||||
common_peg_parser visit(const common_chat_schema & node) { return visit(node, root_); }
|
||||
|
||||
private:
|
||||
common_peg_parser visit(const common_chat_schema & node, const std::string & name) {
|
||||
depth_guard guard(depth_);
|
||||
switch (node.kind()) {
|
||||
case common_chat_schema::KIND_REF: return visit_ref(node.as<common_chat_schema_ref>());
|
||||
case common_chat_schema::KIND_ANY_OF: return visit_any_of(node.as<common_chat_schema_any_of>(), name);
|
||||
case common_chat_schema::KIND_ALL_OF: return visit_all_of(node.as<common_chat_schema_all_of>(), name);
|
||||
case common_chat_schema::KIND_ARRAY: return visit_array(node.as<common_chat_schema_array>(), name);
|
||||
case common_chat_schema::KIND_TUPLE: return visit_tuple(node.as<common_chat_schema_tuple>(), name);
|
||||
case common_chat_schema::KIND_OBJECT: return visit_object(node.as<common_chat_schema_object>(), name);
|
||||
default: return p_.ref("gemma4-value");
|
||||
}
|
||||
}
|
||||
|
||||
void collect_result(const json & curr) {
|
||||
json response;
|
||||
if (curr.contains("content")) {
|
||||
const auto & content = curr.at("content");
|
||||
if (content.is_string()) {
|
||||
// Try to parse the content as JSON; fall back to raw string
|
||||
try {
|
||||
response = json::parse(content.get<std::string>());
|
||||
} catch (...) {
|
||||
response = content;
|
||||
// Whether a schema contributes a shape of its own, as opposed to accepting any value.
|
||||
static bool shaped(const common_chat_schema & node) {
|
||||
switch (node.kind()) {
|
||||
case common_chat_schema::KIND_REF:
|
||||
return node.as<common_chat_schema_ref>().target != nullptr;
|
||||
case common_chat_schema::KIND_ANY_OF:
|
||||
for (const auto & child : node.as<common_chat_schema_any_of>().children) {
|
||||
if (shaped(*child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case common_chat_schema::KIND_ALL_OF: {
|
||||
const auto & children = node.as<common_chat_schema_all_of>().children;
|
||||
return children.size() == 1 && shaped(*children[0]);
|
||||
}
|
||||
case common_chat_schema::KIND_ARRAY:
|
||||
case common_chat_schema::KIND_TUPLE:
|
||||
case common_chat_schema::KIND_OBJECT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The template writes keys bare. Models keep that for identifiers and quote anything else,
|
||||
// with single or double quotes or the string delimiter, so those are accepted as well.
|
||||
common_peg_parser key(const std::string & key) {
|
||||
auto name = [&]() { return p_.tag(gemma4::DICT_KEY_NAME, p_.literal(key)); };
|
||||
|
||||
bool identifier = !key.empty() && !std::isdigit(static_cast<unsigned char>(key[0]));
|
||||
for (char c : key) {
|
||||
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
|
||||
identifier = false;
|
||||
}
|
||||
}
|
||||
if (identifier) {
|
||||
return p_.tag(gemma4::DICT_KEY, name() + p_.literal(":"));
|
||||
}
|
||||
|
||||
auto alts = p_.choice();
|
||||
if (!key.empty() && key.find_first_of(":}") == std::string::npos && std::string("'\"<").find(key[0]) == std::string::npos) {
|
||||
// Check if key is unambiguous
|
||||
alts |= name();
|
||||
}
|
||||
// Gemma 4 may emit keys in its string delimiters or quotes
|
||||
alts |= p_.literal("<|\"|>") + name() + p_.literal("<|\"|>");
|
||||
if (key.find('\'') == std::string::npos) {
|
||||
alts |= p_.literal("'") + name() + p_.literal("'");
|
||||
}
|
||||
if (key.find('"') == std::string::npos) {
|
||||
alts |= p_.literal("\"") + name() + p_.literal("\"");
|
||||
}
|
||||
return p_.tag(gemma4::DICT_KEY, alts + p_.literal(":"));
|
||||
}
|
||||
|
||||
common_peg_parser visit_ref(const common_chat_schema_ref & ref) {
|
||||
if (!ref.target) {
|
||||
return p_.ref("gemma4-value");
|
||||
}
|
||||
auto it = refs_.find(ref.target);
|
||||
if (it != refs_.end()) {
|
||||
return p_.ref(it->second);
|
||||
}
|
||||
// A target reached from several places gets one rule, named by the order it was first seen.
|
||||
auto rule = root_ + "-ref-" + std::to_string(refs_.size());
|
||||
refs_[ref.target] = rule;
|
||||
return p_.rule(rule, [&]() { return visit(*ref.target, rule + "-value"); });
|
||||
}
|
||||
|
||||
// Shaped alternatives are tried first, then anything else collapses into one generic value.
|
||||
common_peg_parser visit_any_of(const common_chat_schema_any_of & node, const std::string & name) {
|
||||
if (!shaped(node)) {
|
||||
return p_.ref("gemma4-value");
|
||||
}
|
||||
if (node.children.size() == 1) {
|
||||
return visit(*node.children[0], name);
|
||||
}
|
||||
auto alts = p_.choice();
|
||||
bool loose = false;
|
||||
for (size_t i = 0; i < node.children.size(); i++) {
|
||||
if (shaped(*node.children[i])) {
|
||||
alts |= visit(*node.children[i], name + "-" + std::to_string(i));
|
||||
} else {
|
||||
response = content;
|
||||
loose = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
|
||||
// Match name with corresponding tool call
|
||||
size_t idx = tool_responses.size();
|
||||
if (idx < tool_calls.size()) {
|
||||
auto & tc = tool_calls[idx];
|
||||
if (tc.contains("function")) {
|
||||
name = tc.at("function").value("name", "");
|
||||
}
|
||||
if (loose) {
|
||||
alts |= p_.ref("gemma4-value");
|
||||
}
|
||||
|
||||
// Fallback to the tool call id
|
||||
if (name.empty()) {
|
||||
name = curr.value("tool_call_id", "");
|
||||
}
|
||||
|
||||
tool_responses.push_back({{"name", name}, {"response", response}});
|
||||
return p_.rule(name, alts);
|
||||
}
|
||||
|
||||
json build() {
|
||||
collect();
|
||||
common_peg_parser visit_all_of(const common_chat_schema_all_of & node, const std::string & name) {
|
||||
if (node.children.size() == 1) {
|
||||
return visit(*node.children[0], name);
|
||||
}
|
||||
return p_.ref("gemma4-value");
|
||||
}
|
||||
|
||||
json msg = {
|
||||
{"role", "assistant"},
|
||||
{"tool_calls", tool_calls},
|
||||
common_peg_parser visit_array(const common_chat_schema_array & node, const std::string & name) {
|
||||
auto ws = p_.space();
|
||||
auto item = node.items && shaped(*node.items) ? visit(*node.items, name + "-item") : p_.ref("gemma4-value");
|
||||
auto elements = item + p_.zero_or_more(p_.literal(",") + ws + item);
|
||||
return p_.rule(name, p_.tag(gemma4::ARRAY,
|
||||
p_.literal("[") + ws + (p_.literal("]") | (elements + ws + p_.literal("]")))));
|
||||
}
|
||||
|
||||
common_peg_parser visit_tuple(const common_chat_schema_tuple & node, const std::string & name) {
|
||||
auto ws = p_.space();
|
||||
auto items = p_.sequence();
|
||||
for (size_t i = 0; i < node.items.size(); i++) {
|
||||
if (i > 0) {
|
||||
items = items + p_.literal(",") + ws;
|
||||
}
|
||||
items = items + visit(*node.items[i], name + "-" + std::to_string(i));
|
||||
}
|
||||
return p_.rule(name, p_.tag(gemma4::ARRAY, p_.literal("[") + ws + items + ws + p_.literal("]")));
|
||||
}
|
||||
|
||||
common_peg_parser visit_object(const common_chat_schema_object & node, const std::string & name) {
|
||||
auto ws = p_.space();
|
||||
|
||||
std::vector<const common_chat_schema_property *> props;
|
||||
for (const auto & prop : node.properties) {
|
||||
props.push_back(&prop);
|
||||
}
|
||||
|
||||
// The Gemma 4 jinja template sorts properties, so our rules need to match.
|
||||
auto lower = [](const std::string & s) {
|
||||
std::string out = s;
|
||||
for (auto & c : out) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
if (!tool_responses.empty()) {
|
||||
msg["tool_responses"] = tool_responses;
|
||||
}
|
||||
if (!content.is_null()) {
|
||||
msg["content"] = content;
|
||||
}
|
||||
if (!reasoning_content.is_null()) {
|
||||
msg["reasoning_content"] = reasoning_content;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
std::stable_sort(props.begin(), props.end(), [&](const auto * a, const auto * b) {
|
||||
return lower(a->name) < lower(b->name);
|
||||
});
|
||||
|
||||
static bool has_content(const json & msg) {
|
||||
if (!msg.contains("content") || msg.at("content").is_null()) {
|
||||
return false;
|
||||
if (props.empty()) {
|
||||
if (node.additional_properties) {
|
||||
return p_.ref("gemma4-dict");
|
||||
}
|
||||
return p_.rule(name, p_.tag(gemma4::DICT, p_.literal("{") + ws + p_.literal("}")));
|
||||
}
|
||||
const auto & content = msg.at("content");
|
||||
if (content.is_string() && !content.get<std::string>().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (content.is_array() && !content.empty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool has_tool_calls(const json & msg) {
|
||||
return msg.contains("tool_calls") && msg.at("tool_calls").is_array() && !msg.at("tool_calls").empty();
|
||||
std::optional<common_peg_parser> rest;
|
||||
std::optional<common_peg_parser> first;
|
||||
if (node.additional_properties) {
|
||||
auto kv = p_.ref("gemma4-dict-kv");
|
||||
rest = p_.zero_or_more(p_.literal(",") + ws + kv);
|
||||
first = p_.optional(kv + *rest);
|
||||
}
|
||||
|
||||
auto then_rest = [&](const common_peg_parser & parser) {
|
||||
return rest ? parser + *rest : parser;
|
||||
};
|
||||
|
||||
for (size_t i = props.size(); i-- > 0;) {
|
||||
const auto & prop = *props[i];
|
||||
auto kv = p_.tag(gemma4::DICT_KV, key(prop.name) + ws + visit(*prop.schema, name + "-" + std::to_string(i)));
|
||||
|
||||
common_peg_parser rest_i = then_rest(p_.literal(",") + ws + kv);
|
||||
common_peg_parser first_i = then_rest(kv);
|
||||
if (!prop.required) {
|
||||
rest_i = then_rest(p_.optional(p_.literal(",") + ws + kv));
|
||||
first_i = first ? (first_i | *first) : p_.optional(first_i);
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
rest = p_.rule(name + "-rest-" + std::to_string(i), rest_i);
|
||||
}
|
||||
first = first_i;
|
||||
}
|
||||
|
||||
return p_.rule(name, p_.tag(gemma4::DICT, p_.literal("{") + ws + *first + ws + p_.literal("}")));
|
||||
}
|
||||
};
|
||||
|
||||
void convert_tool_responses_gemma4(json & messages) {
|
||||
json result = json::array();
|
||||
size_t i = 0;
|
||||
|
||||
while (i < messages.size()) {
|
||||
auto & msg = messages[i];
|
||||
|
||||
if (msg.value("role", "") != "assistant" || !msg.contains("tool_calls") ||
|
||||
!msg.at("tool_calls").is_array() || msg.at("tool_calls").empty()) {
|
||||
result.push_back(msg);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
gemma4_model_turn_builder builder(messages, i);
|
||||
result.push_back(builder.build());
|
||||
i = builder.pos;
|
||||
}
|
||||
|
||||
messages = result;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
common_chat_params common_chat_params_init_gemma4(const common_chat_template & tmpl,
|
||||
const autoparser::generation_params & inputs) {
|
||||
@@ -218,51 +350,29 @@ common_chat_params common_chat_params_init_gemma4(const common_chat_template &
|
||||
}
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
// Gemma4 tool calling syntax
|
||||
// Rules should match traversal logic in gemma4_to_json()
|
||||
p.rule("gemma4-string-content", p.until("<|\"|>"));
|
||||
p.rule("gemma4-string", p.literal("<|\"|>") + p.ref("gemma4-string-content") + p.literal("<|\"|>"));
|
||||
p.rule("gemma4-bool", p.json_bool());
|
||||
p.rule("gemma4-null", p.json_null());
|
||||
p.rule("gemma4-number", p.json_number());
|
||||
p.rule("gemma4-dict-key", p.rule("gemma4-dict-key-name", p.chars("[^:}]", 1, -1)) + p.literal(":"));
|
||||
p.rule("gemma4-dict-kv", p.ref("gemma4-dict-key") + p.space() + p.ref("gemma4-value"));
|
||||
p.rule("gemma4-dict", [&]() {
|
||||
auto ws = p.space();
|
||||
auto member = p.ref("gemma4-dict-kv");
|
||||
auto members = p.sequence({member, p.zero_or_more(p.sequence({p.literal(","), ws, member}))});
|
||||
return p.sequence({
|
||||
p.literal("{"), ws,
|
||||
p.choice({p.literal("}"), p.sequence({members, ws, p.literal("}")})})
|
||||
});
|
||||
});
|
||||
p.rule("gemma4-array", [&]() {
|
||||
auto ws = p.space();
|
||||
auto value = p.ref("gemma4-value");
|
||||
auto elements = p.sequence({value, p.zero_or_more(p.sequence({p.literal(","), ws, value}))});
|
||||
return p.sequence({
|
||||
p.literal("["), ws,
|
||||
p.choice({p.literal("]"), p.sequence({elements, ws, p.literal("]")})})
|
||||
});
|
||||
});
|
||||
p.rule("gemma4-value", [&]() {
|
||||
return p.choice({
|
||||
p.ref("gemma4-string"), p.ref("gemma4-dict"), p.ref("gemma4-array"),
|
||||
p.ref("gemma4-number"), p.ref("gemma4-bool"), p.ref("gemma4-null")
|
||||
});
|
||||
});
|
||||
gemma4_value_rules(p);
|
||||
|
||||
auto tool_choice = p.choice();
|
||||
|
||||
foreach_function(inputs.tools, [&](size_t tool_index, const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
std::string name = function.at("name");
|
||||
// TODO @aldehir : need to extend json-schema-to-grammar to produce more than JSON rules
|
||||
// const auto & params = function.at("parameters");
|
||||
|
||||
// Arguments follow the shape of the parameter schema when it is an object,
|
||||
// see gemma4_schema_parser, and are a generic dict otherwise.
|
||||
auto args = p.ref("gemma4-dict");
|
||||
try {
|
||||
auto doc = common_chat_schema_from_json(common_chat_tool_parameters(function));
|
||||
if (doc.root->kind() == common_chat_schema::KIND_OBJECT) {
|
||||
args = gemma4_schema_parser(p, "tool-" + std::to_string(tool_index) + "-args").visit(*doc.root);
|
||||
}
|
||||
} catch (const std::exception & e) {
|
||||
LOG_WRN("%s: tool %s uses the generic argument parser: %s\n", __func__, name.c_str(), e.what());
|
||||
}
|
||||
|
||||
tool_choice |= p.rule("tool-" + std::to_string(tool_index), p.tool(p.sequence({
|
||||
p.tool_open(p.tool_name(p.literal(name)) + p.peek(p.literal("{"))),
|
||||
p.tool_args(p.ref("gemma4-dict")),
|
||||
p.tool_args(args),
|
||||
})));
|
||||
});
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ common_chat_params common_chat_params_init_gpt_oss(const common_chat_template &
|
||||
auto content = p.rule("message-content", p.until("<|end|>"));
|
||||
auto channel = p.literal("<|channel|>") + (p.literal("commentary") | p.literal("analysis"));
|
||||
auto constrain_type = p.chars("[A-Za-z0-9_-]", 1, -1);
|
||||
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
|
||||
|
||||
// Occasionally, gpt-oss-20b will prefix channels with this commentary
|
||||
auto stray_commentary = p.optional(p.literal("<|channel|>commentary") + p.optional(p.literal(" to=assistant")));
|
||||
@@ -84,7 +85,7 @@ common_chat_params common_chat_params_init_gpt_oss(const common_chat_template &
|
||||
|
||||
auto analysis = p.ref("analysis");
|
||||
auto preamble = p.rule("preamble", p.literal("<|channel|>commentary<|message|>") + p.content(content) + end);
|
||||
auto final_msg = p.rule("final", stray_commentary + p.literal("<|channel|>final<|message|>") + p.content(content));
|
||||
auto final_msg = p.rule("final", stray_commentary + p.literal("<|channel|>final") + constraint + p.literal("<|message|>") + p.content(content));
|
||||
|
||||
// Consume any unsolicited tool calls, e.g. builtin functions
|
||||
auto unsolicited = p.rule("unsolicited", p.atomic(p.optional(channel) + p.literal(" to=") + content + end));
|
||||
@@ -92,7 +93,6 @@ common_chat_params common_chat_params_init_gpt_oss(const common_chat_template &
|
||||
auto any = p.rule("any", preamble | analysis);
|
||||
|
||||
if (has_response_format) {
|
||||
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
|
||||
auto response_format = p.rule("response-format",
|
||||
p.literal("<|channel|>final") + constraint + p.literal("<|message|>") +
|
||||
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)));
|
||||
@@ -109,7 +109,6 @@ common_chat_params common_chat_params_init_gpt_oss(const common_chat_template &
|
||||
const auto params = common_chat_tool_parameters(function);
|
||||
|
||||
auto func_name = p.literal(" to=functions.") + p.tool_name(p.literal(name));
|
||||
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
|
||||
auto args = p.tool_args(p.schema(p.json(), "tool-" + std::to_string(tool_index) + "-schema", params));
|
||||
|
||||
// recipient in role header
|
||||
|
||||
@@ -109,7 +109,7 @@ common_chat_params common_chat_params_init_ministral_3(const common_chat_templat
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
|
||||
data.grammar_lazy = !has_response_format && has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
|
||||
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
|
||||
@@ -46,9 +46,10 @@ common_chat_params common_chat_params_init_muse_spark(const common_chat_template
|
||||
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
// Constrained grammar whenever tools are offered.
|
||||
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
|
||||
// Constrained grammar whenever tools are offered or a response format is requested.
|
||||
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
|
||||
|
||||
data.parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
|
||||
auto start = p.rule("start", p.literal("<|start|>assistant"));
|
||||
@@ -75,6 +76,15 @@ common_chat_params common_chat_params_init_muse_spark(const common_chat_template
|
||||
auto final_msg = p.rule("final", recipient + p.literal("<|message|>") +
|
||||
p.content(p.until_one_of({ "<|eot|>", "<|eom|>" })));
|
||||
|
||||
if (has_response_format) {
|
||||
auto response_json = p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema));
|
||||
auto response_format = p.rule("response-format",
|
||||
recipient + p.literal("<|message|>") +
|
||||
((p.literal("```json") + p.space() + response_json + p.space() + p.literal("```")) | response_json));
|
||||
|
||||
return p.zero_or_more(start + analysis) + start + response_format;
|
||||
}
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
auto string_value = p.ac(
|
||||
p.tool_arg_string_value(p.until("</atem:parameter>")) + p.tool_arg_close(p.literal("</atem:parameter>")),
|
||||
@@ -132,7 +142,7 @@ common_chat_params common_chat_params_init_muse_spark(const common_chat_template
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
|
||||
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
});
|
||||
|
||||
@@ -41,12 +41,6 @@ std::string common_chat_template_generation_prompt_impl(
|
||||
|
||||
bool is_lfm2_template(const std::string & src);
|
||||
|
||||
namespace workaround {
|
||||
|
||||
void convert_tool_responses_gemma4(json & messages);
|
||||
|
||||
}
|
||||
|
||||
common_chat_params common_chat_params_init_cohere2moe(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
@@ -191,7 +191,7 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
|
||||
data.grammar_lazy = !has_response_format && has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
|
||||
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
|
||||
+1
-1
@@ -385,7 +385,7 @@ static bool is_draft_file(const std::string & fname) {
|
||||
}
|
||||
|
||||
common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const {
|
||||
const std::filesystem::path dir = std::filesystem::u8path(models_dir);
|
||||
const std::filesystem::path dir = models_dir;
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
throw std::runtime_error(string_format("error: '%s' does not exist or is not a directory\n", models_dir.c_str()));
|
||||
}
|
||||
|
||||
+137
-10
@@ -1027,6 +1027,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
const int32_t * target_layer_ids = nullptr; // model_dft's extract layer indices
|
||||
uint32_t target_layer_ids_n = 0;
|
||||
|
||||
// inject target features from the target's device buffer instead of host copies (env LLAMA_DFLASH_DEV=0 disables)
|
||||
bool use_dev_features = false;
|
||||
|
||||
common_speculative_impl_draft_dflash(const common_params_speculative & params, uint32_t n_seq,
|
||||
common_speculative_type type = COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH)
|
||||
: common_speculative_impl(type, n_seq, params.draft.n_max)
|
||||
@@ -1141,6 +1144,23 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true);
|
||||
}
|
||||
|
||||
// the device buffer keeps layers in ascending id order, the fc input expects target_layer_ids order
|
||||
{
|
||||
const char * env = getenv("LLAMA_DFLASH_DEV");
|
||||
const bool ascending = std::adjacent_find(target_layer_ids, target_layer_ids + target_layer_ids_n,
|
||||
[](int32_t a, int32_t b) { return a >= b; }) == target_layer_ids + target_layer_ids_n;
|
||||
|
||||
use_dev_features = !is_lilicorr && ascending && (env == nullptr || atoi(env) != 0);
|
||||
if (use_dev_features) {
|
||||
llama_set_embeddings_layer_inp_dev(ctx_tgt, true);
|
||||
use_dev_features = llama_supports_embd_row(ctx_dft);
|
||||
if (!use_dev_features) {
|
||||
llama_set_embeddings_layer_inp_dev(ctx_tgt, false);
|
||||
}
|
||||
}
|
||||
LOG_INF("%s: DFlash target features via %s\n", __func__, use_dev_features ? "device buffer" : "host copies");
|
||||
}
|
||||
|
||||
// DFlash2 reads its selector lattice from h_nextn and never consumes raw logits.
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ !is_dflash2 && !is_lilicorr);
|
||||
llama_set_causal_attn(ctx_dft, causal_attn); // DFlash needs non-causal attention unless the model says otherwise
|
||||
@@ -1233,6 +1253,25 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
for (int32_t offset = 0; offset < n_rows; offset += n_ubatch) {
|
||||
const int32_t n_chunk = std::min(n_ubatch, n_rows - offset);
|
||||
|
||||
if (use_dev_features) {
|
||||
// the features are the target batch rows in the target's device buffer
|
||||
batch_inject.clear();
|
||||
for (int32_t i = 0; i < n_chunk; ++i) {
|
||||
const int32_t row = i_batch_beg[seq_id] + offset + i;
|
||||
const llama_pos p = batch_in.tokens[row].pos[0];
|
||||
const llama_pos pos_arr[4] = { p, p, p, 0 };
|
||||
batch_inject.add_embd_row(row, pos_arr, seq_id, false);
|
||||
}
|
||||
|
||||
const int32_t rc = llama_process(ctx_dft, LLAMA_PROCESS_TYPE_DECODE, batch_inject.get());
|
||||
if (rc != 0) {
|
||||
LOG_ERR("%s: llama_process(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n",
|
||||
__func__, rc, (int) n_chunk, (int) offset);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// gather target features per extract layer; the fused decode encodes and
|
||||
// injects them into the K/V cache at the target positions
|
||||
features_buf.resize((size_t) n_chunk * n_embd_enc);
|
||||
@@ -1524,6 +1563,13 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
std::vector<int> i_last;
|
||||
std::vector<std::vector<float>> chain_h;
|
||||
|
||||
// target h rows on the device (llama_set_embeddings_nextn_dev): the catch-up in process() reads row k - 1 of the last
|
||||
// target decode in place, and row n_batch_tgt + seq_id for the first token of seq_id, uploaded from pending_h
|
||||
// pending_h stays on the host: process() and accept() fetch it before the next target decode overwrites the rows
|
||||
bool dev_rows = false;
|
||||
int32_t n_batch_tgt = 0;
|
||||
std::vector<int32_t> verify_row0; // [n_seq] device row of the first verify row, see verify_h_rows
|
||||
|
||||
common_speculative_impl_draft_mtp(const common_params_speculative & params, uint32_t n_seq)
|
||||
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_MTP, n_seq, params.draft.n_max)
|
||||
, params(params.draft)
|
||||
@@ -1574,12 +1620,12 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
}
|
||||
}
|
||||
|
||||
llama_set_embeddings_nextn(ctx_tgt, true, /*masked*/ false);
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true);
|
||||
|
||||
is_mem_shared = llama_get_ctx_other(ctx_dft) == ctx_tgt;
|
||||
chain_heads = n_mtp_layers > 1 && !is_mem_shared;
|
||||
|
||||
llama_set_embeddings_nextn(ctx_tgt, true, /*masked*/ is_mem_shared);
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true);
|
||||
|
||||
if (chain_heads) {
|
||||
this->params.n_max = std::min(this->params.n_max, n_mtp_layers);
|
||||
|
||||
@@ -1598,10 +1644,27 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
|
||||
verify_h.assign(n_seq, {});
|
||||
verify_h_rows.assign(n_seq, 0);
|
||||
|
||||
n_batch_tgt = (int32_t) llama_n_batch(ctx_tgt);
|
||||
verify_row0.assign(n_seq, -1);
|
||||
|
||||
// shared memory and chained heads keep the host rows
|
||||
if (!is_mem_shared && !chain_heads) {
|
||||
dev_rows = llama_set_embeddings_nextn_dev(ctx_tgt, true) && llama_set_embd_state_src(ctx_dft, ctx_tgt);
|
||||
if (!dev_rows) {
|
||||
llama_set_embd_state_src(ctx_dft, nullptr);
|
||||
llama_set_embeddings_nextn_dev(ctx_tgt, false);
|
||||
}
|
||||
}
|
||||
SPC_TRC("- target h rows on the device: %s\n", dev_rows ? "yes" : "no");
|
||||
}
|
||||
|
||||
~common_speculative_impl_draft_mtp() override {
|
||||
auto * ctx_dft = this->params.ctx_dft;
|
||||
if (dev_rows) {
|
||||
llama_set_embd_state_src(ctx_dft, nullptr);
|
||||
llama_set_embeddings_nextn_dev(this->params.ctx_tgt, false);
|
||||
}
|
||||
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) backend_chains.size(); ++seq_id) {
|
||||
if (backend_chains[seq_id] == nullptr) {
|
||||
continue;
|
||||
@@ -1676,7 +1739,16 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
// i.e. we cannot have seq_id like this: [0, 0, 0, 1, 1, 0, 1, 1]
|
||||
// ^--- this is a problem
|
||||
// TODO:this is generally true, but would be nice to assert it
|
||||
const float * h_tgt = llama_get_embeddings_nextn(ctx_tgt);
|
||||
if (dev_rows) {
|
||||
GGML_ASSERT(n_tokens <= n_batch_tgt && "the target batch exceeds the h_nextn device buffer");
|
||||
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
|
||||
if (i_batch_beg[seq_id] >= 0) {
|
||||
llama_set_embeddings_nextn_rows(ctx_tgt, n_batch_tgt + seq_id, 1, pending_h[seq_id].data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float * h_tgt = dev_rows ? nullptr : llama_get_embeddings_nextn(ctx_tgt);
|
||||
|
||||
for (int k = 0; k < n_tokens; ++k) {
|
||||
const auto & t = batch_in.tokens[k];
|
||||
@@ -1688,6 +1760,11 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
? batch.add(t.id, t.pos[0], seq_id, false)
|
||||
: batch.add_embd(t.embd, t.pos.data(), seq_id, false);
|
||||
|
||||
if (dev_rows) {
|
||||
batch.set_embd_state_row(idx, k == i_batch_beg[seq_id] ? n_batch_tgt + seq_id : k - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float * h_row = k == i_batch_beg[seq_id]
|
||||
? pending_h[seq_id].data()
|
||||
: h_tgt + (size_t) (k - 1) * n_embd;
|
||||
@@ -1733,16 +1810,36 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
}
|
||||
|
||||
const int32_t n_rows = i_batch_end[seq_id] - i_batch_beg[seq_id] + 1;
|
||||
verify_h_rows[seq_id] = n_rows;
|
||||
verify_h[seq_id].resize((size_t) n_rows * n_embd);
|
||||
if (is_mem_shared && !batch_in.tokens[i_batch_end[seq_id]].output) {
|
||||
verify_h_rows[seq_id] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < n_rows; ++i) {
|
||||
const float * h = llama_get_embeddings_nextn_ith(ctx_tgt, i_batch_beg[seq_id] + i);
|
||||
const bool all_rows_output = std::all_of(
|
||||
batch_in.tokens.begin() + i_batch_beg[seq_id],
|
||||
batch_in.tokens.begin() + i_batch_end[seq_id] + 1,
|
||||
[](const common_batch::token & token) { return token.output; });
|
||||
const int32_t h_rows = is_mem_shared && !all_rows_output ? 1 : n_rows;
|
||||
const int32_t h_offset = is_mem_shared && !all_rows_output ? i_batch_end[seq_id] : i_batch_beg[seq_id];
|
||||
|
||||
verify_h_rows[seq_id] = h_rows;
|
||||
|
||||
if (dev_rows) {
|
||||
// the rows stay on the device until the next target decode, only the pending row comes to the host
|
||||
verify_row0[seq_id] = h_offset;
|
||||
llama_get_embeddings_nextn_rows(ctx_tgt, h_offset + h_rows - 1, 1, pending_h[seq_id].data());
|
||||
continue;
|
||||
}
|
||||
|
||||
verify_h[seq_id].resize((size_t) h_rows * n_embd);
|
||||
|
||||
for (int32_t i = 0; i < h_rows; ++i) {
|
||||
const float * h = llama_get_embeddings_nextn_ith(ctx_tgt, h_offset + i);
|
||||
std::memcpy(verify_h[seq_id].data() + (size_t) i * n_embd, h, row_bytes);
|
||||
}
|
||||
|
||||
std::memcpy(pending_h[seq_id].data(),
|
||||
verify_h[seq_id].data() + (size_t) (n_rows - 1) * n_embd, row_bytes);
|
||||
verify_h[seq_id].data() + (size_t) (h_rows - 1) * n_embd, row_bytes);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1925,9 +2022,39 @@ struct common_speculative_impl_draft_mtp : public common_speculative_impl {
|
||||
}
|
||||
|
||||
const int32_t i_h = std::min<int32_t>(n_accepted, n_rows - 1);
|
||||
if (dev_rows) {
|
||||
// process() already fetched the last row
|
||||
if (i_h != n_rows - 1) {
|
||||
llama_get_embeddings_nextn_rows(params.ctx_tgt, verify_row0[seq_id] + i_h, 1, pending_h[seq_id].data());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const size_t row_bytes = (size_t) n_embd * sizeof(float);
|
||||
std::memcpy(pending_h[seq_id].data(), verify_h[seq_id].data() + (size_t) i_h * n_embd, row_bytes);
|
||||
}
|
||||
|
||||
bool get_state(llama_seq_id seq_id, std::vector<uint8_t> & data) const override {
|
||||
if (seq_id < 0 || seq_id >= (llama_seq_id) n_seq) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t row_bytes = (size_t) n_embd * sizeof(float);
|
||||
data.resize(row_bytes);
|
||||
std::memcpy(data.data(), pending_h[seq_id].data(), row_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_state(llama_seq_id seq_id, const std::vector<uint8_t> & data) override {
|
||||
if (seq_id < 0 || seq_id >= (llama_seq_id) n_seq) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t row_bytes = (size_t) n_embd * sizeof(float);
|
||||
if (data.size() != row_bytes) {
|
||||
return;
|
||||
}
|
||||
std::memcpy(pending_h[seq_id].data(), data.data(), row_bytes);
|
||||
}
|
||||
};
|
||||
|
||||
// state of self-speculation (simple implementation, not ngram-map)
|
||||
@@ -2731,7 +2858,7 @@ common_speculative_init_result::common_speculative_init_result(
|
||||
model_path = params.speculative.draft.mparams.path;
|
||||
LOG_INF("%s: loading draft model '%s'\n", __func__, model_path.c_str());
|
||||
|
||||
llama_model * model_dft = llama_model_load_from_file(model_path.c_str(), mparams);
|
||||
llama_model * model_dft = common_model_load(params.speculative.draft.mparams.get_paths(), mparams);
|
||||
if (model_dft == NULL) {
|
||||
LOG_ERR("%s: failed to load draft model, '%s'\n", __func__, model_path.c_str());
|
||||
return;
|
||||
|
||||
@@ -20,6 +20,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"NEOChatModel": "sensenova_u1",
|
||||
"AfmoeForCausalLM": "afmoe",
|
||||
"LagunaForCausalLM": "laguna",
|
||||
"RicursiveForCausalLM": "ricursive",
|
||||
"FlintForCausalLM": "flint",
|
||||
"ApertusForCausalLM": "llama",
|
||||
"ArceeForCausalLM": "llama",
|
||||
@@ -45,7 +46,9 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"ChameleonForConditionalGeneration": "chameleon",
|
||||
"ChatGLMForConditionalGeneration": "chatglm",
|
||||
"ChatGLMModel": "chatglm",
|
||||
"ChatterboxModel": "chatterbox",
|
||||
"ClefModel": "clef",
|
||||
"CloverMoeModelLM": "clover",
|
||||
"CodeShellForCausalLM": "codeshell",
|
||||
"CogVLMForCausalLM": "cogvlm",
|
||||
"Cohere2MoeForCausalLM": "command_r",
|
||||
@@ -60,6 +63,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"DeepseekV2ForCausalLM": "deepseek",
|
||||
"DeepseekV3ForCausalLM": "deepseek",
|
||||
"DeepseekV32ForCausalLM": "deepseek",
|
||||
"Xing4_0ForCausalLM": "xing",
|
||||
"DFlashDraftModel": "qwen",
|
||||
"DFlash2DraftModel": "qwen",
|
||||
"LiLiCorrDraftModel": "nemotron",
|
||||
@@ -93,6 +97,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"FalconForCausalLM": "falcon",
|
||||
"FalconH1ForCausalLM": "falcon_h1",
|
||||
"FalconMambaForCausalLM": "mamba",
|
||||
"FishOmniModel": "drama3",
|
||||
"GPT2LMHeadModel": "gpt2",
|
||||
"GPTBigCodeForCausalLM": "starcoder",
|
||||
"GPTNeoXForCausalLM": "gptneox",
|
||||
@@ -153,6 +158,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"KORMoForCausalLM": "qwen",
|
||||
"KimiK25ForConditionalGeneration": "deepseek",
|
||||
"KimiK3ForConditionalGeneration": "kimi_k3",
|
||||
"KimiK31ForConditionalGeneration": "kimi_k3",
|
||||
"KimiLinearForCausalLM": "kimi_linear",
|
||||
"KimiLinearModel": "kimi_linear",
|
||||
"KimiVLForConditionalGeneration": "deepseek",
|
||||
@@ -187,6 +193,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"Eagle3LlamaForCausalLM": "llama",
|
||||
"LlamaForCausalLMEagle3": "llama",
|
||||
"LlavaForConditionalGeneration": "llama",
|
||||
"LlavaOnevision2ForConditionalGeneration": "qwen",
|
||||
"LlavaStableLMEpochForCausalLM": "stablelm",
|
||||
"MPTForCausalLM": "mpt",
|
||||
"MT5ForConditionalGeneration": "t5",
|
||||
@@ -196,6 +203,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"MambaLMHeadModel": "mamba",
|
||||
"MapleForCausalLM": "maple",
|
||||
"MellumForCausalLM": "mellum",
|
||||
"Metis16ForCausalLM": "metis",
|
||||
"MiMoV2FlashForCausalLM": "mimo",
|
||||
"MiMoV2ForCausalLM": "mimo",
|
||||
"MiniCPM3ForCausalLM": "minicpm",
|
||||
@@ -222,6 +230,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"ModernBertForSequenceClassification": "bert",
|
||||
"ModernBertModel": "bert",
|
||||
"NanbeigeForCausalLM": "nanbeige",
|
||||
"Nemotron3_5TranscribeForConditionalGeneration": "nemotron",
|
||||
"NemotronForCausalLM": "nemotron",
|
||||
"NemotronHForCausalLM": "nemotron",
|
||||
"NemotronHPuzzleForCausalLM": "nemotron",
|
||||
@@ -327,6 +336,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
|
||||
MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"AudioFlamingo3ForConditionalGeneration": "ultravox",
|
||||
"ChatterboxModel": "chatterbox",
|
||||
"ClefModel": "clef",
|
||||
"CogVLMForCausalLM": "cogvlm",
|
||||
"Cohere2VisionForConditionalGeneration": "command_r",
|
||||
@@ -341,6 +351,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"DotsOCRForCausalLM": "dotsocr",
|
||||
"EmbeddingGemma2Model": "gemma",
|
||||
"Exaone4_5_ForConditionalGeneration": "exaone",
|
||||
"FishOmniModel": "drama3",
|
||||
"Gemma3ForConditionalGeneration": "gemma",
|
||||
"Gemma3nForConditionalGeneration": "gemma",
|
||||
"Gemma4ForConditionalGeneration": "gemma",
|
||||
@@ -360,6 +371,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"InternVisionModel": "internvl",
|
||||
"JanusForConditionalGeneration": "januspro",
|
||||
"KimiK25ForConditionalGeneration": "kimivl",
|
||||
"KimiK31ForConditionalGeneration": "kimivl",
|
||||
"KimiVLForConditionalGeneration": "kimivl",
|
||||
"Lfm2AudioForConditionalGeneration": "lfm2",
|
||||
"D1OmniModel": "lfm2",
|
||||
@@ -370,6 +382,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"LightOnOCRForConditionalGeneration": "lighton_ocr",
|
||||
"Llama4ForConditionalGeneration": "llama4",
|
||||
"LlavaForConditionalGeneration": "llava",
|
||||
"LlavaOnevision2ForConditionalGeneration": "magevl",
|
||||
"MERaLiON2ForConditionalGeneration": "ultravox",
|
||||
"MiMoV2ForCausalLM": "mimo",
|
||||
"MiniMaxM3SparseForConditionalGeneration": "minimax",
|
||||
@@ -379,6 +392,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"MiniCPMOcrForConditionalGeneration": "minicpm",
|
||||
"Mistral3ForConditionalGeneration": "llava",
|
||||
"Mistral3Model": "llava",
|
||||
"Nemotron3_5TranscribeForConditionalGeneration": "nemotron",
|
||||
"NemotronH_Omni_Reasoning_V3": "nemotron",
|
||||
"NemotronH_Nano_VL_V2": "nemotron",
|
||||
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
@@ -198,6 +199,18 @@ class SarvamMoEModel(BailingMoeV2Model):
|
||||
# - full rotary (no partial_rotary_factor)
|
||||
# - expert bias is zero-mean normalized at load time
|
||||
|
||||
def set_vocab(self):
|
||||
# BPE with 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()
|
||||
hparams = self.hparams
|
||||
|
||||
+141
-61
@@ -7,6 +7,7 @@ import ast
|
||||
import logging
|
||||
import contextlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -31,10 +32,10 @@ import gguf
|
||||
from gguf.vocab import MistralTokenizerType, MistralVocab
|
||||
|
||||
try:
|
||||
from mistral_common.tokens.tokenizers.base import TokenizerVersion # type: ignore[import-not-found, ty:unresolved-import]
|
||||
from mistral_common.tokens.tokenizers.multimodal import DATASET_MEAN as _MISTRAL_COMMON_DATASET_MEAN, DATASET_STD as _MISTRAL_COMMON_DATASET_STD # type: ignore[import-not-found, ty:unresolved-import]
|
||||
from mistral_common.tokens.tokenizers.tekken import Tekkenizer # type: ignore[import-not-found, ty:unresolved-import]
|
||||
from mistral_common.tokens.tokenizers.sentencepiece import ( # type: ignore[import-not-found, ty:unresolved-import]
|
||||
from mistral_common.tokens.tokenizers.base import TokenizerVersion # ty: ignore[import-not-found]
|
||||
from mistral_common.tokens.tokenizers.multimodal import DATASET_MEAN as _MISTRAL_COMMON_DATASET_MEAN, DATASET_STD as _MISTRAL_COMMON_DATASET_STD # ty: ignore[import-not-found]
|
||||
from mistral_common.tokens.tokenizers.tekken import Tekkenizer # ty: ignore[import-not-found]
|
||||
from mistral_common.tokens.tokenizers.sentencepiece import ( # ty: ignore[import-not-found]
|
||||
SentencePieceTokenizer,
|
||||
)
|
||||
|
||||
@@ -133,7 +134,8 @@ class ModelBase:
|
||||
target_model_dir: Path | None = None,
|
||||
fuse_gate_up_exps: bool = False,
|
||||
fp8_as_q8: bool = False,
|
||||
fuse_qkv: bool = False):
|
||||
fuse_qkv: bool = False,
|
||||
ple_only: bool = False, no_ple: bool = False):
|
||||
if type(self) is ModelBase or \
|
||||
type(self) is TextModel or \
|
||||
type(self) is MmprojModel:
|
||||
@@ -142,6 +144,11 @@ class ModelBase:
|
||||
if self.is_mistral_format and not _mistral_common_installed:
|
||||
raise ImportError(_mistral_import_error_msg)
|
||||
|
||||
if ple_only and no_ple:
|
||||
raise ValueError("ple_only and no_ple are mutually exclusive")
|
||||
self.ple_only = ple_only
|
||||
self.no_ple = no_ple
|
||||
self._has_ple = False
|
||||
self.dir_model = dir_model
|
||||
self.ftype = ftype
|
||||
self.fname_out = fname_out
|
||||
@@ -167,6 +174,8 @@ class ModelBase:
|
||||
self._fusable_qkv_weight_layers: set[int] = set()
|
||||
self._fusable_qkv_bias_layers: set[int] = set()
|
||||
self.hparams = ModelBase.load_hparams(self.dir_model, self.is_mistral_format) if hparams is None else hparams
|
||||
# source tensor name to filtered tensor name mapping.
|
||||
self._source_tensor_names: dict[str, str | None] = {}
|
||||
self.model_tensors = self.index_tensors(remote_hf_model_id=remote_hf_model_id)
|
||||
self.metadata_override = metadata_override
|
||||
self.model_name = model_name
|
||||
@@ -202,7 +211,8 @@ class ModelBase:
|
||||
|
||||
# Configure GGUF Writer
|
||||
self.gguf_writer = gguf.GGUFWriter(path=None, arch=gguf.MODEL_ARCH_NAMES[self.model_arch], endianess=self.endianess, use_temp_file=self.use_temp_file,
|
||||
split_max_tensors=split_max_tensors, split_max_size=split_max_size, dry_run=dry_run, small_first_shard=small_first_shard)
|
||||
split_max_tensors=split_max_tensors, split_max_size=split_max_size, dry_run=dry_run, small_first_shard=small_first_shard,
|
||||
tensor_filter=self._include_ple_tensor if ple_only or no_ple else None)
|
||||
|
||||
# Mistral specific
|
||||
self.disable_mistral_community_chat_template = disable_mistral_community_chat_template
|
||||
@@ -225,21 +235,25 @@ class ModelBase:
|
||||
tensors: dict[str, Callable[[], Tensor]] = {}
|
||||
|
||||
if remote_hf_model_id is not None:
|
||||
is_safetensors = True
|
||||
|
||||
logger.info(f"Using remote model with HuggingFace id: {remote_hf_model_id}")
|
||||
remote_tensors = gguf.utility.SafetensorRemote.get_list_tensors_hf_model(remote_hf_model_id)
|
||||
for name, remote_tensor in remote_tensors.items():
|
||||
data_gen = lambda r=remote_tensor: LazyTorchTensor.from_remote_tensor(r) # noqa: E731
|
||||
if titem := self.filter_tensors((name, data_gen)):
|
||||
titem = self.filter_tensors((name, data_gen))
|
||||
self._source_tensor_names[name] = titem[0] if titem else None
|
||||
if titem:
|
||||
tname, tgen = titem
|
||||
tensors[tname] = tgen
|
||||
|
||||
return tensors
|
||||
|
||||
prefix = "model" if not self.is_mistral_format else "consolidated"
|
||||
return self.index_local_tensors("model" if not self.is_mistral_format else "consolidated")
|
||||
|
||||
# index the local "<prefix>*.safetensors" files (or pytorch_model*.bin), through filter_tensors
|
||||
def index_local_tensors(self, prefix: str) -> dict[str, Callable[[], Tensor]]:
|
||||
tensors: dict[str, Callable[[], Tensor]] = {}
|
||||
part_names: list[str] = ModelBase.get_model_part_names(self.dir_model, prefix, ".safetensors")
|
||||
is_safetensors: bool = len(part_names) > 0 or (not self.is_mistral_format and (self.dir_model / "model.safetensors.index.json").is_file())
|
||||
is_safetensors: bool = len(part_names) > 0 or (not self.is_mistral_format and (self.dir_model / f"{prefix}.safetensors.index.json").is_file())
|
||||
if not is_safetensors:
|
||||
part_names = ModelBase.get_model_part_names(self.dir_model, "pytorch_model", ".bin")
|
||||
|
||||
@@ -247,7 +261,7 @@ class ModelBase:
|
||||
tensor_names_from_parts: set[str] = set()
|
||||
|
||||
if not self.is_mistral_format:
|
||||
index_name = "model.safetensors" if is_safetensors else "pytorch_model.bin"
|
||||
index_name = f"{prefix}.safetensors" if is_safetensors else "pytorch_model.bin"
|
||||
index_name += ".index.json"
|
||||
index_file = self.dir_model / index_name
|
||||
|
||||
@@ -259,7 +273,7 @@ class ModelBase:
|
||||
if weight_map is None or not isinstance(weight_map, dict):
|
||||
raise ValueError(f"Can't load 'weight_map' from {index_name!r}")
|
||||
tensor_names_from_index.update(weight_map.keys())
|
||||
part_dict: dict[str, None] = dict.fromkeys(weight_map.values(), None) # ty: ignore[invalid-assignment]
|
||||
part_dict: dict[str, None] = dict.fromkeys(weight_map.values(), None)
|
||||
part_names = sorted(part_dict.keys())
|
||||
else:
|
||||
weight_map = {}
|
||||
@@ -292,7 +306,9 @@ class ModelBase:
|
||||
data_gen = lambda data=data_torch: LazyTorchTensor.from_eager(data) # noqa: E731
|
||||
else:
|
||||
data_gen = lambda data=data_torch: data # noqa: E731
|
||||
if titem := self.filter_tensors((name, data_gen)):
|
||||
titem = self.filter_tensors((name, data_gen))
|
||||
self._source_tensor_names[name] = titem[0] if titem else None
|
||||
if titem:
|
||||
tname, tgen = titem
|
||||
tensors[tname] = tgen
|
||||
|
||||
@@ -328,6 +344,36 @@ class ModelBase:
|
||||
logger.info(f" + {scale_name} (per-expert scale, shape [{len(scales)}])")
|
||||
self.gguf_writer.add_tensor(scale_name, scale_vals)
|
||||
|
||||
def _prepare_kv_cache_scales(self):
|
||||
for name in list(self.model_tensors.keys()):
|
||||
if not name.endswith((".k_scale", ".v_scale")):
|
||||
continue
|
||||
|
||||
mapped = self.tensor_map.get_type_and_name(key=name, try_suffixes=(".k_scale", ".v_scale"))
|
||||
if mapped is None:
|
||||
continue
|
||||
|
||||
tensor_type, new_name = mapped
|
||||
if tensor_type not in (gguf.MODEL_TENSOR.ATTN_K, gguf.MODEL_TENSOR.ATTN_V, gguf.MODEL_TENSOR.ATTN_QKV):
|
||||
continue
|
||||
|
||||
bid = int(new_name.split(".")[1])
|
||||
scale_type = gguf.MODEL_TENSOR.ATTN_K if name.endswith(".k_scale") else gguf.MODEL_TENSOR.ATTN_V
|
||||
suffix = ".k_scale" if name.endswith(".k_scale") else ".v_scale"
|
||||
new_name = gguf.TENSOR_NAMES[scale_type].format(bid=bid) + suffix
|
||||
|
||||
scale = LazyTorchTensor.to_eager(self.model_tensors.pop(name)())
|
||||
if scale.dtype not in (torch.bfloat16, torch.float32) or scale.numel() != 1:
|
||||
raise ValueError(f"KV cache scale {name!r} must be a scalar BF16 or FP32 tensor")
|
||||
|
||||
scale = scale.float()
|
||||
value = float(scale.item())
|
||||
if not math.isfinite(value) or value == 0.0:
|
||||
raise ValueError(f"KV cache scale {name!r} must be finite and nonzero")
|
||||
|
||||
logger.info(f" + {new_name} (KV cache scale, shape [1])")
|
||||
self.gguf_writer.add_tensor(new_name, scale.flatten().numpy())
|
||||
|
||||
def dequant_model(self):
|
||||
# If all quantized tensors were already handled (e.g. pure NVFP4), skip
|
||||
if self._is_nvfp4 and not any(k.endswith((".weight_scale", ".weight_scale_inv", ".input_scale", ".activation_scale", "_activation_scale", ".k_scale", ".v_scale")) for k in self.model_tensors):
|
||||
@@ -492,7 +538,7 @@ class ModelBase:
|
||||
s = self.model_tensors[name]
|
||||
self.model_tensors[weight_name] = lambda w=w, s=s: dequant_bitnet(w(), s())
|
||||
tensors_to_remove.append(name)
|
||||
elif quant_method == "fp8":
|
||||
elif quant_method in ("fp8", "bsgemm-moe"): # bsgemm-moe: drama-3, same block layout
|
||||
block_size = quant_config.get("weight_block_size")
|
||||
for name in self.model_tensors.keys():
|
||||
if name.endswith("_scale_inv"):
|
||||
@@ -1277,6 +1323,7 @@ class ModelBase:
|
||||
expert_scales: dict[tuple[int, str], list[tuple[int, float]]] = {}
|
||||
expert_input_scales: dict[tuple[int, str], list[tuple[int, float]]] = {}
|
||||
expert_shapes: dict[tuple[int, str], list[int]] = {}
|
||||
expert_names: dict[tuple[int, str], str] = {}
|
||||
n_experts = self.find_hparam(["num_local_experts", "num_experts", "n_routed_experts"], optional=True) or 0
|
||||
consumed: list[str] = []
|
||||
|
||||
@@ -1325,6 +1372,7 @@ class ModelBase:
|
||||
expert_scales[key] = []
|
||||
expert_input_scales[key] = []
|
||||
expert_shapes[key] = shape
|
||||
expert_names[key] = name
|
||||
expert_blocks[key].append((expert_id, raw.copy()))
|
||||
# Collect per-expert scale2 (scalar per expert)
|
||||
expert_scales[key].append((expert_id, float(scale2.float().sum())))
|
||||
@@ -1333,13 +1381,13 @@ class ModelBase:
|
||||
|
||||
# Flush when all experts for this (layer, proj) are collected
|
||||
if n_experts > 0 and len(expert_blocks[key]) >= n_experts:
|
||||
self._flush_nvfp4_experts(key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type)
|
||||
self._flush_nvfp4_experts(key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type)
|
||||
else:
|
||||
self._repack_nvfp4(name, weight, scale, scale2, input_scale)
|
||||
|
||||
# Flush any remaining experts (fallback if n_experts was unknown)
|
||||
for bid, proj_type in list(expert_blocks.keys()):
|
||||
self._flush_nvfp4_experts((bid, proj_type), expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type)
|
||||
self._flush_nvfp4_experts((bid, proj_type), expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type)
|
||||
|
||||
# Remove consumed tensors so get_tensors/modify_tensors won't see them
|
||||
for name in consumed:
|
||||
@@ -1350,7 +1398,7 @@ class ModelBase:
|
||||
if name.endswith((".k_scale", ".v_scale")):
|
||||
del self.model_tensors[name]
|
||||
|
||||
def _flush_nvfp4_experts(self, key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type):
|
||||
def _flush_nvfp4_experts(self, key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type):
|
||||
experts = expert_blocks.pop(key)
|
||||
scales = expert_scales.pop(key)
|
||||
input_scales = expert_input_scales.pop(key)
|
||||
@@ -1362,7 +1410,7 @@ class ModelBase:
|
||||
new_name = self.map_tensor_name(merged_name)
|
||||
logger.info(f"Repacked {new_name} with shape [{len(experts)}, {shape[0]}, {shape[1]}] and quantization NVFP4")
|
||||
self.gguf_writer.add_tensor(new_name, merged, raw_dtype=gguf.GGMLQuantizationType.NVFP4)
|
||||
self._tag_prec_a4(merged_name, new_name)
|
||||
self._tag_prec_a4(expert_names.pop(key), new_name)
|
||||
|
||||
scales.sort(key=lambda x: x[0])
|
||||
self._write_scales_tensor(new_name.replace(".weight", ".scale"), [s[1] for s in scales])
|
||||
@@ -1372,6 +1420,11 @@ class ModelBase:
|
||||
|
||||
del experts, merged
|
||||
|
||||
def _include_ple_tensor(self, name: str) -> bool:
|
||||
is_ple = name == gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD] + ".weight"
|
||||
self._has_ple |= is_ple
|
||||
return is_ple if self.ple_only else not is_ple
|
||||
|
||||
def prepare_tensors(self):
|
||||
# detect NVFP4 quantization (ModelOpt and Compressed-tensors formats)
|
||||
quantization_config = self.hparams.get("quantization_config") or {}
|
||||
@@ -1413,18 +1466,27 @@ class ModelBase:
|
||||
# Per-tensor NVFP4 precision.
|
||||
self._nvfp4_layer_algo = {}
|
||||
if quant_layers:
|
||||
# store all possible module paths and assert if a quantized layer is not in the model
|
||||
modules: set[str] = set()
|
||||
for name in self.model_tensors:
|
||||
while name := name.rpartition(".")[0]:
|
||||
modules.add(name)
|
||||
|
||||
for layer_name, entry in quant_layers.items():
|
||||
if not isinstance(entry, dict):
|
||||
# quantized_layers keys are module paths in the source checkpoint, not tensor names
|
||||
# resolve each source tensor to the closest quantized_layers parent and record the algo under its filtered name for _tag_prec_a4
|
||||
layer_algo = {k: v.get("quant_algo") for k, v in quant_layers.items() if isinstance(v, dict)}
|
||||
matched: set[str] = set()
|
||||
for src_name, filtered_name in self._source_tensor_names.items():
|
||||
module = src_name
|
||||
while module := module.rpartition(".")[0]:
|
||||
if module in layer_algo:
|
||||
break
|
||||
if not module:
|
||||
continue
|
||||
if titem := self.filter_tensors((layer_name, lambda: torch.empty(0))):
|
||||
assert titem[0] in modules, f"quantized_layers entry {layer_name!r} is not in the model tensors"
|
||||
self._nvfp4_layer_algo[titem[0]] = entry.get("quant_algo")
|
||||
|
||||
matched.add(module)
|
||||
# filter_tensors may drop a tensors so we need to check for that here.
|
||||
if filtered_name is not None:
|
||||
self._nvfp4_layer_algo[filtered_name.removesuffix(".weight").removesuffix(".bias")] = layer_algo[module]
|
||||
|
||||
unmatched = layer_algo.keys() - matched
|
||||
assert not unmatched, f"quantized_layers entries not in the model tensors: {sorted(unmatched)}"
|
||||
|
||||
self._prepare_kv_cache_scales()
|
||||
|
||||
# NVFP4 weights are repacked and written directly to gguf_writer.
|
||||
# This must run before dequant_model so NVFP4 tensors are removed
|
||||
@@ -1496,6 +1558,8 @@ class ModelBase:
|
||||
break
|
||||
|
||||
for new_name, data_torch in (self.modify_tensors(data_torch, name, bid)):
|
||||
if (self.ple_only or self.no_ple) and not self._include_ple_tensor(new_name):
|
||||
continue
|
||||
n_dims = len(data_torch.shape)
|
||||
data_qtype: gguf.GGMLQuantizationType | bool = self.tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
@@ -1628,6 +1692,8 @@ class ModelBase:
|
||||
|
||||
def write(self):
|
||||
self.prepare_tensors()
|
||||
if (self.ple_only or self.no_ple) and not self._has_ple:
|
||||
raise ValueError("model has no per-layer embedding table to export separately")
|
||||
self.prepare_metadata(vocab_only=False)
|
||||
self.gguf_writer.write_header_to_file(path=self.fname_out)
|
||||
self.gguf_writer.write_kv_data_to_file()
|
||||
@@ -1820,7 +1886,8 @@ class TextModel(ModelBase):
|
||||
fname_default: str = gguf.naming_convention(self.metadata.name, self.metadata.basename, self.metadata.finetune, self.metadata.version, size_label=None, output_type=None, model_type="vocab")
|
||||
|
||||
# Use the default filename
|
||||
self.fname_out = self.fname_out / f"{fname_default}.gguf"
|
||||
prefix = "ple-" if self.ple_only else "no-ple-" if self.no_ple else ""
|
||||
self.fname_out = self.fname_out / f"{prefix}{fname_default}.gguf"
|
||||
else:
|
||||
# Output path is a custom defined templated filename
|
||||
# Note: `not is_dir()` is used because `.is_file()` will not detect
|
||||
@@ -1829,8 +1896,9 @@ class TextModel(ModelBase):
|
||||
# Process templated file name with the output ftype, useful with the "auto" ftype
|
||||
self.fname_out = self.fname_out.parent / gguf.fill_templated_filename(self.fname_out.name, output_type)
|
||||
|
||||
logger.info("Set model tokenizer")
|
||||
self.set_vocab()
|
||||
if not self.ple_only:
|
||||
logger.info("Set model tokenizer")
|
||||
self.set_vocab()
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
self.gguf_writer.add_block_count(self.block_count)
|
||||
@@ -1987,15 +2055,16 @@ class TextModel(ModelBase):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
vocab_size = self.hparams.get("vocab_size", len(tokenizer.vocab)) # ty: ignore[unresolved-attribute]
|
||||
assert max(tokenizer.vocab.values()) < vocab_size # ty: ignore[unresolved-attribute]
|
||||
assert tokenizer is not None
|
||||
vocab_size = self.hparams.get("vocab_size", len(tokenizer.vocab))
|
||||
assert max(tokenizer.vocab.values()) < vocab_size
|
||||
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in tokenizer.vocab.items()} # ty: ignore[unresolved-attribute]
|
||||
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in tokenizer.vocab.items()}
|
||||
added_vocab = tokenizer.get_added_vocab()
|
||||
|
||||
added_tokens_decoder = tokenizer.added_tokens_decoder # ty: ignore[unresolved-attribute]
|
||||
added_tokens_decoder = tokenizer.added_tokens_decoder
|
||||
|
||||
for i in range(vocab_size):
|
||||
if i not in reverse_vocab:
|
||||
@@ -2008,7 +2077,7 @@ class TextModel(ModelBase):
|
||||
# To avoid unexpected issues - we make sure to normalize non-normalized tokens
|
||||
if not added_tokens_decoder[i].normalized:
|
||||
previous_token = token
|
||||
token = tokenizer.decode(tokenizer.encode(token, add_special_tokens=False)) # ty: ignore[unresolved-attribute, invalid-assignment]
|
||||
token = tokenizer.decode(tokenizer.encode(token, add_special_tokens=False)) # ty: ignore[invalid-assignment]
|
||||
if previous_token != token:
|
||||
logger.info(f"{repr(previous_token)} is encoded and decoded back to {repr(token)} using AutoTokenizer")
|
||||
|
||||
@@ -2114,6 +2183,9 @@ class TextModel(ModelBase):
|
||||
if chkhsh == "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4":
|
||||
# ref: https://huggingface.co/evilfreelancer/ruGPT3XL
|
||||
res = "gpt-2"
|
||||
if chkhsh == "df1bc34a6366466bcb36a9a1b1cc0afdb08cfe21c9250602c930f159085390a9":
|
||||
# ref: https://huggingface.co/Lernex/Metis-1.6-Logos
|
||||
res = "gpt-2"
|
||||
if chkhsh == "9e454714343b69b99b71795c1d27a68c2a1d15dab111f4d353109f966af29da7":
|
||||
# ref: https://huggingface.co/LiquidAI/LFM2.5-8B-A1B
|
||||
res = "lfm2"
|
||||
@@ -2415,6 +2487,7 @@ class TextModel(ModelBase):
|
||||
def _set_vocab_hybriddna(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
vocab = tokenizer.get_vocab()
|
||||
vocab_size = self.hparams.get("vocab_size", len(vocab))
|
||||
assert max(vocab.values()) < vocab_size
|
||||
@@ -2428,11 +2501,11 @@ class TextModel(ModelBase):
|
||||
if tokenizer.k != 6 or tail not in ("pad_a", "pad_n"):
|
||||
raise NotImplementedError(f"HybridDNATokenizer with k={tokenizer.k}, tail={tail!r}")
|
||||
for kmer in [*tokenizer.kmers, *getattr(tokenizer, "npad_tokens", [])]:
|
||||
reverse_vocab[tokenizer.dna_token_to_id[kmer]] = kmer + "\ue000" # ty: ignore[unresolved-attribute]
|
||||
reverse_vocab[tokenizer.dna_token_to_id[kmer]] = kmer + "\ue000"
|
||||
# older HybridDNATokenizer versions (Carbon-500M/3B/8B) do not register the base tokenizer's added tokens (<|im_start|>, <think>, ...) as their own
|
||||
# but they are still encoded as special tokens: take their types from the base tokenizer
|
||||
base_tokenizer = getattr(tokenizer, "_base_tokenizer", tokenizer)
|
||||
added_tokens_decoder = {**base_tokenizer.added_tokens_decoder, **tokenizer.added_tokens_decoder} # ty: ignore[unresolved-attribute]
|
||||
added_tokens_decoder = {**base_tokenizer.added_tokens_decoder, **tokenizer.added_tokens_decoder}
|
||||
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
@@ -2479,24 +2552,25 @@ class TextModel(ModelBase):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
vocab_size = hparams["vocab_size"]
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size # ty: ignore[unresolved-attribute]
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size
|
||||
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
merges = []
|
||||
vocab = {}
|
||||
mergeable_ranks = tokenizer.mergeable_ranks # ty: ignore[unresolved-attribute]
|
||||
mergeable_ranks = tokenizer.mergeable_ranks
|
||||
for token, rank in mergeable_ranks.items():
|
||||
vocab[QwenModel.token_bytes_to_string(token)] = rank
|
||||
if len(token) == 1:
|
||||
continue
|
||||
merged = QwenModel.bpe(mergeable_ranks, token, max_rank=rank)
|
||||
assert len(merged) == 2
|
||||
merges.append(' '.join(map(QwenModel.token_bytes_to_string, merged)))
|
||||
merges.append(str(' '.join(map(QwenModel.token_bytes_to_string, merged))))
|
||||
|
||||
# for this kind of tokenizer, added_vocab is not a subset of vocab, so they need to be combined
|
||||
added_vocab = tokenizer.special_tokens # ty: ignore[unresolved-attribute]
|
||||
added_vocab = tokenizer.special_tokens
|
||||
reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **added_vocab}.items()}
|
||||
|
||||
for i in range(vocab_size):
|
||||
@@ -2519,10 +2593,10 @@ class TextModel(ModelBase):
|
||||
special_vocab.merges = merges
|
||||
# only add special tokens when they were not already loaded from config.json
|
||||
if len(special_vocab.special_token_ids) == 0:
|
||||
special_vocab._set_special_token("bos", tokenizer.special_tokens["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eos", tokenizer.special_tokens["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("bos", tokenizer.special_tokens["<|endoftext|>"])
|
||||
special_vocab._set_special_token("eos", tokenizer.special_tokens["<|endoftext|>"])
|
||||
# this one is usually not in config.json anyway
|
||||
special_vocab._set_special_token("unk", tokenizer.special_tokens["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("unk", tokenizer.special_tokens["<|endoftext|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def _set_vocab_sentencepiece(self, add_to_gguf=True):
|
||||
@@ -2796,21 +2870,23 @@ class TextModel(ModelBase):
|
||||
def _set_vocab_glmedge(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
assert tokenizer is not None
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
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._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"])
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def _set_vocab_glm(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
assert tokenizer is not None
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
self.gguf_writer.add_tokenizer_model("gpt2")
|
||||
@@ -2819,10 +2895,10 @@ class TextModel(ModelBase):
|
||||
self.gguf_writer.add_token_types(toktypes)
|
||||
# Special tokens
|
||||
# Note: Using <|endoftext|> (151329) for eot causes endless generation
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["[gMASK]"]) # ty: ignore[unresolved-attribute] # 151331
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"]) # ty: ignore[unresolved-attribute] # 151336
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute] # 151329
|
||||
special_vocab._set_special_token("eom", tokenizer.get_added_vocab()["<|observation|>"]) # ty: ignore[unresolved-attribute] # 151338
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["[gMASK]"]) # 151331
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"]) # 151336
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"]) # 151329
|
||||
special_vocab._set_special_token("eom", tokenizer.get_added_vocab()["<|observation|>"]) # 151338
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def _set_vocab_interns1(self):
|
||||
@@ -2831,16 +2907,17 @@ class TextModel(ModelBase):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
vocab = getattr(tokenizer, 'vocab', tokenizer.get_vocab()) # ty: ignore[unresolved-attribute]
|
||||
assert tokenizer is not None
|
||||
vocab = getattr(tokenizer, 'vocab', tokenizer.get_vocab())
|
||||
vocab_size = self.hparams.get("vocab_size", len(vocab))
|
||||
assert max(vocab.values()) < vocab_size
|
||||
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in vocab.items()}
|
||||
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
|
||||
added_vocab = tokenizer.get_added_vocab()
|
||||
|
||||
added_tokens_decoder = tokenizer.added_tokens_decoder # ty: ignore[unresolved-attribute]
|
||||
added_tokens_decoder = tokenizer.added_tokens_decoder
|
||||
|
||||
for i in range(vocab_size):
|
||||
if i not in reverse_vocab:
|
||||
@@ -2853,7 +2930,7 @@ class TextModel(ModelBase):
|
||||
# To avoid unexpected issues - we make sure to normalize non-normalized tokens
|
||||
if not added_tokens_decoder[i].normalized:
|
||||
previous_token = token
|
||||
token = tokenizer.decode(tokenizer.encode(token, add_special_tokens=False)) # ty: ignore[unresolved-attribute, invalid-assignment]
|
||||
token = tokenizer.decode(tokenizer.encode(token, add_special_tokens=False)) # ty: ignore[invalid-assignment]
|
||||
if previous_token != token:
|
||||
logger.info(f"{repr(previous_token)} is encoded and decoded back to {repr(token)} using AutoTokenizer")
|
||||
|
||||
@@ -3384,7 +3461,7 @@ def get_model_architecture(hparams: dict[str, Any], model_type: ModelType) -> st
|
||||
# Kimi-K3's text_config reports "KimiLinearForCausalLM", which is the older
|
||||
# Kimi-Linear-48B architecture and cannot load K3 (no attention residuals,
|
||||
# latent MoE, situ, ...). Route on the top-level architecture instead.
|
||||
if model_type == ModelType.TEXT and arch in ("NEOChatModel", "StepVLForConditionalGeneration", "Sarashina2VisionForCausalLM", "Exaone4_5_ForConditionalGeneration", "Step3p7ForConditionalGeneration", "KimiK3ForConditionalGeneration"):
|
||||
if model_type == ModelType.TEXT and arch in ("NEOChatModel", "StepVLForConditionalGeneration", "Sarashina2VisionForCausalLM", "Exaone4_5_ForConditionalGeneration", "Step3p7ForConditionalGeneration", "KimiK3ForConditionalGeneration", "KimiK31ForConditionalGeneration", "Nemotron3_5TranscribeForConditionalGeneration"):
|
||||
return arch
|
||||
|
||||
# if "architectures" is found in the sub-config, use that instead
|
||||
@@ -3392,6 +3469,9 @@ def get_model_architecture(hparams: dict[str, Any], model_type: ModelType) -> st
|
||||
arch = text_config["architectures"][0]
|
||||
elif model_type == ModelType.MMPROJ and vision_config.get("architectures") is not None:
|
||||
arch = vision_config["architectures"][0]
|
||||
# some models (llava_onevision2) only declare the architecture inside text_config
|
||||
if arch is None and text_config.get("architectures") is not None:
|
||||
arch = text_config["architectures"][0]
|
||||
if arch is None:
|
||||
raise ValueError("Failed to detect model architecture")
|
||||
return arch
|
||||
|
||||
+2
-1
@@ -60,7 +60,8 @@ class BeamModel(TextModel):
|
||||
# otherwise EOT is guessed from a hash-ordered scan
|
||||
from transformers import AutoTokenizer
|
||||
tok = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
ret_id = tok.convert_tokens_to_ids("<|return|>") # ty: ignore[unresolved-attribute]
|
||||
assert tok is not None
|
||||
ret_id = tok.convert_tokens_to_ids("<|return|>")
|
||||
if isinstance(ret_id, int) and ret_id >= 0:
|
||||
self.gguf_writer.add_eot_token_id(ret_id)
|
||||
|
||||
|
||||
+11
-10
@@ -132,6 +132,7 @@ class BertModel(TextModel):
|
||||
from base64 import b64decode
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
assert tokenizer is not None
|
||||
|
||||
with open(tokenizer_path, "r", encoding="utf-8") as fp:
|
||||
tokenizer_json = json.load(fp)
|
||||
@@ -140,13 +141,13 @@ class BertModel(TextModel):
|
||||
with open(tokenizer_config_path, "r", encoding="utf-8") as fp:
|
||||
tokenizer_config_json = json.load(fp)
|
||||
|
||||
add_prefix = tokenizer.add_prefix_space # ty: ignore[unresolved-attribute]
|
||||
remove_whitespaces = tokenizer.clean_up_tokenization_spaces # ty: ignore[unresolved-attribute]
|
||||
add_prefix = tokenizer.add_prefix_space
|
||||
remove_whitespaces = tokenizer.clean_up_tokenization_spaces
|
||||
precompiled_charsmap = b64decode(tokenizer_json["normalizer"]["precompiled_charsmap"])
|
||||
|
||||
vocab_size = max(self.hparams.get("vocab_size", 0), tokenizer.vocab_size) # ty: ignore[unresolved-attribute]
|
||||
vocab_size = max(self.hparams.get("vocab_size", 0), tokenizer.vocab_size)
|
||||
else:
|
||||
sentencepiece_model = model.ModelProto() # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
||||
sentencepiece_model = model.ModelProto() # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[missing-attribute]
|
||||
sentencepiece_model.ParseFromString(open(tokenizer_path, "rb").read())
|
||||
assert sentencepiece_model.trainer_spec.model_type == 1 # UNIGRAM
|
||||
|
||||
@@ -183,20 +184,20 @@ class BertModel(TextModel):
|
||||
scores[token_id] = score
|
||||
toktypes[token_id] = toktype
|
||||
else:
|
||||
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
|
||||
added_vocab = tokenizer.get_added_vocab()
|
||||
unk_token = tokenizer_config_json.get("unk_token")
|
||||
unk_token_id = added_vocab.get(unk_token, tokenizer_json["model"].get("unk_id", 3)) # ty: ignore[no-matching-overload]
|
||||
unk_token_id = added_vocab.get(unk_token, tokenizer_json["model"].get("unk_id", 3))
|
||||
|
||||
for token_id in range(tokenizer.vocab_size): # ty: ignore[unresolved-attribute]
|
||||
piece = tokenizer._convert_id_to_token(token_id) # ty: ignore[unresolved-attribute]
|
||||
if (piece := tokenizer._convert_id_to_token(token_id)) is not None: # ty: ignore[unresolved-attribute]
|
||||
for token_id in range(tokenizer.vocab_size):
|
||||
piece = tokenizer._convert_id_to_token(token_id)
|
||||
if (piece := tokenizer._convert_id_to_token(token_id)) is not None:
|
||||
text = piece.encode("utf-8")
|
||||
score = tokenizer_json["model"]["vocab"][token_id][1]
|
||||
|
||||
toktype = SentencePieceTokenTypes.NORMAL
|
||||
if token_id == unk_token_id:
|
||||
toktype = SentencePieceTokenTypes.UNKNOWN
|
||||
elif token_id in tokenizer.all_special_ids: # ty: ignore[unresolved-attribute]
|
||||
elif token_id in tokenizer.all_special_ids:
|
||||
toktype = SentencePieceTokenTypes.CONTROL
|
||||
elif token_id in added_vocab.values():
|
||||
toktype = SentencePieceTokenTypes.USER_DEFINED
|
||||
|
||||
+19
-16
@@ -22,12 +22,13 @@ class ChatGLMModel(TextModel):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(dir_model, trust_remote_code=True)
|
||||
vocab_size = hparams.get("padded_vocab_size", len(tokenizer.get_vocab())) # ty: ignore[unresolved-attribute]
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size # ty: ignore[unresolved-attribute]
|
||||
assert tokenizer is not None
|
||||
vocab_size = hparams.get("padded_vocab_size", len(tokenizer.get_vocab()))
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size
|
||||
role_special_tokens = ["<|system|>", "<|user|>", "<|assistant|>", "<|observation|>"]
|
||||
special_tokens = ["[MASK]", "[gMASK]", "[sMASK]", "sop", "eop"] + role_special_tokens
|
||||
for token_id in range(vocab_size):
|
||||
piece = tokenizer._convert_id_to_token(token_id) # ty: ignore[unresolved-attribute]
|
||||
piece = tokenizer._convert_id_to_token(token_id)
|
||||
if token_id == 0:
|
||||
piece = "<unk>"
|
||||
elif token_id == 1:
|
||||
@@ -35,17 +36,18 @@ class ChatGLMModel(TextModel):
|
||||
elif token_id == 2:
|
||||
piece = "<eos>"
|
||||
|
||||
text = piece.encode("utf-8") # ty: ignore[unresolved-attribute]
|
||||
assert isinstance(piece, str)
|
||||
text = piece.encode("utf-8")
|
||||
score = 0.0
|
||||
# Referencing the tokenizer Python implementation(https://huggingface.co/THUDM/chatglm3-6b/blob/main/tokenization_chatglm.py),
|
||||
# it is only valid if it is less than tokenizer.tokenizer.sp_model.vocab_size()
|
||||
if len(piece) != 0 and token_id < tokenizer.tokenizer.sp_model.vocab_size(): # ty: ignore[unresolved-attribute, invalid-argument-type]
|
||||
score = tokenizer.tokenizer.sp_model.get_score(token_id) # ty: ignore[unresolved-attribute]
|
||||
if len(piece) != 0 and token_id < tokenizer.tokenizer.sp_model.vocab_size():
|
||||
score = tokenizer.tokenizer.sp_model.get_score(token_id)
|
||||
|
||||
if token_id >= tokenizer.tokenizer.sp_model.vocab_size(): # ty: ignore[unresolved-attribute]
|
||||
if token_id >= tokenizer.tokenizer.sp_model.vocab_size():
|
||||
if piece in special_tokens:
|
||||
toktype = SentencePieceTokenTypes.CONTROL
|
||||
elif len(piece) == 0: # ty: ignore[invalid-argument-type]
|
||||
elif len(piece) == 0:
|
||||
text = f"[PAD{token_id}]".encode("utf-8")
|
||||
toktype = SentencePieceTokenTypes.UNUSED
|
||||
else:
|
||||
@@ -56,13 +58,13 @@ class ChatGLMModel(TextModel):
|
||||
continue
|
||||
|
||||
toktype = SentencePieceTokenTypes.NORMAL
|
||||
if tokenizer.tokenizer.sp_model.is_unknown(token_id): # ty: ignore[unresolved-attribute]
|
||||
if tokenizer.tokenizer.sp_model.is_unknown(token_id):
|
||||
toktype = SentencePieceTokenTypes.UNKNOWN
|
||||
elif tokenizer.tokenizer.sp_model.is_control(token_id): # ty: ignore[unresolved-attribute]
|
||||
elif tokenizer.tokenizer.sp_model.is_control(token_id):
|
||||
toktype = SentencePieceTokenTypes.CONTROL
|
||||
elif tokenizer.tokenizer.sp_model.is_unused(token_id): # ty: ignore[unresolved-attribute]
|
||||
elif tokenizer.tokenizer.sp_model.is_unused(token_id):
|
||||
toktype = SentencePieceTokenTypes.UNUSED
|
||||
elif tokenizer.tokenizer.sp_model.is_byte(token_id): # ty: ignore[unresolved-attribute]
|
||||
elif tokenizer.tokenizer.sp_model.is_byte(token_id):
|
||||
toktype = SentencePieceTokenTypes.BYTE
|
||||
|
||||
tokens.append(text)
|
||||
@@ -115,8 +117,9 @@ class ChatGLMModel(TextModel):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
vocab_size = hparams.get("padded_vocab_size",hparams["vocab_size"])
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size # ty: ignore[unresolved-attribute]
|
||||
assert max(tokenizer.get_vocab().values()) < vocab_size
|
||||
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
self.gguf_writer.add_tokenizer_model("gpt2")
|
||||
@@ -125,10 +128,10 @@ class ChatGLMModel(TextModel):
|
||||
self.gguf_writer.add_token_types(toktypes)
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
# only add special tokens when they were not already loaded from config.json
|
||||
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"])
|
||||
# this one is usually not in config.json anyway
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
# Chatterbox (ResembleAI) turbo and multilingual v3, from the raw checkpoints of the official repos
|
||||
# the checkpoints ship no config.json, the variant is detected from the talker file and the hparams come from the reference code
|
||||
# talker: text_emb and speech_emb are fused into one [text | speech] vocab, speech token i is <|speech_i|>
|
||||
# mmproj: the s3gen sidecar, the voice and conditioning encoders, the position and speech tables, and the conds.pt voice
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterable
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import ModelBase, TextModel, MmprojModel, LazyTorchTensor, gguf, logger
|
||||
from .llama import LlamaModel
|
||||
|
||||
# checkpoint file prefixes, the files are "<prefix>.safetensors"
|
||||
TURBO_TALKER = "t3_turbo_v1"
|
||||
MTL_TALKER = "t3_mtl23ls_v3"
|
||||
TURBO_S3GEN = "s3gen_meanflow"
|
||||
MTL_S3GEN = "s3gen_v3"
|
||||
VE = "ve"
|
||||
|
||||
# relative speech ids shared by both variants (start/stop_speech_token in the reference)
|
||||
SPEECH_BOS = 6561
|
||||
SPEECH_EOS = 6562
|
||||
|
||||
# sampling defaults of the reference, samplers it does not use are disabled so the common defaults do not apply
|
||||
TURBO_TOP_K = 1000
|
||||
MTL_TOP_K = 0
|
||||
TURBO_SAMPLING = {"min_p": 0.0, "top_p": 0.95, "temp": 0.8, "penalty_repeat": 1.2}
|
||||
MTL_SAMPLING = {"min_p": 0.05, "top_p": 1.0, "temp": 0.8, "penalty_repeat": 1.2}
|
||||
# the reference repetition penalty covers the whole generated history
|
||||
PENALTY_LAST_N = -1
|
||||
|
||||
|
||||
def _has_file(dir_model: Path, prefix: str) -> bool:
|
||||
return (dir_model / f"{prefix}.safetensors").is_file()
|
||||
|
||||
|
||||
def _is_turbo(dir_model: Path) -> bool:
|
||||
if _has_file(dir_model, TURBO_TALKER):
|
||||
return True
|
||||
if _has_file(dir_model, MTL_TALKER):
|
||||
return False
|
||||
raise FileNotFoundError(f"no chatterbox talker checkpoint in {dir_model}")
|
||||
|
||||
|
||||
# talker hparams of the reference (GPT2_MEDIUM_CONFIG / LLAMA_520M_CONFIG_DICT, T3Config)
|
||||
TURBO_TEXT_CONFIG = {
|
||||
"n_layer": 24, "n_embd": 1024, "n_inner": 4096, "n_head": 16, "n_ctx": 8196, "layer_norm_epsilon": 1e-05,
|
||||
"vocab_size": 50276, "speech_vocab_size": 6563,
|
||||
}
|
||||
MTL_TEXT_CONFIG = {
|
||||
"num_hidden_layers": 30, "hidden_size": 1024, "intermediate_size": 4096, "num_attention_heads": 16,
|
||||
"num_key_value_heads": 16, "head_dim": 64, "max_position_embeddings": 131072, "rms_norm_eps": 1e-05,
|
||||
"rope_theta": 500000.0,
|
||||
"rope_scaling": {"factor": 8.0, "high_freq_factor": 4.0, "low_freq_factor": 1.0, "original_max_position_embeddings": 8192},
|
||||
"vocab_size": 2454, "speech_vocab_size": 8194,
|
||||
}
|
||||
|
||||
|
||||
def _is_chatterbox_checkpoint(dir_model: Path) -> bool:
|
||||
return _has_file(dir_model, VE) and (_has_file(dir_model, TURBO_TALKER) or _has_file(dir_model, MTL_TALKER))
|
||||
|
||||
|
||||
@ModelBase.register_hparams_loader(_is_chatterbox_checkpoint)
|
||||
def _load_chatterbox_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
turbo = _is_turbo(dir_model)
|
||||
logger.info(f"gguf: detected Chatterbox {'turbo' if turbo else 'multilingual v3'} checkpoint")
|
||||
text_config = dict(TURBO_TEXT_CONFIG if turbo else MTL_TEXT_CONFIG)
|
||||
# the mmproj tensors are named explicitly, they do not go through the block tensor map
|
||||
return {"architectures": ["ChatterboxModel"], "text_config": text_config, "audio_config": {"num_hidden_layers": 0}}
|
||||
|
||||
|
||||
@ModelBase.register("ChatterboxModel")
|
||||
class ChatterboxTalkerModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.LLAMA # multilingual; the turbo constructor switches to GPT2
|
||||
|
||||
def __init__(self, dir_model: Path, *args, **kwargs):
|
||||
self.is_turbo = _is_turbo(dir_model)
|
||||
if self.is_turbo:
|
||||
self.model_arch = gguf.MODEL_ARCH.GPT2
|
||||
super().__init__(dir_model, *args, **kwargs)
|
||||
self._text_embd: Tensor | None = None
|
||||
self._speech_embd: Tensor | None = None
|
||||
self._text_head: Tensor | None = None
|
||||
self._speech_head: Tensor | None = None
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
return self.index_local_tensors(TURBO_TALKER if self.is_turbo else MTL_TALKER)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, _ = item
|
||||
# the reference feeds inputs_embeds, the transformer input embeddings are unused
|
||||
if name in ("tfmr.wte.weight", "tfmr.embed_tokens.weight"):
|
||||
return None
|
||||
if name.startswith(("cond_enc.", "text_pos_emb.", "speech_pos_emb.")):
|
||||
return None
|
||||
return super().filter_tensors(item)
|
||||
|
||||
def set_vocab(self):
|
||||
if self.is_turbo:
|
||||
self._set_vocab_turbo()
|
||||
else:
|
||||
self._set_vocab_mtl()
|
||||
|
||||
def _speech_token_names(self, n_speech: int) -> list[str]:
|
||||
return [f"<|speech_{i}|>" for i in range(n_speech)]
|
||||
|
||||
def _set_vocab_turbo(self):
|
||||
# stock GPT-2 BPE from the checkpoint dir, extended with the speech tokens
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
n_text = len(tokens)
|
||||
n_speech = self.hparams["speech_vocab_size"]
|
||||
speech = self._speech_token_names(n_speech)
|
||||
tokens += speech
|
||||
toktypes += [gguf.TokenType.CONTROL] * n_speech
|
||||
|
||||
with open(self.dir_model / "merges.txt", "r", encoding="utf-8") as f:
|
||||
merges = [line.rstrip("\n") for line in f if line.strip() and not line.startswith("#version")]
|
||||
|
||||
self._write_vocab(tokens, toktypes, merges, tokpre, n_text)
|
||||
|
||||
def _write_vocab(self, tokens: list[str], toktypes: list[int], merges: list[str], tokpre: str, n_text: int):
|
||||
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)
|
||||
self.gguf_writer.add_token_merges(merges)
|
||||
|
||||
self.gguf_writer.add_bos_token_id(n_text + SPEECH_BOS)
|
||||
self.gguf_writer.add_eos_token_id(n_text + SPEECH_EOS)
|
||||
self.gguf_writer.add_add_bos_token(False)
|
||||
self.gguf_writer.add_add_eos_token(False)
|
||||
|
||||
# the reference samples the speech head only
|
||||
self.gguf_writer.add_suppress_tokens(list(range(n_text)))
|
||||
|
||||
def _set_vocab_mtl(self):
|
||||
# the reference BPE is char-level, it is re-encoded to byte-level with extra merges that rebuild each multi-byte char
|
||||
# its lowercasing is folded into the embedding table, so the prompt needs no text preprocessing
|
||||
with open(self.dir_model / "mtl_tokenizer.json", "r", encoding="utf-8") as f:
|
||||
tok = json.load(f)
|
||||
|
||||
byte_map = gguf.vocab.bytes_to_unicode()
|
||||
|
||||
def enc(s: str) -> str:
|
||||
return "".join(byte_map[b] for b in s.encode("utf-8"))
|
||||
|
||||
n_text = self.hparams["vocab_size"]
|
||||
tokens: list[str] = [f"[unused_{i}]" for i in range(n_text)]
|
||||
toktypes = [int(gguf.TokenType.UNUSED)] * n_text
|
||||
char_merges: list[str] = []
|
||||
for t, i in tok["model"]["vocab"].items():
|
||||
if t == " ":
|
||||
# the reference substitutes [SPACE] before encoding, the raw space token is never used
|
||||
continue
|
||||
tokens[i] = enc(t)
|
||||
toktypes[i] = int(gguf.TokenType.NORMAL)
|
||||
if len(t) == 1 and len(t.encode("utf-8")) > 1:
|
||||
parts = [byte_map[b] for b in t.encode("utf-8")]
|
||||
for k in range(1, len(parts)):
|
||||
char_merges.append("".join(parts[:k]) + " " + parts[k])
|
||||
for entry in tok.get("added_tokens", []):
|
||||
tokens[entry["id"]] = entry["content"]
|
||||
toktypes[entry["id"]] = int(gguf.TokenType.CONTROL)
|
||||
|
||||
# raw spaces tokenize to [SPACE] directly
|
||||
space_id = tok["model"]["vocab"]["[SPACE]"]
|
||||
tokens[space_id] = enc(" ")
|
||||
toktypes[space_id] = int(gguf.TokenType.NORMAL)
|
||||
|
||||
n_speech = self.hparams["speech_vocab_size"]
|
||||
tokens += self._speech_token_names(n_speech)
|
||||
toktypes += [int(gguf.TokenType.CONTROL)] * n_speech
|
||||
|
||||
# chars are atomic in the reference, they must form before any of its merges
|
||||
merges = char_merges
|
||||
for m in tok["model"].get("merges", []):
|
||||
a, b = m if isinstance(m, list) else m.split(" ")
|
||||
merges.append(enc(a) + " " + enc(b))
|
||||
|
||||
self._write_vocab(tokens, toktypes, merges, "default", n_text)
|
||||
|
||||
def _mtl_case_fold_pairs(self) -> list[tuple[int, int]]:
|
||||
# (upper_id, lower_id) of the single-char vocab entries, for every script in the vocab
|
||||
with open(self.dir_model / "mtl_tokenizer.json", "r", encoding="utf-8") as f:
|
||||
vocab = json.load(f)["model"]["vocab"]
|
||||
pairs: list[tuple[int, int]] = []
|
||||
for t, i in vocab.items():
|
||||
if len(t) != 1:
|
||||
continue
|
||||
low = t.lower()
|
||||
if low != t and len(low) == 1 and low in vocab:
|
||||
pairs.append((i, vocab[low]))
|
||||
return pairs
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
sampling = TURBO_SAMPLING if self.is_turbo else MTL_SAMPLING
|
||||
self.gguf_writer.add_sampling_top_k(TURBO_TOP_K if self.is_turbo else MTL_TOP_K)
|
||||
self.gguf_writer.add_sampling_min_p(sampling["min_p"])
|
||||
self.gguf_writer.add_sampling_top_p(sampling["top_p"])
|
||||
self.gguf_writer.add_sampling_temp(sampling["temp"])
|
||||
self.gguf_writer.add_sampling_penalty_repeat(sampling["penalty_repeat"])
|
||||
self.gguf_writer.add_sampling_penalty_last_n(PENALTY_LAST_N)
|
||||
if not self.is_turbo:
|
||||
self.gguf_writer.add_classifier_free_guidance(True)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
if self.is_turbo:
|
||||
return
|
||||
# llama3 rope scaling baked into the rope_freqs factors tensor
|
||||
rp = self.hparams["rope_scaling"]
|
||||
dim = self.hparams["head_dim"]
|
||||
base = self.hparams["rope_theta"]
|
||||
freqs = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
|
||||
factor = rp["factor"]
|
||||
low_freq_wavelen = rp["original_max_position_embeddings"] / rp["low_freq_factor"]
|
||||
high_freq_wavelen = rp["original_max_position_embeddings"] / rp["high_freq_factor"]
|
||||
rope_factors = []
|
||||
for freq in freqs:
|
||||
wavelen = 2 * math.pi / freq
|
||||
if wavelen < high_freq_wavelen:
|
||||
rope_factors.append(1)
|
||||
elif wavelen > low_freq_wavelen:
|
||||
rope_factors.append(factor)
|
||||
else:
|
||||
smooth = (rp["original_max_position_embeddings"] / wavelen - rp["low_freq_factor"]) / (rp["high_freq_factor"] - rp["low_freq_factor"])
|
||||
rope_factors.append(1 / ((1 - smooth) / factor + smooth))
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.ROPE_FREQS), torch.tensor(rope_factors, dtype=torch.float32))
|
||||
|
||||
def _maybe_emit_fused(self) -> Iterable[tuple[str, Tensor]]:
|
||||
if self._text_embd is not None and self._speech_embd is not None:
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.TOKEN_EMBD),
|
||||
torch.cat([self._text_embd, self._speech_embd], dim=0))
|
||||
self._text_embd = None
|
||||
self._speech_embd = None
|
||||
if self._text_head is not None and self._speech_head is not None:
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.OUTPUT),
|
||||
torch.cat([self._text_head, self._speech_head], dim=0))
|
||||
self._text_head = None
|
||||
self._speech_head = None
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
# generate_extra_tensors output comes back through here with its final name
|
||||
if name.startswith("rope_freqs"):
|
||||
yield (name, data_torch)
|
||||
return
|
||||
# fused [text | speech] vocab: embeddings and output head
|
||||
if name == "text_emb.weight":
|
||||
if not self.is_turbo:
|
||||
# the reference lowercases its input, the uppercase rows take the lowercase ones
|
||||
fold = list(range(data_torch.shape[0]))
|
||||
for upper, lower in self._mtl_case_fold_pairs():
|
||||
fold[upper] = lower
|
||||
data_torch = data_torch[torch.tensor(fold)]
|
||||
self._text_embd = data_torch
|
||||
yield from self._maybe_emit_fused()
|
||||
return
|
||||
if name == "speech_emb.weight":
|
||||
self._speech_embd = data_torch
|
||||
yield from self._maybe_emit_fused()
|
||||
return
|
||||
if name == "text_head.weight":
|
||||
self._text_head = data_torch
|
||||
yield from self._maybe_emit_fused()
|
||||
return
|
||||
if name == "speech_head.weight":
|
||||
self._speech_head = data_torch
|
||||
yield from self._maybe_emit_fused()
|
||||
return
|
||||
if name == "speech_head.bias":
|
||||
# the text head has no bias, its zone is suppressed at sampling anyway
|
||||
yield (self.format_tensor_name(gguf.MODEL_TENSOR.OUTPUT, suffix=".bias"), F.pad(data_torch, (self.hparams["vocab_size"], 0)))
|
||||
return
|
||||
|
||||
assert name.startswith("tfmr.")
|
||||
name = name[len("tfmr."):]
|
||||
|
||||
if self.is_turbo:
|
||||
# HF GPT-2 layout: Conv1D style weights are stored transposed
|
||||
if name.endswith((".c_attn.weight", ".c_proj.weight", ".c_fc.weight")):
|
||||
data_torch = data_torch.transpose(1, 0)
|
||||
yield (self.map_tensor_name(name), data_torch)
|
||||
return
|
||||
|
||||
if name.endswith("q_proj.weight"):
|
||||
data_torch = LlamaModel.permute(data_torch, self.hparams["num_attention_heads"], None)
|
||||
if name.endswith("k_proj.weight"):
|
||||
data_torch = LlamaModel.permute(data_torch, self.hparams["num_attention_heads"], self.hparams["num_key_value_heads"])
|
||||
yield (self.map_tensor_name("model." + name), data_torch)
|
||||
|
||||
|
||||
@ModelBase.register("ChatterboxModel")
|
||||
class ChatterboxMmprojModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = True
|
||||
|
||||
def __init__(self, dir_model: Path, *args, **kwargs):
|
||||
self.is_turbo = _is_turbo(dir_model)
|
||||
super().__init__(dir_model, *args, **kwargs)
|
||||
self._wnorm_g: dict[str, Tensor] = {}
|
||||
self._wnorm_v: dict[str, Tensor] = {}
|
||||
|
||||
def get_audio_config(self) -> dict[str, Any] | None:
|
||||
return self.global_config.get("audio_config")
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
tensors: dict[str, Callable[[], Tensor]] = {}
|
||||
for prefix in (TURBO_S3GEN if self.is_turbo else MTL_S3GEN, VE, TURBO_TALKER if self.is_turbo else MTL_TALKER):
|
||||
tensors.update(self.index_local_tensors(prefix))
|
||||
return tensors
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
# the s3gen, ve and talker checkpoints have disjoint top-level names
|
||||
name, gen = item
|
||||
new_name: str | None = None
|
||||
# batchnorm bookkeeping (over the gguf name length cap) and the tokenizer dsp buffers are unused
|
||||
if name.endswith("num_batches_tracked") or name in ("tokenizer.window", "tokenizer._mel_filters"):
|
||||
return None
|
||||
for src, dst in (
|
||||
("flow.encoder.", "a.gen.fenc."),
|
||||
("flow.decoder.estimator.", "a.gen.est."),
|
||||
("mel2wav.", "a.gen.hift."),
|
||||
("speaker_encoder.", "a.spk."),
|
||||
("tokenizer.", "a.s3tok."),
|
||||
("flow.", "a.gen.flow."), # input_embedding, encoder_proj, spk_embed_affine_layer
|
||||
("lstm.", "a.ve.lstm."),
|
||||
("proj.", "a.ve.proj."),
|
||||
("cond_enc.", "a.cenc."),
|
||||
):
|
||||
if name.startswith(src):
|
||||
new_name = dst + name[len(src):]
|
||||
break
|
||||
if name == "text_pos_emb.emb.weight":
|
||||
new_name = "a.gen.t3.text_pos_emb"
|
||||
elif name == "speech_pos_emb.emb.weight":
|
||||
new_name = "a.gen.t3.speech_pos_emb"
|
||||
elif name == "speech_emb.weight":
|
||||
new_name = gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.A_GEN_CODE_OUT_EMBD] + ".weight"
|
||||
if new_name is None:
|
||||
return None
|
||||
return super().filter_tensors((new_name, gen))
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
self.gguf_writer.add_file_type(self.ftype)
|
||||
|
||||
# speaker encoder, the front-end hparams are hard-coded in tools/mtmd
|
||||
self.gguf_writer.add_clip_has_audio_encoder(True)
|
||||
self.gguf_writer.add_clip_audio_projector_type(gguf.VisionProjectorType.CHATTERBOX_SPKENC)
|
||||
self.gguf_writer.add_audio_projection_dim(80)
|
||||
self.gguf_writer.add_audio_num_mel_bins(80)
|
||||
self.gguf_writer.add_audio_block_count(0)
|
||||
self.gguf_writer.add_audio_embedding_length(192)
|
||||
self.gguf_writer.add_audio_head_count(1)
|
||||
self.gguf_writer.add_audio_feed_forward_length(192)
|
||||
self.gguf_writer.add_audio_attention_layernorm_eps(1e-5)
|
||||
|
||||
# audio generator (s3gen, chatterbox projector); the flow encoder shape
|
||||
self.gguf_writer.add_clip_has_gen_audio_encoder(True)
|
||||
self.gguf_writer.add_clip_gen_audio_projector_type(gguf.VisionProjectorType.CHATTERBOX)
|
||||
self.gguf_writer.add_gen_audio_projection_dim(80)
|
||||
self.gguf_writer.add_gen_audio_embedding_length(512)
|
||||
self.gguf_writer.add_gen_audio_feed_forward_length(2048)
|
||||
self.gguf_writer.add_gen_audio_block_count(6)
|
||||
self.gguf_writer.add_gen_audio_head_count(8)
|
||||
self.gguf_writer.add_gen_audio_attention_layernorm_eps(1e-5)
|
||||
|
||||
def _fuse_weight_norm(self, name: str, data_torch: Tensor) -> tuple[str, Tensor] | None:
|
||||
# torch weight_norm parametrization: weight = g * v / |v| over dims 1..n
|
||||
base = name.split(".parametrizations.weight.original")[0]
|
||||
if name.endswith("original0"):
|
||||
self._wnorm_g[base] = data_torch
|
||||
else:
|
||||
self._wnorm_v[base] = data_torch
|
||||
if base in self._wnorm_g and base in self._wnorm_v:
|
||||
g = self._wnorm_g.pop(base)
|
||||
v = self._wnorm_v.pop(base)
|
||||
norm = v.float().norm(dim=tuple(range(1, v.dim())), keepdim=True)
|
||||
return (base + ".weight", g.float() * v.float() / norm)
|
||||
return None
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if ".parametrizations.weight.original" in name:
|
||||
fused = self._fuse_weight_norm(name, data_torch)
|
||||
if fused is not None:
|
||||
yield fused
|
||||
return
|
||||
yield (name, data_torch)
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
del name, bid
|
||||
# tensors read on the host stay F32
|
||||
if new_name.startswith(("a.ve.", "a.cenc.", "voice.", "a.gen.t3.", "a.gen.hift.m_source.")):
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
# the conv ops have no BF16 kernels
|
||||
if n_dims >= 2 and new_name.endswith((".weight", ".weight_v")):
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
conds = torch.load(self.dir_model / "conds.pt", map_location="cpu", weights_only=True)
|
||||
t3c = conds["t3"] if isinstance(conds, dict) else conds.t3
|
||||
genc = conds["gen"] if isinstance(conds, dict) else conds.gen
|
||||
t3c = vars(t3c) if not isinstance(t3c, dict) else t3c
|
||||
genc = vars(genc) if not isinstance(genc, dict) else genc
|
||||
|
||||
def talker_tensor(name: str) -> Tensor:
|
||||
return LazyTorchTensor.to_eager(self.model_tensors[name]())
|
||||
|
||||
# the conds.pt voice: voice.default holds the talker conditioning rows, voice.default.ref_* the flow decoder reference
|
||||
# 1D tensors are written as F32, the speech ids stay exact
|
||||
yield ("voice.default.ref_tokens", genc["prompt_token"][0].float())
|
||||
yield ("voice.default.ref_feat", genc["prompt_feat"][0].float())
|
||||
yield ("voice.default.ref_spk", genc["embedding"][0].float())
|
||||
|
||||
spkr_w = talker_tensor("a.cenc.spkr_enc.weight").float()
|
||||
spkr_b = talker_tensor("a.cenc.spkr_enc.bias").float()
|
||||
spkr_row = spkr_w @ t3c["speaker_emb"][0].float() + spkr_b
|
||||
speech_emb = talker_tensor(self.format_tensor_name(gguf.MODEL_TENSOR.A_GEN_CODE_OUT_EMBD)).float()
|
||||
cond_tokens = t3c["cond_prompt_speech_tokens"][0]
|
||||
|
||||
if self.is_turbo:
|
||||
# turbo talker conditioning: [spkr, speech rows of the cond tokens]
|
||||
yield ("voice.default", torch.cat([spkr_row[None], speech_emb[cond_tokens]], dim=0))
|
||||
return
|
||||
|
||||
# multilingual talker conditioning [spkr, perceiver x32, emotion], from the reference perceiver over the cond speech tokens
|
||||
with torch.no_grad():
|
||||
speech_pos = talker_tensor("a.gen.t3.speech_pos_emb").float()
|
||||
pse = speech_emb[cond_tokens] + speech_pos[: cond_tokens.shape[0]]
|
||||
|
||||
ln_w = talker_tensor("a.cenc.perceiver.attn.norm.weight").float()
|
||||
ln_b = talker_tensor("a.cenc.perceiver.attn.norm.bias").float()
|
||||
wq = talker_tensor("a.cenc.perceiver.attn.to_q.weight").float()
|
||||
bq = talker_tensor("a.cenc.perceiver.attn.to_q.bias").float()
|
||||
wk = talker_tensor("a.cenc.perceiver.attn.to_k.weight").float()
|
||||
bk = talker_tensor("a.cenc.perceiver.attn.to_k.bias").float()
|
||||
wv = talker_tensor("a.cenc.perceiver.attn.to_v.weight").float()
|
||||
bv = talker_tensor("a.cenc.perceiver.attn.to_v.bias").float()
|
||||
wo = talker_tensor("a.cenc.perceiver.attn.proj_out.weight").float()
|
||||
bo = talker_tensor("a.cenc.perceiver.attn.proj_out.bias").float()
|
||||
query = talker_tensor("a.cenc.perceiver.pre_attention_query")[0].float()
|
||||
|
||||
n_head = 4
|
||||
n_e = query.shape[1]
|
||||
|
||||
def attn_block(x1: Tensor, x2: Tensor) -> Tensor:
|
||||
nx1 = F.layer_norm(x1, (n_e,), ln_w, ln_b)
|
||||
nx2 = F.layer_norm(x2, (n_e,), ln_w, ln_b)
|
||||
q = (nx1 @ wq.T + bq).view(-1, n_head, n_e // n_head).transpose(0, 1)
|
||||
k = (nx2 @ wk.T + bk).view(-1, n_head, n_e // n_head).transpose(0, 1)
|
||||
v = (nx2 @ wv.T + bv).view(-1, n_head, n_e // n_head).transpose(0, 1)
|
||||
ctx = F.scaled_dot_product_attention(q, k, v)
|
||||
ctx = ctx.transpose(0, 1).reshape(-1, n_e)
|
||||
return ctx @ wo.T + bo + x1
|
||||
|
||||
pre = attn_block(query, pse)
|
||||
p32 = attn_block(pre, pre)
|
||||
|
||||
emo = talker_tensor("a.cenc.emotion_adv_fc.weight").float()
|
||||
emo_row = emo[:, 0] * t3c["emotion_adv"].reshape(-1)[0]
|
||||
yield ("voice.default", torch.cat([spkr_row[None], p32, emo_row[None]], dim=0))
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
from .base import ModelBase, gguf
|
||||
from .qwen import _LinearAttentionVReorderBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
@ModelBase.register("CloverMoeModelLM")
|
||||
@ModelBase.example("Diffuspace/Clover-35B-A3B-Preview")
|
||||
class CloverMoeModel(_LinearAttentionVReorderBase):
|
||||
model_arch = gguf.MODEL_ARCH.CLOVER_MOE
|
||||
supports_mtp_export = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.no_mtp = True
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def set_vocab(self):
|
||||
self._set_vocab_gpt2()
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_expert_shared_count(self.hparams.get("num_shared_experts", 1))
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, _ = item
|
||||
if name.endswith((".conv_right_weight", ".bd_softmax_logit_scale", ".bd_softmax_history_bias", ".bd_softmax_history_c")):
|
||||
return None
|
||||
|
||||
return super().filter_tensors(item)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(".A_log") or (name.endswith("norm.weight") and not name.endswith("linear_attn.norm.weight")):
|
||||
data_torch = data_torch.float()
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
@@ -277,13 +277,14 @@ class DeepseekV2Model(TextModel):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
if tokpre == "kimi-k2":
|
||||
# Build merges list using the approach similar to HunYuanMoE
|
||||
merges = []
|
||||
vocab = {}
|
||||
mergeable_ranks = tokenizer.model._mergeable_ranks # ty: ignore[unresolved-attribute]
|
||||
mergeable_ranks = tokenizer.model._mergeable_ranks # ty: ignore[missing-union-attribute]
|
||||
for token, rank in mergeable_ranks.items():
|
||||
vocab[QwenModel.token_bytes_to_string(token)] = rank
|
||||
if len(token) == 1:
|
||||
@@ -294,7 +295,7 @@ class DeepseekV2Model(TextModel):
|
||||
|
||||
# Build token list
|
||||
vocab_size = self.hparams["vocab_size"]
|
||||
special_tokens = tokenizer.special_tokens # ty: ignore[unresolved-attribute]
|
||||
special_tokens = tokenizer.special_tokens
|
||||
reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
@@ -1078,7 +1079,7 @@ class DeepseekV4FlashVisionModel(MmprojModel):
|
||||
self.gguf_writer.add_vision_use_silu(True) # SwiGLU MLP
|
||||
self.gguf_writer.add_vision_projector_scale_factor(self.hparams_vision["downsample_ratio"])
|
||||
self.gguf_writer.add_vision_min_pixels(self.hparams_vision["min_pixels"])
|
||||
# hardcoded on the C++ side (see PROJECTOR_TYPE_DEEPSEEK4V in clip.cpp)
|
||||
# hardcoded on the C++ side (see PROJECTOR_TYPE_DEEPSEEK4V in tools/mtmd)
|
||||
# if future models use different values, add GGUF keys for those
|
||||
assert self.global_config["vision_max_n_token"] == 384
|
||||
assert self.global_config["vision_max_wh_ratio"] == 8
|
||||
|
||||
@@ -62,6 +62,7 @@ def engram_compressed_token_map(tokenizer_dir: Path) -> tuple[list[int], int]:
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(tokenizer_dir)
|
||||
assert tokenizer is not None
|
||||
sentinel = "\ue000"
|
||||
normalizer = normalizers.Sequence([
|
||||
normalizers.NFKC(),
|
||||
@@ -80,6 +81,7 @@ def engram_compressed_token_map(tokenizer_dir: Path) -> tuple[list[int], int]:
|
||||
text = backend.decode([token_id], skip_special_tokens=False)
|
||||
if "\ufffd" in text:
|
||||
key = backend.id_to_token(token_id)
|
||||
assert key is not None
|
||||
else:
|
||||
normalized = normalizer.normalize_str(text)
|
||||
key = normalized if normalized else text
|
||||
@@ -458,7 +460,7 @@ class DeepseekV41VisionModel(DeepseekV4FlashVisionModel):
|
||||
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
|
||||
# hardcoded in tools/mtmd (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"]
|
||||
|
||||
+2
-1
@@ -75,13 +75,14 @@ class Dots3NoteModel(DeepseekV2Model):
|
||||
def set_vocab(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
assert tokenizer is not None
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
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._set_special_token("eot", tokenizer.get_added_vocab()["<|endofassistant|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|endofassistant|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import ModelBase, MmprojModel, gguf
|
||||
from .qwen import Qwen3_5MoeTextModel
|
||||
|
||||
# Fish Audio Drama 3 (FishOmniModel)
|
||||
# text model: Qwen3.5-MoE backbone, samples the main token of each frame
|
||||
# mmproj: codebook tower and Fish DAC decoder (codec.pth)
|
||||
|
||||
_TOWER_SHARED = "audio_decoder_mtp_shared.layers."
|
||||
_TOWER_RESIDUAL = "audio_decoder_mtp.layers."
|
||||
|
||||
|
||||
@ModelBase.register("FishOmniModel")
|
||||
@ModelBase.example("fishaudio/drama-3")
|
||||
class Drama3TextModel(Qwen3_5MoeTextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35MOE
|
||||
no_mtp = True # the checkpoint has no MTP head
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
if item[0].startswith("audio_decoder"):
|
||||
return None
|
||||
return super().filter_tensors(item)
|
||||
|
||||
|
||||
@ModelBase.register("FishOmniModel")
|
||||
@ModelBase.example("fishaudio/drama-3")
|
||||
class Drama3MmprojModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = False
|
||||
|
||||
_LAYER_MAP = {
|
||||
"enorm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_ENORM, True),
|
||||
"hnorm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_HNORM, True),
|
||||
"eh_proj.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_EH_PROJ, False),
|
||||
"semantic_embedding.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_BLK_EMBD, False),
|
||||
"codebook_embedding.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_BLK_EMBD, False),
|
||||
"final_layernorm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_BLK_OUT_NORM, True),
|
||||
"transformer_layer.attention_norm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_NORM, True),
|
||||
"transformer_layer.ffn_norm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_FFN_NORM, True),
|
||||
"transformer_layer.attention.q_norm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_Q_NORM, True),
|
||||
"transformer_layer.attention.k_norm.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_K_NORM, True),
|
||||
"transformer_layer.attention.wo.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_ATTN_OUT, False),
|
||||
"transformer_layer.feed_forward.w1.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_FFN_GATE, False),
|
||||
"transformer_layer.feed_forward.w3.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_FFN_UP, False),
|
||||
"transformer_layer.feed_forward.w2.weight": (gguf.MODEL_TENSOR.A_GEN_CODE_FFN_DOWN, False),
|
||||
}
|
||||
|
||||
_heads: dict[int, Tensor]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._heads = {}
|
||||
|
||||
def get_audio_config(self) -> dict[str, Any] | None:
|
||||
cfg = dict(self.global_config["audio_decoder_config"])
|
||||
cfg["num_hidden_layers"] = cfg["shared_enrichment_layers"] + cfg["num_codebooks"] - 1
|
||||
return cfg
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
cfg = self.hparams
|
||||
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.DRAMA3_GEN)
|
||||
self.gguf_writer.add_gen_audio_projection_dim(self.n_embd_text)
|
||||
self.gguf_writer.add_gen_audio_embedding_length(cfg["dim"])
|
||||
self.gguf_writer.add_gen_audio_feed_forward_length(cfg["intermediate_size"])
|
||||
self.gguf_writer.add_gen_audio_block_count(cfg["num_hidden_layers"])
|
||||
self.gguf_writer.add_gen_audio_head_count(cfg["n_head"])
|
||||
self.gguf_writer.add_gen_audio_head_count_kv(cfg["n_local_heads"])
|
||||
self.gguf_writer.add_gen_audio_attention_layernorm_eps(cfg["norm_eps"])
|
||||
# other tower and codec hparams are hardcoded in tools/mtmd
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name = item[0]
|
||||
if name.startswith("audio_decoder") or name == "model.language_model.embed_tokens.weight":
|
||||
return item
|
||||
return None
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
# conv1d/conv1d_dw kernels must be F16, ggml_conv_1d(_dw) has no BF16 path
|
||||
if new_name.endswith(".weight") and (
|
||||
new_name in ("a.gen.wav.dac.entry.weight", "a.gen.wav.dac.post_conv.weight")
|
||||
or (".up.blk." in new_name and new_name.endswith(".dwconv.weight"))
|
||||
or (".dac.blk." in new_name and (new_name.endswith(".conv1.weight") or new_name.endswith(".conv2.weight")))
|
||||
):
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
# keep ConvTranspose1d kernels and folded codebook tables in F32
|
||||
if new_name.endswith(".conv.weight") and (".up.blk." in new_name or ".dac.blk." in new_name):
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
if new_name.startswith("a.gen.wav.quant."):
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
T = gguf.MODEL_TENSOR
|
||||
cfg = self.hparams
|
||||
|
||||
if name.startswith("a.gen.wav."):
|
||||
yield (name, data_torch)
|
||||
return
|
||||
|
||||
if name == "model.language_model.embed_tokens.weight":
|
||||
# semantic band of the backbone embeddings
|
||||
begin = self.global_config["semantic_start_token_id"]
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_OUT_EMBD), data_torch[begin:begin + cfg["vocab_size"]])
|
||||
return
|
||||
|
||||
if name == "audio_decoder.codebook_embeddings.weight":
|
||||
n_cb = cfg["num_codebooks"]
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_EMBD), data_torch.reshape(n_cb, -1, data_torch.shape[-1]))
|
||||
return
|
||||
|
||||
if name.startswith("audio_decoder_mtp.heads."):
|
||||
n_heads = cfg["num_codebooks"] - 1
|
||||
self._heads[int(name.split(".")[2])] = data_torch
|
||||
if len(self._heads) < n_heads:
|
||||
return
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_HEAD), torch.stack([self._heads.pop(i) for i in range(n_heads)]))
|
||||
return
|
||||
|
||||
for prefix, offset in ((_TOWER_SHARED, 0), (_TOWER_RESIDUAL, cfg["shared_enrichment_layers"])):
|
||||
if not name.startswith(prefix):
|
||||
continue
|
||||
idx, key = name[len(prefix):].split(".", 1)
|
||||
il = int(idx) + offset
|
||||
if key == "transformer_layer.attention.wqkv.weight":
|
||||
# rows are [query | gate | key | value]
|
||||
n_q = cfg["n_head"] * cfg["head_dim"]
|
||||
n_kv = cfg["n_local_heads"] * cfg["head_dim"]
|
||||
q, gate, k, v = data_torch.split([n_q, n_q, n_kv, n_kv], dim=0)
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_ATTN_Q, il), q)
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_ATTN_GATE, il), gate)
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_ATTN_K, il), k)
|
||||
yield (self.format_tensor_name(T.A_GEN_CODE_ATTN_V, il), v)
|
||||
return
|
||||
tensor, zero_centered = self._LAYER_MAP[key]
|
||||
if zero_centered:
|
||||
data_torch = data_torch.float() + 1
|
||||
yield (self.format_tensor_name(tensor, il), data_torch)
|
||||
return
|
||||
|
||||
raise ValueError(f"unexpected tensor {name}")
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
# only the DAC decoder, the encoder is for voice cloning (not supported yet)
|
||||
sd = torch.load(self.dir_model / "codec.pth", map_location="cpu", mmap=True, weights_only=True)
|
||||
sd = sd.get("state_dict", sd)
|
||||
T = gguf.MODEL_TENSOR
|
||||
|
||||
def get(name: str) -> Tensor:
|
||||
return sd[name].float()
|
||||
|
||||
def conv(prefix: str) -> Tensor:
|
||||
# fold weight norm
|
||||
if prefix + ".conv.weight" in sd:
|
||||
return get(prefix + ".conv.weight")
|
||||
g = get(prefix + ".conv.parametrizations.weight.original0")
|
||||
v = get(prefix + ".conv.parametrizations.weight.original1")
|
||||
return g * v / v.norm(dim=(1, 2), keepdim=True)
|
||||
|
||||
def quant_table(prefix: str) -> Tensor:
|
||||
# fold out_proj into the codebook, so decoding is a row lookup
|
||||
g = get(prefix + ".out_proj.weight_g")
|
||||
v = get(prefix + ".out_proj.weight_v")
|
||||
w = (g * v / v.norm(dim=(1, 2), keepdim=True)).squeeze(-1)
|
||||
return get(prefix + ".codebook.weight") @ w.T + get(prefix + ".out_proj.bias")
|
||||
|
||||
def snake(alpha: Tensor) -> tuple[Tensor, Tensor]:
|
||||
alpha = alpha.flatten()
|
||||
return alpha, 1.0 / (alpha + 1e-9)
|
||||
|
||||
q = "quantizer."
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_QUANT_FIRST_CB), quant_table(q + "semantic_quantizer.quantizers.0"))
|
||||
n_rest = sum(1 for k in sd if k.startswith(q + "quantizer.quantizers.") and k.endswith(".codebook.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_QUANT_REST_CB),
|
||||
torch.stack([quant_table(f"{q}quantizer.quantizers.{i}") for i in range(n_rest)]))
|
||||
|
||||
# post_module uses interleaved RoPE, permute q/k rows so NEOX RoPE gives the same result
|
||||
tfm = q + "post_module."
|
||||
n_head, d_head = 16, 64
|
||||
order = torch.cat([torch.arange(0, d_head, 2), torch.arange(1, d_head, 2)])
|
||||
|
||||
def neox_rows(w: Tensor) -> Tensor:
|
||||
return w.reshape(n_head, d_head, -1)[:, order].reshape(w.shape)
|
||||
|
||||
n_tfm = sum(1 for k in sd if k.startswith(tfm + "layers.") and k.endswith(".ffn_norm.weight"))
|
||||
for il in range(n_tfm):
|
||||
p = f"{tfm}layers.{il}."
|
||||
wq, wk, wv = get(p + "attention.wqkv.weight").chunk(3, dim=0)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_Q, il), neox_rows(wq))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_K, il), neox_rows(wk))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_V, il), wv)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_OUT, il), get(p + "attention.wo.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_NORM, il), get(p + "attention_norm.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_ATTN_SCALE, il), get(p + "attention_layer_scale.gamma"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_FFN_NORM, il), get(p + "ffn_norm.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_FFN_GATE, il), get(p + "feed_forward.w1.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_FFN_UP, il), get(p + "feed_forward.w3.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_FFN_DOWN, il), get(p + "feed_forward.w2.weight"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_FFN_SCALE, il), get(p + "ffn_layer_scale.gamma"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_TFM_OUTPUT_NORM), get(tfm + "norm.weight"))
|
||||
|
||||
# upsample: 2x (ConvTranspose1d + ConvNeXt)
|
||||
for il in range(2):
|
||||
p = f"{q}upsample.{il}."
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_UP_CONV, il, suffix=".weight"), conv(p + "0"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_UP_CONV, il, suffix=".bias"), get(p + "0.conv.bias"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_UP_DWCONV, il, suffix=".weight"), conv(p + "1.dwconv"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_UP_DWCONV, il, suffix=".bias"), get(p + "1.dwconv.conv.bias"))
|
||||
for key, tensor in (("norm", T.A_GEN_WAV_UP_NORM), ("pwconv1", T.A_GEN_WAV_UP_PW1), ("pwconv2", T.A_GEN_WAV_UP_PW2)):
|
||||
yield (self.format_tensor_name(tensor, il, suffix=".weight"), get(f"{p}1.{key}.weight"))
|
||||
yield (self.format_tensor_name(tensor, il, suffix=".bias"), get(f"{p}1.{key}.bias"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_UP_GAMMA, il, suffix=""), get(p + "1.gamma"))
|
||||
|
||||
# DAC: entry conv, 4x (Snake, ConvTranspose1d, 3 residual units), Snake, post conv
|
||||
d = "decoder.model."
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_ENTRY, suffix=".weight"), conv(d + "0"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_ENTRY, suffix=".bias"), get(d + "0.conv.bias"))
|
||||
for bid in range(4):
|
||||
p = f"{d}{bid + 1}.block."
|
||||
a, b = snake(get(p + "0.alpha"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_UP_SNAKE, bid, suffix=".alpha"), a)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_UP_SNAKE, bid, suffix=".beta"), b)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_UP_CONV, bid, suffix=".weight"), conv(p + "1"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_UP_CONV, bid, suffix=".bias"), get(p + "1.conv.bias"))
|
||||
for xid in range(3):
|
||||
r = f"{p}{xid + 2}.block."
|
||||
for act, tensor in (("0", T.A_GEN_WAV_DAC_RES_ACT1), ("2", T.A_GEN_WAV_DAC_RES_ACT2)):
|
||||
a, b = snake(get(r + act + ".alpha"))
|
||||
name = gguf.TENSOR_NAMES[tensor].format(bid=bid, xid=xid)
|
||||
yield (name + ".alpha", a)
|
||||
yield (name + ".beta", b)
|
||||
for cv, tensor in (("1", T.A_GEN_WAV_DAC_RES_CONV1), ("3", T.A_GEN_WAV_DAC_RES_CONV2)):
|
||||
name = gguf.TENSOR_NAMES[tensor].format(bid=bid, xid=xid)
|
||||
yield (name + ".weight", conv(r + cv))
|
||||
yield (name + ".bias", get(r + cv + ".conv.bias"))
|
||||
a, b = snake(get(d + "5.alpha"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_POST_SNAKE, suffix=".alpha"), a)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_POST_SNAKE, suffix=".beta"), b)
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_POST_CONV, suffix=".weight"), conv(d + "6"))
|
||||
yield (self.format_tensor_name(T.A_GEN_WAV_DAC_POST_CONV, suffix=".bias"), get(d + "6.conv.bias"))
|
||||
+3
-2
@@ -19,15 +19,16 @@ class DreamModel(TextModel):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
|
||||
vocab_dict = tokenizer.get_vocab() # ty: ignore[unresolved-attribute]
|
||||
vocab_dict = tokenizer.get_vocab()
|
||||
vocab_size = self.hparams.get("vocab_size", len(vocab_dict))
|
||||
assert max(vocab_dict.values()) < vocab_size
|
||||
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in vocab_dict.items()}
|
||||
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
|
||||
added_vocab = tokenizer.get_added_vocab()
|
||||
|
||||
for i in range(vocab_size):
|
||||
if i not in reverse_vocab:
|
||||
|
||||
+10
-8
@@ -31,6 +31,7 @@ class Glm4Model(TextModel):
|
||||
def set_vocab(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
self.gguf_writer.add_tokenizer_model("gpt2")
|
||||
@@ -38,10 +39,10 @@ class Glm4Model(TextModel):
|
||||
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._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|user|>"])
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
@@ -391,16 +392,17 @@ class SolarOpenModel(Glm4MoeModel):
|
||||
def set_vocab(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
assert tokenizer is not None
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
tokens, toktypes, tokpre = self.get_vocab_base()
|
||||
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._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|endoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<unk>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|startoftext|>"]) # ty: ignore[unresolved-attribute]
|
||||
special_vocab._set_special_token("eos", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("eot", tokenizer.get_added_vocab()["<|endoftext|>"])
|
||||
special_vocab._set_special_token("unk", tokenizer.get_added_vocab()["<unk>"])
|
||||
special_vocab._set_special_token("bos", tokenizer.get_added_vocab()["<|startoftext|>"])
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,16 @@ class GptOssModel(TextModel):
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
# Normalize erroneous GPT-OSS MXFP4 tensor naming.
|
||||
# https://github.com/p-e-w/heretic/pull/211#issuecomment-5094370390
|
||||
if "mlp.experts." in name:
|
||||
for proj in ("down_proj", "gate_up_proj"):
|
||||
name = name.replace(
|
||||
f"{proj}_blocks_blocks", f"{proj}_blocks"
|
||||
).replace(
|
||||
f"{proj}_blocks_scales", f"{proj}_scales"
|
||||
)
|
||||
|
||||
if "sinks" in name:
|
||||
name += ".weight"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class HunYuanMoEModel(TextModel):
|
||||
def set_vocab(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
|
||||
# 1. Get the pre-tokenizer identifier hash
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
@@ -31,7 +32,7 @@ class HunYuanMoEModel(TextModel):
|
||||
# 2. Reverse-engineer the merges list from mergeable_ranks
|
||||
merges = []
|
||||
vocab = {}
|
||||
mergeable_ranks = tokenizer.mergeable_ranks # ty: ignore[unresolved-attribute]
|
||||
mergeable_ranks = tokenizer.mergeable_ranks
|
||||
for token, rank in mergeable_ranks.items():
|
||||
vocab[QwenModel.token_bytes_to_string(token)] = rank
|
||||
if len(token) == 1:
|
||||
@@ -42,8 +43,8 @@ class HunYuanMoEModel(TextModel):
|
||||
|
||||
# 3. Generate the tokens and toktypes lists
|
||||
vocab_size = self.hparams["vocab_size"]
|
||||
assert tokenizer.vocab_size == vocab_size # ty: ignore[unresolved-attribute]
|
||||
special_tokens = tokenizer.special_tokens # ty: ignore[unresolved-attribute]
|
||||
assert tokenizer.vocab_size == vocab_size
|
||||
special_tokens = tokenizer.special_tokens
|
||||
reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
@@ -184,6 +185,7 @@ class HunYuanModel(TextModel):
|
||||
else:
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
|
||||
# 1. Get the pre-tokenizer identifier hash
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
@@ -191,7 +193,7 @@ class HunYuanModel(TextModel):
|
||||
# 2. Reverse-engineer the merges list from mergeable_ranks
|
||||
merges = []
|
||||
vocab = {}
|
||||
mergeable_ranks = tokenizer.mergeable_ranks # ty: ignore[unresolved-attribute]
|
||||
mergeable_ranks = tokenizer.mergeable_ranks
|
||||
for token, rank in mergeable_ranks.items():
|
||||
vocab[QwenModel.token_bytes_to_string(token)] = rank
|
||||
if len(token) == 1:
|
||||
@@ -202,8 +204,8 @@ class HunYuanModel(TextModel):
|
||||
|
||||
# 3. Generate the tokens and toktypes lists
|
||||
vocab_size = self.hparams["vocab_size"]
|
||||
assert tokenizer.vocab_size == vocab_size # ty: ignore[unresolved-attribute]
|
||||
special_tokens = tokenizer.special_tokens # ty: ignore[unresolved-attribute]
|
||||
assert tokenizer.vocab_size == vocab_size
|
||||
special_tokens = tokenizer.special_tokens
|
||||
reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
|
||||
@@ -36,7 +36,7 @@ class InternLM2Model(TextModel):
|
||||
logger.error(f'Error: Missing {tokenizer_path}')
|
||||
sys.exit(1)
|
||||
|
||||
sentencepiece_model = model.ModelProto() # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
||||
sentencepiece_model = model.ModelProto() # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[missing-attribute]
|
||||
sentencepiece_model.ParseFromString(open(tokenizer_path, "rb").read())
|
||||
add_prefix = sentencepiece_model.normalizer_spec.add_dummy_prefix
|
||||
|
||||
|
||||
@@ -343,3 +343,33 @@ class KimiK3Model(TextModel):
|
||||
return
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("KimiK31ForConditionalGeneration")
|
||||
@ModelBase.example("moonshotai/Kimi-K3.1-Pro")
|
||||
class KimiK31Model(KimiK3Model):
|
||||
"""
|
||||
Kimi-K3.1: K3 with the full-attention MLA layers replaced by gated DSA.
|
||||
|
||||
The tensor set is K3's plus a lightning indexer per full-attention layer,
|
||||
and `dsa_config` adds the attention.indexer.* metadata.
|
||||
"""
|
||||
|
||||
model_arch = gguf.MODEL_ARCH.KIMI_K31
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
# --- DSA indexer ---
|
||||
dsa = self.hparams["dsa_config"]
|
||||
dsa_layers = dsa["dsa_layers"]
|
||||
# the layer lists are 1-indexed, like linear_attn_config's
|
||||
assert sorted(dsa_layers) == sorted(self.hparams["linear_attn_config"]["full_attn_layers"]), \
|
||||
"every full-attention layer is expected to own a DSA indexer"
|
||||
self.gguf_writer.add_indexer_head_count(dsa["index_num_heads"])
|
||||
self.gguf_writer.add_indexer_key_length(dsa["index_head_dim"])
|
||||
self.gguf_writer.add_indexer_top_k(dsa["index_topk"])
|
||||
self.gguf_writer.add_indexer_types([(il + 1) in dsa_layers
|
||||
for il in range(self.hparams["num_hidden_layers"])])
|
||||
# eps of the indexer key LayerNorm; the config declares no separate value
|
||||
self.gguf_writer.add_layer_norm_eps(dsa.get("index_norm_eps", self.hparams["rms_norm_eps"]))
|
||||
|
||||
@@ -29,13 +29,14 @@ class KimiLinearModel(TextModel):
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
assert tokenizer is not None
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
|
||||
if tokpre == "kimi-k2":
|
||||
# Build merges list using the approach similar to HunYuanMoE
|
||||
merges = []
|
||||
vocab = {}
|
||||
mergeable_ranks = tokenizer.model._mergeable_ranks # ty: ignore[unresolved-attribute]
|
||||
mergeable_ranks = tokenizer.model._mergeable_ranks # ty: ignore[missing-union-attribute]
|
||||
for token, rank in mergeable_ranks.items():
|
||||
vocab[QwenModel.token_bytes_to_string(token)] = rank
|
||||
if len(token) == 1:
|
||||
@@ -45,7 +46,7 @@ class KimiLinearModel(TextModel):
|
||||
merges.append(' '.join(map(QwenModel.token_bytes_to_string, merged)))
|
||||
# Build token list
|
||||
vocab_size = self.hparams["vocab_size"]
|
||||
special_tokens = tokenizer.special_tokens # ty: ignore[unresolved-attribute]
|
||||
special_tokens = tokenizer.special_tokens
|
||||
reverse_vocab = {id_ : encoded_tok for encoded_tok, id_ in {**vocab, **special_tokens}.items()}
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
@@ -71,7 +72,7 @@ class KimiLinearModel(TextModel):
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=False)
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
# override eos id in config.json with tiktoken eos id
|
||||
self.gguf_writer.add_eos_token_id(tokenizer.eos_id) # ty: ignore[unresolved-attribute]
|
||||
self.gguf_writer.add_eos_token_id(tokenizer.eos_id)
|
||||
else:
|
||||
raise NotImplementedError(f"Deepseek pre-tokenizer {tokpre!r} is not supported yet!")
|
||||
|
||||
|
||||
+25
-3
@@ -57,6 +57,8 @@ class KimiVLModel(MmprojModel):
|
||||
class KimiK25Model(MmprojModel):
|
||||
"""Kimi-K2.5 with MoonViT3d vision encoder"""
|
||||
|
||||
projector_type = gguf.VisionProjectorType.KIMIK25
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -78,7 +80,7 @@ class KimiK25Model(MmprojModel):
|
||||
super().set_gguf_parameters()
|
||||
assert self.hparams_vision is not None
|
||||
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.KIMIK25)
|
||||
self.gguf_writer.add_clip_projector_type(self.projector_type)
|
||||
|
||||
# Position embedding parameters (for interpolation)
|
||||
self.gguf_writer.add_uint32("vision.pos_emb_height", self.hparams_vision.get("init_pos_emb_height", 64))
|
||||
@@ -87,7 +89,11 @@ class KimiK25Model(MmprojModel):
|
||||
|
||||
# Projector parameters
|
||||
self.gguf_writer.add_vision_use_gelu(self.hparams_vision.get("projector_hidden_act", "gelu") == "gelu")
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("projector_ln_eps", 1e-5))
|
||||
if self.hparams_vision.get("norm_type", "layernorm") == "rmsnorm":
|
||||
# nn.RMSNorm without eps uses finfo(float32).eps, also for bf16 inputs
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(torch.finfo(torch.float32).eps)
|
||||
else:
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("projector_ln_eps", 1e-5))
|
||||
self.gguf_writer.add_vision_projector_scale_factor(self.merge_kernel_size[0])
|
||||
|
||||
# Image size limits
|
||||
@@ -120,7 +126,7 @@ class KimiK25Model(MmprojModel):
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
assert self.hparams_vision is not None
|
||||
n_head = self.hparams_vision.get("num_attention_heads", 16)
|
||||
n_head = self.find_vparam(["num_attention_heads", "vt_num_attention_heads"], optional=True) or 16
|
||||
|
||||
# Permute Q/K weights/biases from interleaved to split RoPE format
|
||||
# This allows using build_rope_2d at runtime without post-permutation.
|
||||
@@ -171,3 +177,19 @@ class Glm5vModel(KimiK25Model):
|
||||
name = name.replace("mm_projector.linear_", "mm_projector.proj.linear_", 1)
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("KimiK31ForConditionalGeneration")
|
||||
@ModelBase.example("moonshotai/Kimi-K3.1-Pro")
|
||||
class KimiK31VisionModel(KimiK25Model):
|
||||
"""Kimi-K2.5 MoonViT3d with RMSNorm, a wider QKV and the patchmergerv2 projector"""
|
||||
|
||||
projector_type = gguf.VisionProjectorType.KIMIK31
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
assert self.hparams_vision is not None
|
||||
assert self.hparams_vision["norm_type"] == "rmsnorm"
|
||||
assert self.hparams_vision["mm_projector_type"] == "patchmergerv2"
|
||||
n_head = self.find_vparam(["vt_num_attention_heads"])
|
||||
self.gguf_writer.add_vision_head_dim(self.hparams_vision["qkv_hidden_size"] // n_head)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user