mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-20 11:47:28 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ba3b9fd3d | ||
|
|
5a9a34d888 | ||
|
|
39da130fcd | ||
|
|
4e201388c3 | ||
|
|
1ba568c5ad | ||
|
|
36140dbee2 | ||
|
|
df11702136 | ||
|
|
9df0b73c70 | ||
|
|
b38960f520 | ||
|
|
a01ce9ffc6 | ||
|
|
8115b4c31f | ||
|
|
119f68d7d4 | ||
|
|
b684498579 | ||
|
|
522f2b6eed | ||
|
|
83c2d012a2 | ||
|
|
13786fbb3a | ||
|
|
d80619397c | ||
|
|
704366d76f | ||
|
|
707616feb5 | ||
|
|
d8d92973c8 | ||
|
|
7879491cf4 | ||
|
|
8466395353 | ||
|
|
dd002b91f5 | ||
|
|
1d88cb05f1 | ||
|
|
3bb63cbcbf | ||
|
|
f711c8f303 | ||
|
|
921257f500 | ||
|
|
3ffb62935b | ||
|
|
7adf160c41 | ||
|
|
ab7ed0d4b5 | ||
|
|
4c3c5810f8 | ||
|
|
1b29621bac | ||
|
|
6fa4f6cd3e | ||
|
|
0ce8cd1141 | ||
|
|
f174553e16 | ||
|
|
7951f601e8 | ||
|
|
33d6135cbe | ||
|
|
098ce526b5 | ||
|
|
f3fb2fcdbc | ||
|
|
fda00f18cd | ||
|
|
4866db350d | ||
|
|
42b70032b1 | ||
|
|
4c1004c492 | ||
|
|
412a363c65 | ||
|
|
2b57148b08 | ||
|
|
8f97d82da3 | ||
|
|
27af58168a | ||
|
|
7db0d3d781 | ||
|
|
99db719bdf | ||
|
|
31288c9c66 | ||
|
|
fe8841a78a | ||
|
|
ff5a233767 | ||
|
|
0878f7c304 | ||
|
|
5aeac37a35 | ||
|
|
547208dca2 | ||
|
|
848bbda904 | ||
|
|
797de63b2f | ||
|
|
9d1878a435 | ||
|
|
c0fd786264 | ||
|
|
2744e48d1e | ||
|
|
10b8b75534 | ||
|
|
9d1e03616e | ||
|
|
f2f667b02d | ||
|
|
62318cd57d | ||
|
|
83f9389419 | ||
|
|
1268edac57 | ||
|
|
f74e2fa6a3 | ||
|
|
ea0c4c2c85 | ||
|
|
6420cf5dd5 | ||
|
|
27c7a769da | ||
|
|
2f2763db6e | ||
|
|
01fc3aae84 | ||
|
|
23f7aabc16 | ||
|
|
7182ef71e6 | ||
|
|
2a667032dc | ||
|
|
f3363593f4 | ||
|
|
ceefd16daa | ||
|
|
9a16f1dbe4 | ||
|
|
b84ea5cf5e | ||
|
|
a30fb4924d | ||
|
|
495c5805ce | ||
|
|
623c16393c | ||
|
|
13b8048920 | ||
|
|
0f4376268f | ||
|
|
3dbcb7fca5 | ||
|
|
c804754529 | ||
|
|
c0083e3052 | ||
|
|
13de11e78f | ||
|
|
88aaf325a7 | ||
|
|
a8d9a7e1e5 | ||
|
|
f12aaa344c | ||
|
|
5e03a1ff45 | ||
|
|
b797b9ca81 | ||
|
|
6afa9a3134 | ||
|
|
b6334e37e6 | ||
|
|
e707790c73 | ||
|
|
43d57fb8ca | ||
|
|
2851b3bcbf | ||
|
|
59d5fad7c1 | ||
|
|
e287bd8729 | ||
|
|
2f729cfa4c | ||
|
|
ba3c05f71d | ||
|
|
b76b13379d | ||
|
|
620d2225cc | ||
|
|
cd0a5b6a18 | ||
|
|
a11b6530b5 | ||
|
|
61ad80bf93 | ||
|
|
a6b91d8c5e | ||
|
|
ea736c1d5c | ||
|
|
b0d239164b | ||
|
|
c0cb53ac4e | ||
|
|
4ed1c8507e | ||
|
|
711780ffc7 | ||
|
|
fd55cb98fe | ||
|
|
3e4dfa8f39 | ||
|
|
08698c81ed | ||
|
|
82e1e2fd84 | ||
|
|
47f5a64ae1 | ||
|
|
e36df60470 | ||
|
|
9166ceaf16 | ||
|
|
2130ca8476 | ||
|
|
23f83e056d | ||
|
|
2ce20d22fd | ||
|
|
68ac765726 | ||
|
|
77c1f39929 | ||
|
|
733f57a999 | ||
|
|
5a638aedb7 | ||
|
|
6e66cb5d4e | ||
|
|
e467e5b5e5 | ||
|
|
1ddc262558 | ||
|
|
2639a3b4e0 | ||
|
|
843e7dcc15 | ||
|
|
3e390af18d | ||
|
|
a0e6dc7384 | ||
|
|
23deec0255 | ||
|
|
683bbc0dce | ||
|
|
529ac893e7 | ||
|
|
685f70894b | ||
|
|
c114bd4c58 | ||
|
|
5cdadfc326 | ||
|
|
62df1815bc | ||
|
|
37d6d653e4 |
@@ -145,7 +145,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
# ==============================================================================
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
ENTRYPOINT [ "/app/llama-cli" ]
|
||||
|
||||
@@ -157,7 +157,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
HEALTHCHECK --interval=5m CMD [ "curl", "-f", "http://localhost:8080/health" ]
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -116,7 +116,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -125,7 +125,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@ RUN npm ci
|
||||
COPY tools/ui/ ./
|
||||
RUN LLAMA_BUILD_NUMBER="$APP_VERSION" npm run build
|
||||
|
||||
FROM docker.io/intel/oneapi-toolkit:$ONEAPI_VERSION AS build
|
||||
FROM docker.io/intel/deep-learning-essentials:$ONEAPI_VERSION AS build
|
||||
|
||||
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 && \
|
||||
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
|
||||
@@ -45,7 +50,7 @@ RUN if [ "${GGML_SYCL_F16}" = "ON" ]; then \
|
||||
fi && \
|
||||
echo "Building with dynamic libs" && \
|
||||
cmake -B build -DGGML_NATIVE=OFF -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DLLAMA_BUILD_TESTS=OFF ${OPT_SYCL_F16} && \
|
||||
cmake --build build --config Release -j$(nproc)
|
||||
cmake --build build --config Release -j$((($(nproc)+1)/2))
|
||||
|
||||
RUN mkdir -p /app/lib && \
|
||||
find build -name "*.so*" -exec cp -P {} /app/lib \;
|
||||
@@ -102,7 +107,7 @@ RUN mkdir /tmp/neo/ && cd /tmp/neo/ \
|
||||
&& dpkg --install *.deb
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libgomp1 curl ffmpeg \
|
||||
&& apt-get install -y libgomp1 curl ffmpeg openssl libssl-dev \
|
||||
&& apt autoremove -y \
|
||||
&& apt clean -y \
|
||||
&& rm -rf /tmp/* /var/tmp/* \
|
||||
@@ -141,7 +146,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/lib/ /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -154,7 +159,7 @@ ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/lib/ /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -132,7 +132,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -138,7 +138,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -119,7 +119,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ ENTRYPOINT ["/app/tools.sh"]
|
||||
### Light, CLI only
|
||||
FROM base AS light
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-cli /app/full/llama-completion /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -109,7 +109,7 @@ FROM base AS server
|
||||
ENV LLAMA_ARG_HOST=0.0.0.0
|
||||
ENV LLAMA_ARG_PORT=8080
|
||||
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app
|
||||
COPY --from=build /app/full/llama /app/full/llama-server /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ exclude =
|
||||
# Do not traverse examples and tools
|
||||
examples,
|
||||
tools,
|
||||
# Do not traverse .venv directories
|
||||
.venv*,
|
||||
# Do not include package initializers
|
||||
__init__.py,
|
||||
# No need to traverse our git directory
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
android-ndk-snapdragon:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
container:
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.7'
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
linux-iot-snapdragon:
|
||||
runs-on: ubuntu-24.04 # previously ubuntu-latest
|
||||
container:
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.7'
|
||||
image: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -104,11 +104,11 @@ jobs:
|
||||
name: Test on QDC Device (${{ matrix.device }})
|
||||
needs: [android-ndk-snapdragon, linux-iot-snapdragon]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 180
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
device: [SM8750, SM8850, QCS9075M]
|
||||
device: [SM8850, QCS9075M]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
--pkg-dir pkg-snapdragon/llama.cpp \
|
||||
--model-url "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_0.gguf" \
|
||||
--device ${{ matrix.device }} \
|
||||
${{ startsWith(matrix.device, 'QCS') && '--retries 2 --retry-delay 300' || '' }}
|
||||
${{ startsWith(matrix.device, 'QCS') && '--job-timeout 5400 --retries 2 --retry-delay 300' || '' }}
|
||||
env:
|
||||
QDC_API_KEY: ${{ secrets.QDC_API_KEY }}
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ on:
|
||||
paths: [
|
||||
'.github/workflows/build-ibm.yml',
|
||||
'ggml/src/ggml-cpu/**',
|
||||
'ggml/src/ggml-zdnn/**'
|
||||
'ggml/src/ggml-zdnn/**',
|
||||
'ggml/src/ggml-quants.c',
|
||||
'src/llama-quant.cpp',
|
||||
'gguf-py/gguf/scripts/gguf_convert_endian.py',
|
||||
'tests/test-quantize-be.sh'
|
||||
]
|
||||
|
||||
cache-mode: none
|
||||
@@ -105,6 +109,11 @@ jobs:
|
||||
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
|
||||
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
|
||||
|
||||
- name: Test quantize (s390x)
|
||||
id: quantize_test_s390x
|
||||
run: |
|
||||
./tests/test-quantize-be.sh ./build/bin/llama-quantize ./build/bin/llama-perplexity 2>&1
|
||||
|
||||
ubuntu-26-zdnn-s390x:
|
||||
name: ubuntu-26-zdnn-s390x
|
||||
runs-on: ubuntu-24.04-s390x
|
||||
|
||||
@@ -67,8 +67,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd /tmp
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/5996e26b-f48a-42b1-8db0-b002ad0bd8d7/intel-oneapi-toolkit-2026.1.1.33_offline.sh -O intel-oneapi-toolkit_offline.sh
|
||||
sudo bash intel-oneapi-toolkit_offline.sh -s -a --silent --eula accept
|
||||
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
|
||||
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
|
||||
echo "Install oneDNN components"
|
||||
sudo bash intel-onednn_offline.sh -s -a --silent --eula accept
|
||||
|
||||
- name: Install Level Zero SDK
|
||||
shell: bash
|
||||
@@ -79,6 +86,12 @@ jobs:
|
||||
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
|
||||
sudo apt-get install -y ./libze1.deb ./libze-dev.deb
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
@@ -104,7 +117,6 @@ jobs:
|
||||
-DGGML_SYCL=ON \
|
||||
-DCMAKE_C_COMPILER=icx \
|
||||
-DCMAKE_CXX_COMPILER=icpx \
|
||||
-DLLAMA_OPENSSL=OFF \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_SYCL_F16=${{ matrix.fp16 }} \
|
||||
-DGGML_SYCL_SUPPORT_LEVEL_ZERO_API=ON \
|
||||
@@ -138,8 +150,10 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
WINDOWS_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/0cb67a0d-67f6-410b-868b-f4a0a17ff0cf/intel-oneapi-toolkit-2026.1.1.32_offline.exe
|
||||
WINDOWS_DPCPP_MKL: intel.oneapi.win.cpp-dpcpp-common:intel.oneapi.win.mkl.devel:intel.oneapi.win.dnnl:intel.oneapi.win.tbb.devel
|
||||
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_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
|
||||
ONEAPI_ROOT: "C:/Program Files (x86)/Intel/oneAPI"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
@@ -151,7 +165,8 @@ jobs:
|
||||
- name: Download & Install oneAPI
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/install-oneapi.bat $WINDOWS_BASEKIT_URL $WINDOWS_DPCPP_MKL
|
||||
scripts/install-oneapi.bat $WINDOWS_DLE_URL $WINDOWS_DPCPP_MKL
|
||||
scripts/install-oneapi.bat $WINDOWS_ONEDNN_URL $WINDOWS_DNNL
|
||||
|
||||
- name: Install Level Zero SDK
|
||||
shell: pwsh
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
name: CI (wasi)
|
||||
|
||||
on:
|
||||
workflow_dispatch: # allows manual triggering
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths: [
|
||||
'.github/workflows/build-wasi.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
'**/*.hpp',
|
||||
'**/*.c',
|
||||
'**/*.cpp',
|
||||
'**/*.wgsl',
|
||||
'**/*.tmpl',
|
||||
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
|
||||
]
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths: [
|
||||
'.github/workflows/build-wasi.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
'**/*.hpp',
|
||||
'**/*.c',
|
||||
'**/*.cpp',
|
||||
'**/*.wgsl',
|
||||
'**/*.tmpl',
|
||||
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
|
||||
]
|
||||
|
||||
cache-mode: none
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
WASI_SDK_VERSION: 34
|
||||
DAWN_VERSION: v20260908.214631
|
||||
DAWN_COMMIT: 94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655
|
||||
WASI_WEBGPU_HEADERS_COMMIT: 917765edd7cb8f8cf95bda1217514c78f71b582b
|
||||
|
||||
jobs:
|
||||
ubuntu-webgpu:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: Install wasi-sdk
|
||||
uses: bytecodealliance/setup-wasi-sdk-action@77d09f3df1e8da344d8403cf8631af8977938b4c # v1.0.0
|
||||
with:
|
||||
version: ${{ env.WASI_SDK_VERSION }}
|
||||
add-to-path: false
|
||||
|
||||
- name: wasi:webgpu Dependency
|
||||
run: |
|
||||
mkdir -p wasi-webgpu/dawn-headers
|
||||
curl -fL "https://github.com/google/dawn/releases/download/${DAWN_VERSION}/dawn-headers-${DAWN_COMMIT}.tar.gz" |
|
||||
tar -xz -C wasi-webgpu/dawn-headers --strip-components=1
|
||||
|
||||
git init -q wasi-webgpu/wasi-webgpu-headers
|
||||
cd wasi-webgpu/wasi-webgpu-headers
|
||||
git fetch -q --depth 1 https://github.com/wasi-gfx/wasi-webgpu-headers.git ${WASI_WEBGPU_HEADERS_COMMIT}
|
||||
git checkout -q FETCH_HEAD
|
||||
git submodule update -q --init --depth 1
|
||||
cd ../..
|
||||
|
||||
# ggml-webgpu does find_package(Dawn) and links dawn::webgpu_dawn. There is no Dawn on WASI,
|
||||
# so this target instead compiles wasi-webgpu-headers (webgpu.h over wasi:webgpu imports)
|
||||
# and exposes Dawn's webgpu_cpp.h on top of it.
|
||||
cat > wasi-webgpu/DawnConfig.cmake << 'EOF'
|
||||
set(_wasi_webgpu "${CMAKE_CURRENT_LIST_DIR}/wasi-webgpu-headers")
|
||||
add_library(dawn::webgpu_dawn INTERFACE IMPORTED)
|
||||
set_target_properties(dawn::webgpu_dawn PROPERTIES
|
||||
INTERFACE_SOURCES "${_wasi_webgpu}/webgpu.c;${_wasi_webgpu}/async_futures.c;${_wasi_webgpu}/imports.c;${_wasi_webgpu}/imports_component_type.o"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/dawn-headers/include/webgpu_upstream;${_wasi_webgpu}")
|
||||
EOF
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasi
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
# llama-common does not build for WASI, so the executables are linked by hand
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
WASI_FLAGS="-msimd128 -D_WASI_EMULATED_SIGNAL -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
|
||||
cmake -B build-wasi \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk-p3.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_FLAGS="${WASI_FLAGS}" \
|
||||
-DCMAKE_CXX_FLAGS="${WASI_FLAGS}" \
|
||||
-DCMAKE_HAVE_LIBC_PTHREAD=1 \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_OPENMP=OFF \
|
||||
-DGGML_WEBGPU=ON \
|
||||
-DDawn_DIR=$PWD/wasi-webgpu \
|
||||
-DLLAMA_BUILD_COMMON=OFF
|
||||
|
||||
time cmake --build build-wasi --config Release --target llama -j $(nproc)
|
||||
|
||||
LIBS="build-wasi/ggml/src/libggml.a build-wasi/ggml/src/ggml-webgpu/libggml-webgpu.a build-wasi/ggml/src/libggml-cpu.a build-wasi/ggml/src/libggml-base.a"
|
||||
LDFLAGS="-lunwind -ldl -Wl,-z,stack-size=8388608"
|
||||
CXX="${WASI_SDK_PATH}/bin/clang++ --target=wasm32-wasip3 -O3 -std=c++17 ${WASI_FLAGS} -I include -I ggml/include"
|
||||
|
||||
$CXX examples/simple/simple.cpp build-wasi/src/libllama.a $LIBS $LDFLAGS -o build-wasi/llama-simple.wasm
|
||||
$CXX tests/test-backend-ops.cpp $LIBS $LDFLAGS -o build-wasi/test-backend-ops.wasm
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04-arm-wasi
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
@@ -7,15 +7,7 @@ on:
|
||||
- master
|
||||
paths: [
|
||||
'.github/workflows/build-webgpu.yml',
|
||||
'**/CMakeLists.txt',
|
||||
'**/.cmake',
|
||||
'**/*.h',
|
||||
'**/*.hpp',
|
||||
'**/*.c',
|
||||
'**/*.cpp',
|
||||
'**/*.wgsl',
|
||||
'**/*.tmpl',
|
||||
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
|
||||
'ggml/src/ggml-webgpu/**'
|
||||
]
|
||||
|
||||
pull_request:
|
||||
@@ -63,135 +55,3 @@ jobs:
|
||||
-type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' \) \
|
||||
-print0 |
|
||||
xargs -0 clang-format-22 --dry-run --Werror
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: Dawn Dependency
|
||||
id: dawn-depends
|
||||
run: |
|
||||
DAWN_VERSION="v20260908.214631"
|
||||
DAWN_OWNER="google"
|
||||
DAWN_REPO="dawn"
|
||||
DAWN_ASSET_NAME="Dawn-94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655-macos-latest-Release"
|
||||
echo "Fetching release asset from https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
|
||||
curl -L -o artifact.tar.gz \
|
||||
"https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
|
||||
mkdir dawn
|
||||
tar -xvf artifact.tar.gz -C dawn --strip-components=1
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: webgpu-macos-latest
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
export CMAKE_PREFIX_PATH=dawn
|
||||
cmake -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DGGML_WEBGPU=ON -DGGML_METAL=OFF -DGGML_BLAS=OFF
|
||||
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: webgpu-macos-latest
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
run: |
|
||||
cd build
|
||||
ctest -L main --verbose --timeout 900
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
restore: false
|
||||
save: false
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:kisak/kisak-mesa
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y build-essential mesa-vulkan-drivers \
|
||||
libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev libssl-dev
|
||||
|
||||
- name: Dawn Dependency
|
||||
id: dawn-depends
|
||||
run: |
|
||||
sudo apt-get install -y libxrandr-dev libxinerama-dev libxcursor-dev mesa-common-dev libx11-xcb-dev libxi-dev
|
||||
DAWN_VERSION="v20260908.214631"
|
||||
DAWN_OWNER="google"
|
||||
DAWN_REPO="dawn"
|
||||
DAWN_ASSET_NAME="Dawn-94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655-ubuntu-latest-Release"
|
||||
echo "Fetching release asset from https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
|
||||
curl -L -o artifact.tar.gz \
|
||||
"https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
|
||||
mkdir dawn
|
||||
tar -xvf artifact.tar.gz -C dawn --strip-components=1
|
||||
|
||||
- name: ccache-buckets-restore
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04
|
||||
folder: llama.cpp
|
||||
hf_bucket: ggml-org/cache
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
export Dawn_DIR=dawn/lib64/cmake/Dawn
|
||||
cmake -B build \
|
||||
-DGGML_WEBGPU=ON
|
||||
time cmake --build build --config Release -j $(nproc)
|
||||
|
||||
- name: ccache-buckets-save
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: ./.github/actions/ccache-buckets
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
|
||||
with:
|
||||
key: webgpu-ubuntu-24.04
|
||||
folder: llama.cpp
|
||||
evict-old-files: 1d
|
||||
hf_bucket: ggml-org/cache
|
||||
save: true
|
||||
|
||||
- name: Test
|
||||
id: cmake_test
|
||||
run: |
|
||||
cd build
|
||||
ctest -L main --verbose --timeout 900
|
||||
|
||||
@@ -61,8 +61,17 @@ jobs:
|
||||
RELEASE_BRANCH: ${{ github.ref_name }}
|
||||
SKIP_APIABI_CHECK: ${{ github.event.inputs.skip_apiabi_check }}
|
||||
APIABI_COMPARE_TAG: ${{ github.event.inputs.apiabi_compare_tag }}
|
||||
APIABI_REPORT_DIR: ${{ runner.temp }}/apiabi-reports
|
||||
REQUIRE_DOCKER: ${{ github.event.inputs.require_docker }}
|
||||
|
||||
- name: Upload API/ABI reports
|
||||
if: ${{ always() && github.event.inputs.skip_apiabi_check != 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: apiabi-reports
|
||||
path: ${{ runner.temp }}/apiabi-reports
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Create release tag
|
||||
if: ${{ github.event.inputs.dry_run == 'false' }}
|
||||
run: |
|
||||
|
||||
@@ -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.84
|
||||
pip-install: -r requirements/requirements-all.txt ty==0.0.88
|
||||
# - name: Type-check with Pyright
|
||||
# uses: jakebailey/pyright-action@v2
|
||||
# with:
|
||||
|
||||
@@ -35,7 +35,7 @@ permissions:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON"
|
||||
CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DLLAMA_CONNECT=ON"
|
||||
|
||||
# note: run this workflow one at a time for better cache reuse
|
||||
concurrency:
|
||||
@@ -547,7 +547,7 @@ jobs:
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.7'
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -600,7 +600,7 @@ jobs:
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.7'
|
||||
container: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -1300,8 +1300,11 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
WINDOWS_BASEKIT_URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/0cb67a0d-67f6-410b-868b-f4a0a17ff0cf/intel-oneapi-toolkit-2026.1.1.32_offline.exe
|
||||
WINDOWS_DPCPP_MKL: intel.oneapi.win.cpp-dpcpp-common:intel.oneapi.win.mkl.devel:intel.oneapi.win.dnnl:intel.oneapi.win.tbb.devel
|
||||
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_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
|
||||
ONEAPI_ROOT: "C:/Program Files (x86)/Intel/oneAPI"
|
||||
ONEAPI_INSTALLER_VERSION: "2026.1"
|
||||
@@ -1314,7 +1317,8 @@ jobs:
|
||||
- name: Download & Install oneAPI
|
||||
shell: bash
|
||||
run: |
|
||||
scripts/install-oneapi.bat $WINDOWS_BASEKIT_URL $WINDOWS_DPCPP_MKL
|
||||
scripts/install-oneapi.bat $WINDOWS_DLE_URL $WINDOWS_DPCPP_MKL
|
||||
scripts/install-oneapi.bat $WINDOWS_ONEDNN_URL $WINDOWS_DNNL
|
||||
|
||||
- name: Install Level Zero SDK
|
||||
shell: pwsh
|
||||
@@ -1425,8 +1429,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd /tmp
|
||||
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/5996e26b-f48a-42b1-8db0-b002ad0bd8d7/intel-oneapi-toolkit-2026.1.1.33_offline.sh -O intel-oneapi-toolkit_offline.sh
|
||||
sudo bash intel-oneapi-toolkit_offline.sh -s -a --silent --eula accept
|
||||
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
|
||||
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
|
||||
echo "Install oneDNN components"
|
||||
sudo bash intel-onednn_offline.sh -s -a --silent --eula accept
|
||||
|
||||
- name: Install Level Zero SDK
|
||||
shell: bash
|
||||
@@ -1442,6 +1453,12 @@ jobs:
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssl-dev
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.24
|
||||
with:
|
||||
@@ -1460,8 +1477,8 @@ jobs:
|
||||
-DCMAKE_CXX_COMPILER=icpx \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DLLAMA_OPENSSL=OFF \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DLLAMA_CONNECT=ON \
|
||||
-DGGML_SYCL_F16=${{ matrix.fp16 }}
|
||||
time cmake --build build --config Release -j $(nproc)
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
Project-specific instructions:
|
||||
|
||||
## Coding
|
||||
|
||||
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
|
||||
@@ -1,33 +0,0 @@
|
||||
You are a coding agent. Here are some very important rules that you must follow:
|
||||
|
||||
General:
|
||||
- Be very precise and concise when writing code, comments, explanations, etc.
|
||||
- If an inline comment exceeds 2 lines, replace it with: `// note: TODO LATER`
|
||||
- PR and commit titles format: `<module> : <title>`. Lookup recents for examples
|
||||
- Don't try to build or run the code unless you are explicitly asked to do so
|
||||
- Use the `gh` CLI tool when querying PRs, issues, or other GitHub resources
|
||||
- When [MODEL] is needed, first try to get it from the `PI_MODEL_NAME` env var before asking the user
|
||||
- Never read the `AGENTS.md` file
|
||||
|
||||
Coding:
|
||||
- When in doubt, always refer to the CONTRIBUTING.md file of the project
|
||||
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
|
||||
- When referencing issues or PRs in comments, use the format:
|
||||
- C/C++ code: `// ref: <url>`
|
||||
- Other (CMake, etc.): `# ref: <url>`
|
||||
|
||||
Pull requests (PRs):
|
||||
- New branch names are prefixed with "gg/"
|
||||
- Before opening a pull request, ask the user to confirm the description
|
||||
- Don't explicitly wrap lines in the PR description (each paragraph and bullet is a single line)
|
||||
- When creating a pull request, look for the repository's PR template and follow it
|
||||
- For the AI usage disclosure section, write "YES. pi:llama.cpp/[MODEL]"
|
||||
- If `PI_MODEL_NAME` env var is not set, ask the user to tell you what model was used and write it in place of [MODEL]
|
||||
- Always create the pull requests in draft mode
|
||||
- Never reply to review comments or post comments on issues/PRs without explicit permission from the user
|
||||
|
||||
Commits:
|
||||
- On every commit that you make, include a "Assisted-by: pi:llama.cpp/[MODEL]" tag
|
||||
- Do not explicitly set the git author in commits - rely on the default git config
|
||||
- Always use `--no-gpg-sign` when committing
|
||||
- Never `git push` without explicit confirmation from the user
|
||||
@@ -246,3 +246,6 @@ Chat template and parser:
|
||||
- [PEG parser](docs/development/parsing.md) - alternative to regex that llama.cpp uses to parse model's output
|
||||
- [Auto parser](docs/autoparser.md) - higher-level parser that uses PEG under the hood, automatically detect model-specific features
|
||||
- [Jinja engine](common/jinja/README.md)
|
||||
|
||||
Testing:
|
||||
- [Slice model](skills/slice-model/SKILL.md) - build a small slice (few layers/experts) of a big HF model without downloading the full weights, to test conversion and inference
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@ include(CheckIncludeFileCXX)
|
||||
|
||||
### llama.cpp version
|
||||
set(LLAMA_VERSION_MAJOR 0)
|
||||
set(LLAMA_VERSION_MINOR 6)
|
||||
set(LLAMA_VERSION_MINOR 7)
|
||||
set(LLAMA_VERSION_PATCH 0)
|
||||
set(LLAMA_VERSION_BASE "${LLAMA_VERSION_MAJOR}.${LLAMA_VERSION_MINOR}.${LLAMA_VERSION_PATCH}")
|
||||
|
||||
@@ -136,6 +136,7 @@ option(LLAMA_BUILD_SERVER "llama: build server example"
|
||||
option(LLAMA_BUILD_APP "llama: build the unified binary" ${LLAMA_STANDALONE})
|
||||
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" OFF)
|
||||
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available" ON)
|
||||
option(LLAMA_CONNECT "llama: include the llama-connect tool (downloads a prebuilt binary)" OFF)
|
||||
|
||||
option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT})
|
||||
option(LLAMA_TESTS_INSTALL "llama: install tests" ON)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ include("ggml/cmake/common.cmake")
|
||||
|
||||
function(llama_add_compile_flags)
|
||||
if (LLAMA_FATAL_WARNINGS)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM")
|
||||
list(APPEND C_FLAGS -Werror)
|
||||
list(APPEND CXX_FLAGS -Werror)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
|
||||
+22
-1
@@ -1717,10 +1717,11 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
).set_env("LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT").set_examples({LLAMA_EXAMPLE_SERVER}));
|
||||
add_opt(common_arg(
|
||||
{"-cram", "--cache-ram"}, "N",
|
||||
string_format("set the maximum cache size in MiB (default: %d, -1 - no limit, 0 - disable)"
|
||||
string_format("set the maximum cache size in MiB (default: %d, 0 for decision models, -1 - no limit, 0 - disable)"
|
||||
"[(more info)](https://github.com/ggml-org/llama.cpp/pull/16391)", params.cache_ram_mib),
|
||||
[](common_params & params, int value) {
|
||||
params.cache_ram_mib = value;
|
||||
params.cache_ram_explicit = true;
|
||||
}
|
||||
).set_env("LLAMA_ARG_CACHE_RAM").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}));
|
||||
add_opt(common_arg(
|
||||
@@ -3493,6 +3494,26 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.ui = value;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI"));
|
||||
add_opt(common_arg(
|
||||
{"--connect"},
|
||||
string_format("(experimental) open a peer-to-peer tunnel for server using llama-connect (default: %s)", params.server_connect ? "enabled" : "disabled"),
|
||||
[](common_params & params) {
|
||||
params.server_connect = true;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CONNECT"));
|
||||
add_opt(common_arg(
|
||||
{"--connect-code"}, "CODE",
|
||||
"manually specify a 40-character code for --connect (default: generate a new one for each run)",
|
||||
[](common_params & params, const std::string & value) {
|
||||
std::string val = value;
|
||||
string_replace_all(val, "-", "");
|
||||
string_replace_all(val, " ", "");
|
||||
if (val.size() != 40) {
|
||||
throw std::invalid_argument(string_format("error: invalid connect code '%s', must be 40 characters\n", value.c_str()));
|
||||
}
|
||||
params.server_connect_code = val;
|
||||
}
|
||||
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CONNECT_CODE"));
|
||||
add_opt(common_arg(
|
||||
{"--embedding", "--embeddings"},
|
||||
string_format("restrict to only support embedding use case; use only with dedicated embedding models (default: %s)", params.embedding ? "enabled" : "disabled"),
|
||||
|
||||
@@ -1104,12 +1104,25 @@ static common_chat_params_init_fn common_chat_template_detect_params_init(const
|
||||
return common_chat_params_init_llm_jp_harmony;
|
||||
}
|
||||
|
||||
// Harmony XML tool calls, checked before the GPT-OSS JSON calls
|
||||
if (src.find("<|channel|>") != std::string::npos && src.find("<|constrain|>xml") != std::string::npos &&
|
||||
src.find("rfl:parameter") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: Harmony XML\n");
|
||||
return common_chat_params_init_harmony_xml;
|
||||
}
|
||||
|
||||
// GPT-OSS - has unique channel-based structure that needs dedicated handler
|
||||
if (src.find("<|channel|>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: GPT-OSS\n");
|
||||
return common_chat_params_init_gpt_oss;
|
||||
}
|
||||
|
||||
// Muse Spark shares the Muse recipient protocol and adds a commentary recipient.
|
||||
if (src.find("to=commentary") != std::string::npos && src.find("<|eom|>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: Muse Spark\n");
|
||||
return common_chat_params_init_muse_spark;
|
||||
}
|
||||
|
||||
// Muse Glimmer format using " to=<recipient>" recipients and <|eom|>/<|eot|> message terminators.
|
||||
if (src.find("<atem:function_calls>") != std::string::npos && src.find("<|eom|>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: Muse Glimmer\n");
|
||||
|
||||
@@ -1174,6 +1174,7 @@ static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NA
|
||||
{ COMMON_DECISION_TYPE_VLLM_D3, "vllm-d3" },
|
||||
{ COMMON_DECISION_TYPE_LFM2_D1, "lfm2-d1" },
|
||||
{ COMMON_DECISION_TYPE_LFM2_D1_OMNI, "lfm2-d1-omni" },
|
||||
{ COMMON_DECISION_TYPE_GRANITE_SWITCH, "granite-switch"},
|
||||
// for future models, ALWAYS add org name prefix to avoid name collisions, e.g. "orgname-modelname"
|
||||
};
|
||||
|
||||
|
||||
@@ -640,6 +640,11 @@ struct common_params {
|
||||
int32_t kv_unified_per_slot = 0; // max context per parallel slot; 0 = unset
|
||||
int32_t checkpoint_min_step = 8192; // minimum spacing between context checkpoints
|
||||
int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
|
||||
bool cache_ram_explicit = false; // set by --cache-ram, decision models disable the cache otherwise
|
||||
|
||||
// llama-connect params
|
||||
bool server_connect = false;
|
||||
std::string server_connect_code = "";
|
||||
|
||||
std::string public_path = ""; // NOLINT
|
||||
std::string api_prefix = ""; // NOLINT
|
||||
@@ -969,6 +974,7 @@ enum common_decision_type {
|
||||
COMMON_DECISION_TYPE_VLLM_D3, // same as pplx-decider, non-causal attention
|
||||
COMMON_DECISION_TYPE_LFM2_D1, // same as openjev, the labels depend on the question type
|
||||
COMMON_DECISION_TYPE_LFM2_D1_OMNI, // same as laya, other prompt layout
|
||||
COMMON_DECISION_TYPE_GRANITE_SWITCH, // same as openjev, run by the decision aLoRA of granite switch
|
||||
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
|
||||
};
|
||||
|
||||
|
||||
+177
-59
@@ -39,6 +39,9 @@
|
||||
// downloader
|
||||
//
|
||||
|
||||
// the suffix of the file a download writes until it completes
|
||||
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);
|
||||
@@ -186,6 +189,13 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
[&](const httplib::Response &response) {
|
||||
if (p.downloaded > 0 && response.status != 206) {
|
||||
LOG_WRN("%s: server did not respond with 206 Partial Content for a resume request. Status: %d\n", func, response.status);
|
||||
if (response.status == 416 || response.status == 200) {
|
||||
// the kept partial cannot be resumed: 416 for a complete or stale one,
|
||||
// 200 from a server that ignores the range; drop it, so the next
|
||||
// attempt starts over instead of wedging on the same answer
|
||||
ofs.close();
|
||||
remove(path_tmp.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (p.downloaded == 0 && response.status != 200) {
|
||||
@@ -214,7 +224,7 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
if (progress_step >= p.total / 1000 || p.downloaded == p.total) {
|
||||
if (callback) {
|
||||
callback->on_update(p);
|
||||
if (callback->is_cancelled()) {
|
||||
if (callback->is_cancelled() && (p.total == 0 || p.downloaded != p.total)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -331,10 +341,24 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
const std::string path_temporary = path + ".downloadInProgress";
|
||||
const std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
int delay = retry_delay_seconds;
|
||||
|
||||
if (!skip_etag && !etag.empty()) {
|
||||
if (std::filesystem::exists(path_temporary) && read_etag(path_temporary) != etag) {
|
||||
LOG_WRN("%s: partial file does not match the server etag, discarding it: %s\n", __func__, path_temporary.c_str());
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
write_etag(path_temporary, etag);
|
||||
}
|
||||
|
||||
if (opts.callback) {
|
||||
// report the resume offset, so a resumed file does not start at 0
|
||||
p.downloaded = supports_ranges && std::filesystem::exists(path_temporary) ?
|
||||
std::filesystem::file_size(path_temporary) : 0;
|
||||
opts.callback->on_start(p);
|
||||
}
|
||||
|
||||
@@ -372,6 +396,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
if (!etag.empty() && !skip_etag) {
|
||||
write_etag(path, etag);
|
||||
remove((path_temporary + ".etag").c_str());
|
||||
}
|
||||
success = true;
|
||||
break;
|
||||
@@ -381,12 +406,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, success);
|
||||
}
|
||||
if (opts.callback && opts.callback->is_cancelled() &&
|
||||
std::filesystem::exists(path_temporary)) {
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete temporary file: %s\n", __func__, path_temporary.c_str());
|
||||
}
|
||||
}
|
||||
// a stopped download keeps path_temporary, so the next attempt resumes from it
|
||||
if (!success) {
|
||||
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
|
||||
return -1; // max attempts reached
|
||||
@@ -594,33 +614,65 @@ static hf_cache::hf_file find_best_sibling(const hf_cache::hf_files & files,
|
||||
return best;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_mmproj(const hf_cache::hf_files & files,
|
||||
const std::string & model) {
|
||||
return find_best_sibling(files, model, "mmproj");
|
||||
static const std::vector<common_download_sidecar> sidecars = {
|
||||
{ "mmproj", "mmproj", false, &common_download_opts::download_mmproj, &common_download_hf_plan::mmproj },
|
||||
{ "mtp", "mtp-", true, &common_download_opts::download_mtp, &common_download_hf_plan::mtp },
|
||||
{ "dflash", "dflash-", true, &common_download_opts::download_dflash, &common_download_hf_plan::dflash },
|
||||
{ "eagle3", "eagle3-", true, &common_download_opts::download_eagle3, &common_download_hf_plan::eagle3 },
|
||||
{ "dspark", "dspark-", true, &common_download_opts::download_dspark, &common_download_hf_plan::dspark },
|
||||
};
|
||||
|
||||
const std::vector<common_download_sidecar> & common_download_sidecars() {
|
||||
return sidecars;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_mtp(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "mtp-", tag);
|
||||
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind) {
|
||||
for (const auto & s : sidecars) {
|
||||
if (kind == s.name) {
|
||||
opts.*s.opt = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_eagle3(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "eagle3-", tag);
|
||||
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan) {
|
||||
std::vector<std::string> out;
|
||||
for (const auto & s : sidecars) {
|
||||
if (!(plan.*s.file).path.empty()) {
|
||||
out.emplace_back(s.name);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_dflash(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "dflash-", tag);
|
||||
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan) {
|
||||
std::vector<common_download_partial> out;
|
||||
|
||||
// a file in the cache resolves to its snapshot entry, a file still to download to its blob
|
||||
auto add = [&](const std::string & kind, const hf_cache::hf_file & f) {
|
||||
if (!f.local_path.empty() && f.local_path != f.final_path) {
|
||||
out.push_back({ kind, f.local_path + PARTIAL_SUFFIX });
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto & f : plan.model_files) {
|
||||
add("", f);
|
||||
}
|
||||
for (const auto & s : sidecars) {
|
||||
add(s.name, plan.*s.file);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_dspark(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "dspark-", tag);
|
||||
// true when `name` carries the keyword of a sidecar kind
|
||||
static bool name_is_sidecar(const std::string & name) {
|
||||
for (const auto & s : sidecars) {
|
||||
if (name.find(s.keyword) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gguf_filename_is_model(const std::string & filepath) {
|
||||
@@ -633,12 +685,7 @@ static bool gguf_filename_is_model(const std::string & filepath) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
|
||||
return filename.find("mmproj") == std::string::npos &&
|
||||
filename.find("imatrix") == std::string::npos &&
|
||||
filename.find("mtp-") == std::string::npos &&
|
||||
filename.find("eagle3-") == std::string::npos &&
|
||||
filename.find("dflash-") == std::string::npos &&
|
||||
filename.find("dspark-") == std::string::npos;
|
||||
return filename.find("imatrix") == std::string::npos && !name_is_sidecar(filename);
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_model(const hf_cache::hf_files & files,
|
||||
@@ -730,8 +777,11 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
|
||||
}
|
||||
} else {
|
||||
primary = find_best_model(all, tag);
|
||||
// a requested sidecar can resolve on its own, without a full model of the same tag
|
||||
if (primary.path.empty() && !opts.download_mtp && !opts.download_dflash && !opts.download_eagle3 && !opts.download_dspark) {
|
||||
// a requested draft can resolve on its own, without a full model of the same tag
|
||||
const bool want_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
|
||||
return s.draft && opts.*s.opt;
|
||||
});
|
||||
if (primary.path.empty() && !want_draft) {
|
||||
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
|
||||
list_available_gguf_files(all);
|
||||
return plan;
|
||||
@@ -743,24 +793,22 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
|
||||
plan.model_files = get_split_files(all, primary);
|
||||
}
|
||||
|
||||
if (opts.download_mmproj && !primary.path.empty()) {
|
||||
plan.mmproj = find_best_mmproj(all, primary.path);
|
||||
}
|
||||
if (opts.download_mtp) {
|
||||
plan.mtp = find_best_mtp(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_dflash) {
|
||||
plan.dflash = find_best_dflash(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_eagle3) {
|
||||
plan.eagle3 = find_best_eagle3(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_dspark) {
|
||||
plan.dspark = find_best_dspark(all, primary.path, tag);
|
||||
// a draft resolves at the tag, the mmproj pairs with the model
|
||||
for (const auto & s : sidecars) {
|
||||
if (!(opts.*s.opt)) {
|
||||
continue;
|
||||
}
|
||||
if (s.draft) {
|
||||
plan.*s.file = find_best_sibling(all, primary.path, s.keyword, tag);
|
||||
} else if (!primary.path.empty()) {
|
||||
plan.*s.file = find_best_sibling(all, primary.path, s.keyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (primary.path.empty() &&
|
||||
plan.mtp.local_path.empty() && plan.dflash.local_path.empty() && plan.eagle3.local_path.empty() && plan.dspark.local_path.empty()) {
|
||||
const bool has_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
|
||||
return s.draft && !(plan.*s.file).local_path.empty();
|
||||
});
|
||||
if (primary.path.empty() && !has_draft) {
|
||||
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
|
||||
list_available_gguf_files(all);
|
||||
}
|
||||
@@ -926,6 +974,11 @@ std::string common_docker_resolve_model(const std::string & docker) {
|
||||
}
|
||||
}
|
||||
|
||||
// a cached file that lists a model under its tag: the first split of a model, not a sidecar
|
||||
static bool is_listed_model(const gguf_split_info & split) {
|
||||
return split.index == 1 && !split.tag.empty() && !name_is_sidecar(split.prefix);
|
||||
}
|
||||
|
||||
std::vector<common_cached_model_info> common_list_cached_models() {
|
||||
std::unordered_set<std::string> seen;
|
||||
std::vector<common_cached_model_info> result;
|
||||
@@ -934,12 +987,7 @@ std::vector<common_cached_model_info> common_list_cached_models() {
|
||||
|
||||
for (const auto & f : files) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (split.index != 1 || split.tag.empty() ||
|
||||
split.prefix.find("mmproj") != std::string::npos ||
|
||||
split.prefix.find("mtp-") != std::string::npos ||
|
||||
split.prefix.find("eagle3-") != std::string::npos ||
|
||||
split.prefix.find("dflash-") != std::string::npos ||
|
||||
split.prefix.find("dspark-") != std::string::npos) {
|
||||
if (!is_listed_model(split)) {
|
||||
continue;
|
||||
}
|
||||
if (seen.insert(f.repo_id + ":" + split.tag).second) {
|
||||
@@ -970,6 +1018,44 @@ std::string common_download_resolve_path(const std::string & hf_repo_with_tag, c
|
||||
return find_best_model(files, tag).local_path;
|
||||
}
|
||||
|
||||
// the files -hf loads from the cache, the model with every sidecar, for each cached
|
||||
// tag of `repo_id` other than `tag`
|
||||
static std::unordered_set<std::string> files_in_use(const std::string & repo_id, const std::string & tag) {
|
||||
common_download_opts opts;
|
||||
opts.offline = true;
|
||||
for (const auto & s : sidecars) {
|
||||
opts.*s.opt = true;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> tags;
|
||||
std::unordered_set<std::string> used;
|
||||
|
||||
for (const auto & f : hf_cache::get_cached_files(repo_id)) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (!is_listed_model(split) || split.tag == tag || !tags.insert(split.tag).second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
common_params_model model;
|
||||
model.hf_repo = repo_id + ":" + split.tag;
|
||||
|
||||
auto plan = common_download_get_hf_plan(model, opts);
|
||||
|
||||
for (const auto & m : plan.model_files) {
|
||||
used.insert(m.local_path);
|
||||
}
|
||||
for (const auto & s : sidecars) {
|
||||
const auto & sf = plan.*s.file;
|
||||
if (!sf.local_path.empty()) {
|
||||
used.insert(sf.local_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove);
|
||||
|
||||
bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -989,11 +1075,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// collect snapshot entries whose tag matches
|
||||
// collect snapshot entries whose tag matches, but a file another tag loads stays
|
||||
const auto used = files_in_use(repo_id, tag_upper);
|
||||
|
||||
std::vector<fs::path> to_remove;
|
||||
for (const auto & f : files) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (split.tag == tag_upper) {
|
||||
if (split.tag == tag_upper && used.find(f.local_path) == used.end()) {
|
||||
to_remove.emplace_back(f.local_path);
|
||||
}
|
||||
}
|
||||
@@ -1002,6 +1090,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove_snapshot_entries(repo_id, to_remove);
|
||||
}
|
||||
|
||||
// remove snapshot entries of `repo_id`, then the blobs no remaining entry references
|
||||
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// resolve blob paths from symlinks before deleting snapshot entries
|
||||
std::vector<fs::path> blobs_to_check;
|
||||
for (const auto & p : to_remove) {
|
||||
@@ -1053,3 +1148,26 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts) {
|
||||
common_params_model model;
|
||||
model.hf_repo = hf_repo_with_tag;
|
||||
|
||||
common_download_opts cached = opts;
|
||||
cached.offline = true;
|
||||
|
||||
auto plan = common_download_get_hf_plan(model, cached);
|
||||
|
||||
std::vector<std::filesystem::path> to_remove;
|
||||
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));
|
||||
}
|
||||
}
|
||||
if (to_remove.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove_snapshot_entries(common_download_split_repo_tag(hf_repo_with_tag).first, to_remove);
|
||||
}
|
||||
|
||||
+38
-1
@@ -104,10 +104,17 @@ std::string common_docker_resolve_model(const std::string & docker);
|
||||
// Remove a cached model from disk
|
||||
// input format: "user/model" or "user/model:tag"
|
||||
// - if tag is omitted, removes the entire repo cache directory
|
||||
// - if tag is present, removes only files matching that tag (and orphaned blobs)
|
||||
// - if tag is present, removes only files matching that tag (and orphaned blobs),
|
||||
// except the files another cached tag of the repo still loads
|
||||
// returns true if anything was removed
|
||||
bool common_download_remove(const std::string & hf_repo_with_tag);
|
||||
|
||||
// remove the cached sidecar files `opts` selects for "user/model:tag", as the cached
|
||||
// plan resolves them, and leave the model files in place; a sidecar file another tag
|
||||
// shares goes for every tag that resolves it
|
||||
// returns true if anything was removed
|
||||
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts);
|
||||
|
||||
struct common_download_hf_plan {
|
||||
hf_cache::hf_file primary;
|
||||
hf_cache::hf_files model_files;
|
||||
@@ -119,3 +126,33 @@ struct common_download_hf_plan {
|
||||
hf_cache::hf_file preset; // if set, only this file is downloaded
|
||||
};
|
||||
common_download_hf_plan common_download_get_hf_plan(const common_params_model & model, const common_download_opts & opts);
|
||||
|
||||
// a sidecar kind a download can pull next to its model: mmproj, and the draft heads
|
||||
// of --spec-type draft-<name>
|
||||
struct common_download_sidecar {
|
||||
const char * name; // the kind, as the API names it
|
||||
const char * keyword; // the part of a file name that marks a file of this kind
|
||||
bool draft; // a draft head, resolved at the tag even without a model
|
||||
|
||||
bool common_download_opts::* opt; // the option that requests it
|
||||
hf_cache::hf_file common_download_hf_plan::* file; // the plan file it resolves to
|
||||
};
|
||||
|
||||
// every sidecar kind, the single list the resolution, the listing and the removal follow
|
||||
const std::vector<common_download_sidecar> & common_download_sidecars();
|
||||
|
||||
// select the sidecar `kind` in `opts`; returns false for an unknown kind
|
||||
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind);
|
||||
|
||||
// the sidecar kinds `plan` resolves a file for
|
||||
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan);
|
||||
|
||||
// a file `plan` still has to download, at the path its partial takes while it is
|
||||
// incomplete; kind is the sidecar it belongs to, empty for the model files
|
||||
struct common_download_partial {
|
||||
std::string kind;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
// the partials the download of `plan` writes, for the files not in the cache yet
|
||||
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan);
|
||||
|
||||
+141
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <cpp-httplib/httplib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -97,6 +99,118 @@ static common_http_url common_http_parse_url(const std::string & url) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
// 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)
|
||||
static bool common_http_is_loopback(const std::string & host) {
|
||||
auto lower = [](std::string s) {
|
||||
for (auto & c : s) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
const std::string h = lower(host);
|
||||
|
||||
if (h == "localhost" || h == "::1" || h == "0.0.0.0") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (h.size() > 10 && h.compare(h.size() - 10, 10, ".localhost") == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4 literal: exactly four all-digit octets with values 0-255, loopback is 127.0.0.0/8
|
||||
int octets[4] = { 0, 0, 0, 0 };
|
||||
size_t pos = 0;
|
||||
int n = 0;
|
||||
bool complete = false;
|
||||
while (n < 4 && pos <= h.size()) {
|
||||
size_t dot = h.find('.', pos);
|
||||
if (dot == std::string::npos) {
|
||||
dot = h.size();
|
||||
}
|
||||
if (dot == pos || dot - pos > 3) {
|
||||
return false;
|
||||
}
|
||||
int val = 0;
|
||||
for (size_t i = pos; i < dot; i++) {
|
||||
if (h[i] < '0' || h[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
val = val * 10 + (h[i] - '0');
|
||||
}
|
||||
if (val > 255) {
|
||||
return false;
|
||||
}
|
||||
octets[n++] = val;
|
||||
if (dot == h.size()) {
|
||||
complete = (n == 4);
|
||||
break;
|
||||
}
|
||||
pos = dot + 1;
|
||||
}
|
||||
|
||||
return complete && octets[0] == 127;
|
||||
}
|
||||
|
||||
// match a host against a NO_PROXY list: comma separated entries, "*" bypasses the proxy for every host,
|
||||
// an entry matches the host itself or any of its subdomains, a leading dot is optional, ports are ignored
|
||||
static bool common_http_proxy_bypass(const std::string & no_proxy, const std::string & host) {
|
||||
auto lower = [](std::string s) {
|
||||
for (auto & c : s) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
const std::string h = lower(host);
|
||||
|
||||
for (size_t pos = 0; pos < no_proxy.size(); ) {
|
||||
size_t end = no_proxy.find(',', pos);
|
||||
if (end == std::string::npos) {
|
||||
end = no_proxy.size();
|
||||
}
|
||||
|
||||
std::string entry = lower(no_proxy.substr(pos, end - pos));
|
||||
pos = end + 1;
|
||||
|
||||
entry.erase(0, entry.find_first_not_of(" \t"));
|
||||
auto last = entry.find_last_not_of(" \t");
|
||||
if (last == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
entry.erase(last + 1);
|
||||
|
||||
if (entry == "*") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.front() == '.') {
|
||||
entry.erase(0, 1);
|
||||
}
|
||||
|
||||
if (entry.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (h == entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (h.size() > entry.size() && h[h.size() - entry.size() - 1] == '.' &&
|
||||
h.compare(h.size() - entry.size(), entry.size(), entry) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::pair<httplib::Client, common_http_url> common_http_client(const std::string & url) {
|
||||
common_http_url parts = common_http_parse_url(url);
|
||||
|
||||
@@ -123,6 +237,33 @@ static std::pair<httplib::Client, common_http_url> common_http_client(const std:
|
||||
|
||||
cli.set_follow_location(true);
|
||||
|
||||
// Honor HTTP_PROXY / HTTPS_PROXY environment variables (both lower- and upper-case).
|
||||
auto getenv_s = [](const char * upper, const char * lower) -> std::string {
|
||||
const char * val = std::getenv(upper);
|
||||
if (!val) {
|
||||
val = std::getenv(lower);
|
||||
}
|
||||
return val ? val : "";
|
||||
};
|
||||
|
||||
const std::string proxy_url = (parts.scheme == "https")
|
||||
? getenv_s("HTTPS_PROXY", "https_proxy")
|
||||
: getenv_s("HTTP_PROXY", "http_proxy");
|
||||
|
||||
if (!proxy_url.empty() && !common_http_is_loopback(parts.host) &&
|
||||
!common_http_proxy_bypass(getenv_s("NO_PROXY", "no_proxy"), parts.host)) {
|
||||
try {
|
||||
common_http_url proxy = common_http_parse_url(proxy_url);
|
||||
cli.set_proxy(proxy.host, proxy.port);
|
||||
if (!proxy.user.empty()) {
|
||||
cli.set_proxy_basic_auth(proxy.user, proxy.password);
|
||||
}
|
||||
} catch (const std::exception & e) {
|
||||
// fall back to a direct connection, the URL itself is not logged as it can carry credentials
|
||||
LOG_WRN("%s: ignoring malformed proxy URL from the environment: %s\n", __func__, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return { std::move(cli), std::move(parts) };
|
||||
}
|
||||
|
||||
|
||||
@@ -600,6 +600,7 @@ value for_statement::execute_impl(context & ctx) const {
|
||||
bool noIteration = true;
|
||||
for (size_t i = 0; i < filtered_items.size(); i++) {
|
||||
JJ_DEBUG("For loop iteration %zu/%zu", i + 1, filtered_items.size());
|
||||
context loop_scope(scope);
|
||||
value_object loop_obj = mk_val<value_object>();
|
||||
loop_obj->has_builtins = false; // loop object has no builtins
|
||||
loop_obj->insert("index", mk_val<value_int>(i + 1));
|
||||
@@ -611,11 +612,11 @@ value for_statement::execute_impl(context & ctx) const {
|
||||
loop_obj->insert("length", mk_val<value_int>(filtered_items.size()));
|
||||
loop_obj->insert("previtem", i > 0 ? filtered_items[i - 1] : mk_val<value_undefined>("previtem"));
|
||||
loop_obj->insert("nextitem", i < filtered_items.size() - 1 ? filtered_items[i + 1] : mk_val<value_undefined>("nextitem"));
|
||||
scope.set_val("loop", loop_obj);
|
||||
scope_update_fns[i](scope);
|
||||
loop_scope.set_val("loop", loop_obj);
|
||||
scope_update_fns[i](loop_scope);
|
||||
try {
|
||||
for (auto & stmt : body) {
|
||||
value val = stmt->execute(scope);
|
||||
value val = stmt->execute(loop_scope);
|
||||
result->push_back(val);
|
||||
}
|
||||
} catch (const continue_statement::signal &) {
|
||||
|
||||
@@ -74,16 +74,12 @@ struct context {
|
||||
}
|
||||
~context() = default;
|
||||
|
||||
context(const context & parent) : context() {
|
||||
context(const context & parent) :
|
||||
src(parent.src),
|
||||
current_time(parent.current_time),
|
||||
is_get_stats(parent.is_get_stats),
|
||||
// inherit variables (for example, when entering a new scope)
|
||||
auto & pvar = parent.env->as_ordered_object();
|
||||
for (const auto & pair : pvar) {
|
||||
set_val(pair.first, pair.second);
|
||||
}
|
||||
current_time = parent.current_time;
|
||||
is_get_stats = parent.is_get_stats;
|
||||
src = parent.src;
|
||||
}
|
||||
env(mk_val<value_object>(*parent.env)) {}
|
||||
|
||||
value get_val(const std::string & name) {
|
||||
value default_val = mk_val<value_undefined>(name);
|
||||
|
||||
+66
-12
@@ -244,6 +244,8 @@ static std::string json_ensure_ascii_preserving_format(const std::string & json_
|
||||
return output;
|
||||
}
|
||||
|
||||
static std::string float_repr(double val);
|
||||
|
||||
static value tojson(const func_args & args) {
|
||||
args.ensure_count(1, 5);
|
||||
value val_ascii = args.get_kwarg_or_pos("ensure_ascii", 1);
|
||||
@@ -634,6 +636,11 @@ const func_builtins & value_int_t::get_builtins() const {
|
||||
|
||||
|
||||
const func_builtins & value_float_t::get_builtins() const {
|
||||
// Python's str() of a float, nan and inf stay as they are (tojson writes NaN and Infinity)
|
||||
static const func_handler tostring = [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_float>();
|
||||
return mk_val<value_string>(float_repr(args.get_pos(0)->as_float()));
|
||||
};
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
{"abs", [](const func_args & args) -> value {
|
||||
@@ -650,8 +657,8 @@ const func_builtins & value_float_t::get_builtins() const {
|
||||
args.ensure_vals<value_float>();
|
||||
return mk_val<value_float>(args.get_pos(0)->as_float());
|
||||
}},
|
||||
{"safe", tojson},
|
||||
{"string", tojson},
|
||||
{"safe", tostring},
|
||||
{"string", tostring},
|
||||
{"tojson", tojson},
|
||||
};
|
||||
return builtins;
|
||||
@@ -1021,10 +1028,6 @@ const func_builtins & value_bool_t::get_builtins() const {
|
||||
return builtins;
|
||||
}
|
||||
|
||||
[[noreturn]] static value array_unique_not_implemented(const func_args &) {
|
||||
throw not_implemented_exception("Array unique builtin not implemented");
|
||||
}
|
||||
|
||||
const func_builtins & value_array_t::get_builtins() const {
|
||||
static const func_builtins builtins = {
|
||||
{"default", default_value},
|
||||
@@ -1250,7 +1253,36 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
{"unique", array_unique_not_implemented},
|
||||
{"unique", [](const func_args & args) -> value {
|
||||
args.ensure_count(1, 3);
|
||||
args.ensure_vals<value_array>();
|
||||
value val = args.get_pos(0);
|
||||
value val_case = args.get_kwarg_or_pos("case_sensitive", 1);
|
||||
value attribute = args.get_kwarg_or_pos("attribute", 2);
|
||||
// FIXME: unique is currently always case sensitive
|
||||
//const bool case_sensitive = val_case->as_bool(); // undefined == false
|
||||
value undef = mk_val<value_undefined>();
|
||||
std::vector<value> result;
|
||||
std::vector<value> seen;
|
||||
for (const auto & item : val->as_array()) {
|
||||
value key = item;
|
||||
if (!attribute->is_undefined()) {
|
||||
key = get_attribute(item, attribute, undef);
|
||||
}
|
||||
bool dup = false;
|
||||
for (const auto & s : seen) {
|
||||
if (value_compare(s, key, value_compare_op::eq)) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
seen.push_back(key);
|
||||
result.push_back(item);
|
||||
}
|
||||
}
|
||||
return mk_val<value_array>(std::move(result));
|
||||
}},
|
||||
};
|
||||
return builtins;
|
||||
}
|
||||
@@ -1292,6 +1324,15 @@ const func_builtins & value_object_t::get_builtins() const {
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
{"list", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_object>();
|
||||
const auto & obj = args.get_pos(0)->as_ordered_object();
|
||||
auto result = mk_val<value_array>();
|
||||
for (const auto & pair : obj) {
|
||||
result->push_back(pair.first);
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
{"values", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_object>();
|
||||
const auto & obj = args.get_pos(0)->as_ordered_object();
|
||||
@@ -1522,10 +1563,10 @@ void global_from_json(context & ctx, const common_json & json_obj, bool mark_inp
|
||||
}
|
||||
}
|
||||
|
||||
// a float as Python writes it (repr, json.dumps): the shortest digits that read back as the same value
|
||||
static std::string float_to_json(double val) {
|
||||
// a float as Python's repr() and str() write it: the shortest digits that read back as the same value
|
||||
static std::string float_repr(double val) {
|
||||
if (!std::isfinite(val)) {
|
||||
return std::isnan(val) ? "nan" : val < 0 ? "-inf" : "inf"; // as before (json.dumps writes NaN and Infinity, str() writes nan and inf)
|
||||
return std::isnan(val) ? "nan" : val < 0 ? "-inf" : "inf";
|
||||
}
|
||||
const double mag = std::fabs(val);
|
||||
|
||||
@@ -1551,6 +1592,7 @@ static std::string float_to_json(double val) {
|
||||
if (prec == 16 || reads_back(d)) {
|
||||
digits = d;
|
||||
} else if (is_pow2 && d.back() != '9') {
|
||||
// a last digit 9 is not bumped (it would carry): no power of 2, from 2^-1074 to 2^1023, needs it
|
||||
d.back()++;
|
||||
if (reads_back(d)) {
|
||||
digits = d;
|
||||
@@ -1581,6 +1623,17 @@ static std::string float_to_json(double val) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// a float as json.dumps writes it: nan and inf as NaN and Infinity, which are not valid JSON
|
||||
static std::string float_json(double val) {
|
||||
if (std::isnan(val)) {
|
||||
return "NaN";
|
||||
}
|
||||
if (std::isinf(val)) {
|
||||
return val < 0 ? "-Infinity" : "Infinity";
|
||||
}
|
||||
return float_repr(val);
|
||||
}
|
||||
|
||||
// recursively convert value to JSON string
|
||||
// TODO: avoid circular references
|
||||
static void value_to_json_internal(std::ostringstream & oss, const value & val, int curr_lvl, int indent, const std::string_view item_sep, const std::string_view key_sep) {
|
||||
@@ -1598,7 +1651,7 @@ static void value_to_json_internal(std::ostringstream & oss, const value & val,
|
||||
} else if (is_val<value_int>(val)) {
|
||||
oss << val->as_int();
|
||||
} else if (is_val<value_float>(val)) {
|
||||
oss << float_to_json(val->as_float());
|
||||
oss << float_json(val->as_float());
|
||||
} else if (is_val<value_string>(val)) {
|
||||
oss << "\"";
|
||||
for (char c : val->as_string().str()) {
|
||||
@@ -1645,7 +1698,8 @@ static void value_to_json_internal(std::ostringstream & oss, const value & val,
|
||||
size_t i = 0;
|
||||
for (const auto & pair : obj) {
|
||||
oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : "");
|
||||
value_to_json_internal(oss, mk_val<value_string>(pair.first->as_string().str()), curr_lvl + 1, indent, item_sep, key_sep);
|
||||
const std::string key = is_val<value_float>(pair.first) ? float_json(pair.first->as_float()) : pair.first->as_string().str();
|
||||
value_to_json_internal(oss, mk_val<value_string>(key), curr_lvl + 1, indent, item_sep, key_sep);
|
||||
oss << key_sep;
|
||||
value_to_json_internal(oss, pair.second, curr_lvl + 1, indent, item_sep, key_sep);
|
||||
if (i < obj.size() - 1) {
|
||||
|
||||
@@ -68,7 +68,7 @@ static llama_token try_draft(common_ngram_cache & nc_static, const common_ngram
|
||||
if (part_static_it == nc_static.end()) {
|
||||
return LLAMA_TOKEN_NULL;
|
||||
}
|
||||
const common_ngram_cache_part part_static = part_static_it->second;
|
||||
const common_ngram_cache_part & part_static = part_static_it->second;
|
||||
|
||||
int max_count_static = 0;
|
||||
int sum_count_static = 0;
|
||||
@@ -96,7 +96,7 @@ static llama_token try_draft(common_ngram_cache & nc_static, const common_ngram
|
||||
|
||||
// Try to draft a token from primary cache (context/dynamic), validate with static cache:
|
||||
static llama_token try_draft(
|
||||
common_ngram_cache & nc_primary, const std::vector<common_ngram> & ngrams_primary, common_ngram_cache_part & part_static,
|
||||
common_ngram_cache & nc_primary, const std::vector<common_ngram> & ngrams_primary, const common_ngram_cache_part & part_static,
|
||||
const int * min_sample_size, const int * min_percent) {
|
||||
|
||||
llama_token drafted_token = LLAMA_TOKEN_NULL;
|
||||
@@ -108,7 +108,7 @@ static llama_token try_draft(
|
||||
if (part_primary_it == nc_primary.end()) {
|
||||
continue;
|
||||
}
|
||||
const common_ngram_cache_part part_primary = part_primary_it->second;
|
||||
const common_ngram_cache_part & part_primary = part_primary_it->second;
|
||||
|
||||
int max_count_primary = 0;
|
||||
int max_count_static = 0;
|
||||
@@ -118,7 +118,7 @@ static llama_token try_draft(
|
||||
for (std::pair<llama_token, int> token_count_primary : part_primary) {
|
||||
const llama_token token = token_count_primary.first;
|
||||
|
||||
common_ngram_cache_part::iterator token_count_static_it = part_static.find(token);
|
||||
common_ngram_cache_part::const_iterator token_count_static_it = part_static.find(token);
|
||||
|
||||
const int32_t count_primary = token_count_primary.second;
|
||||
const int32_t count_static = token_count_static_it != part_static.end() ? 100*token_count_static_it->second : 1;
|
||||
@@ -162,11 +162,9 @@ void common_ngram_cache_draft(
|
||||
for (int j = ngram_start_static; j < ngram_start_static + LLAMA_NGRAM_STATIC; ++j) {
|
||||
ngram_static.tokens[j-ngram_start_static] = get_token(inp, draft, j);
|
||||
}
|
||||
common_ngram_cache::iterator part_static_it = nc_static.find(ngram_static);
|
||||
common_ngram_cache_part part_static;
|
||||
if (part_static_it != nc_static.end()) {
|
||||
part_static = part_static_it->second;
|
||||
}
|
||||
static const common_ngram_cache_part part_static_empty;
|
||||
common_ngram_cache::const_iterator part_static_it = nc_static.find(ngram_static);
|
||||
const common_ngram_cache_part & part_static = part_static_it != nc_static.end() ? part_static_it->second : part_static_empty;
|
||||
|
||||
// cd = context + dynamic
|
||||
std::vector<common_ngram> ngrams_cd;
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#include "parsers.h"
|
||||
|
||||
// Harmony with XML tool calls: <|start|>assistant to=NAME<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="KEY">\nVALUE\n</rfl:parameter>
|
||||
common_chat_params common_chat_params_init_harmony_xml(const common_chat_template & tmpl,
|
||||
const autoparser::generation_params & inputs) {
|
||||
common_chat_params data;
|
||||
|
||||
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
|
||||
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
|
||||
data.message_delimiters = {
|
||||
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
|
||||
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
|
||||
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>developer" },
|
||||
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
|
||||
};
|
||||
|
||||
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
|
||||
data.supports_thinking = true;
|
||||
|
||||
data.thinking_start_tag = "<|channel|>analysis<|message|>";
|
||||
data.thinking_end_tags = {"<|end|>"};
|
||||
|
||||
// EOG text must reach the parser: an XML call has no closing tag of its own
|
||||
static const std::vector<std::string> EOG = {
|
||||
"<|call|>", "<|return|>", "<|endoftext|>", "<|im_end|>", "<|fim_pad|>", "<|repo_name|>", "<|file_sep|>",
|
||||
};
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<|channel|>", "<|constrain|>", "<|message|>", "<|start|>", "<|end|>",
|
||||
};
|
||||
data.preserved_tokens.insert(data.preserved_tokens.end(), EOG.begin(), EOG.end());
|
||||
|
||||
if (inputs.has_continuation()) {
|
||||
const auto & msg = inputs.continue_msg;
|
||||
|
||||
data.generation_prompt = "<|start|>assistant<|channel|>analysis<|message|>" + msg.reasoning_content;
|
||||
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
|
||||
data.generation_prompt += "<|end|><|start|>assistant<|channel|>final<|message|>" + msg.render_content();
|
||||
}
|
||||
|
||||
data.prompt.append(data.generation_prompt);
|
||||
}
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
|
||||
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
|
||||
static const std::string PARAM_OPEN = "<rfl:parameter name=\"";
|
||||
static const std::string PARAM_CLOSE = "\n</rfl:parameter>";
|
||||
|
||||
data.parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
|
||||
auto start = p.rule("start", p.literal("<|start|>assistant"));
|
||||
|
||||
auto eog = p.choice();
|
||||
for (const auto & tok : EOG) {
|
||||
eog |= p.literal(tok);
|
||||
}
|
||||
auto with_eog = [&](std::vector<std::string> delims) {
|
||||
delims.insert(delims.end(), EOG.begin(), EOG.end());
|
||||
return delims;
|
||||
};
|
||||
|
||||
auto analysis_open = p.literal("<|channel|>analysis<|message|>");
|
||||
auto analysis_body = p.until_one_of(with_eog({ "<|end|>", "<|constrain|><|start|>assistant", "<|start|>assistant" }));
|
||||
auto analysis_close = p.optional(p.literal("<|end|>") | p.literal("<|constrain|>") | eog);
|
||||
if (extract_reasoning) {
|
||||
p.rule("analysis", analysis_open + p.reasoning(analysis_body) + analysis_close);
|
||||
} else {
|
||||
p.rule("analysis", p.atomic(p.content(analysis_open)) + p.content(analysis_body + analysis_close));
|
||||
}
|
||||
auto analysis = p.ref("analysis");
|
||||
|
||||
auto final_msg = p.rule("final",
|
||||
p.literal("<|channel|>final<|message|>") +
|
||||
p.content(p.until_one_of(with_eog({ "<|end|>" }))) +
|
||||
p.optional(p.literal("<|end|>") | eog));
|
||||
|
||||
if (has_response_format) {
|
||||
auto constrain_type = p.chars("[A-Za-z0-9_-]", 1, -1);
|
||||
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)) +
|
||||
p.optional(p.literal("<|end|>") | eog));
|
||||
|
||||
return start + p.zero_or_more(analysis + start) + response_format;
|
||||
}
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
// tolerate a missing closing tag
|
||||
std::vector<std::string> value_ends = with_eog({ PARAM_OPEN, "<|end|>" });
|
||||
std::vector<std::string> string_ends = { PARAM_CLOSE };
|
||||
auto next_open = p.choice();
|
||||
for (const auto & e : value_ends) {
|
||||
string_ends.push_back("\n" + e);
|
||||
next_open |= p.literal(e);
|
||||
}
|
||||
auto arg_close = p.tool_arg_close(p.gbnf(
|
||||
p.literal(PARAM_CLOSE) | (p.literal("\n") + p.peek(p.optional(p.literal("\n")) + next_open)),
|
||||
gbnf_format_literal("\n") + " " + gbnf_format_literal(PARAM_CLOSE.substr(1)) + "?"));
|
||||
auto arg_string = p.rule("rfl-arg-string",
|
||||
p.ac(p.tool_arg_string_value(p.until_one_of(string_ends)) + arg_close, PARAM_CLOSE));
|
||||
auto arg_sep = p.optional(p.literal("\n"));
|
||||
|
||||
std::string call_close_gbnf = gbnf_format_literal("<|end|>");
|
||||
for (const auto & tok : EOG) {
|
||||
call_close_gbnf += " | " + gbnf_format_literal(tok);
|
||||
}
|
||||
auto call_close = p.gbnf(p.literal("<|end|>") | eog | p.peek(p.literal("<|start|>assistant")),
|
||||
"(" + call_close_gbnf + ")?");
|
||||
|
||||
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");
|
||||
|
||||
std::vector<common_peg_parser> arg_rules;
|
||||
foreach_parameter(function, [&](size_t param_index, const common_chat_schema_property & param, const common_chat_schema_document_ptr & doc) {
|
||||
auto rule_name = "tool-" + std::to_string(tool_index) + "-arg-" + std::to_string(param_index);
|
||||
auto arg_open = p.tool_arg_open(p.literal(PARAM_OPEN) + p.tool_arg_name(p.literal(param.name)) + p.literal("\">\n"));
|
||||
|
||||
auto types = param.schema->value_types();
|
||||
auto arg_value = p.eps();
|
||||
if (!types.has(common_chat_schema::TYPE_STRING) &&
|
||||
(types.has(common_chat_schema::TYPE_OBJECT) || types.has(common_chat_schema::TYPE_ARRAY))) {
|
||||
// the schema grammar fixes the key order, and the model writes keys in its own order
|
||||
arg_value = p.tool_arg_json_value(p.json()) + arg_close;
|
||||
} else if (!types.has(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
|
||||
} else if (types.is_only(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = arg_string;
|
||||
} else {
|
||||
// grammar keeps only the string rule; the parser tries JSON first to type the value
|
||||
auto json_value = p.choice();
|
||||
if (types.has(common_chat_schema::TYPE_OBJECT)) {
|
||||
json_value |= p.json_object();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_ARRAY)) {
|
||||
json_value |= p.json_array();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NUMBER) || types.has(common_chat_schema::TYPE_INTEGER)) {
|
||||
json_value |= p.json_number();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_BOOLEAN)) {
|
||||
json_value |= p.json_bool();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NULL)) {
|
||||
json_value |= p.json_null();
|
||||
}
|
||||
arg_value = p.gbnf(p.atomic(p.tool_arg_json_value(json_value) + arg_close) | arg_string, "rfl-arg-string");
|
||||
}
|
||||
|
||||
arg_rules.push_back(p.rule(rule_name, p.tool_arg(arg_open + arg_value)));
|
||||
});
|
||||
|
||||
if (arg_rules.empty()) {
|
||||
auto any_name = p.chars("[^\"\\n<]", 1, -1);
|
||||
arg_rules.push_back(p.rule("tool-" + std::to_string(tool_index) + "-arg-any",
|
||||
p.tool_arg(p.tool_arg_open(p.literal(PARAM_OPEN) + p.tool_arg_name(any_name) + p.literal("\">\n")) + arg_string)));
|
||||
}
|
||||
|
||||
auto args = p.zero_or_more(p.choice(arg_rules) + arg_sep);
|
||||
|
||||
auto call = p.tool(
|
||||
p.tool_open(p.literal(" to=") + p.tool_name(p.literal(name)) +
|
||||
p.literal("<|channel|>analysis <|constrain|>xml<|message|>")) +
|
||||
p.tool_args(args) +
|
||||
p.tool_close(call_close));
|
||||
|
||||
tool_choice |= p.rule("tool-" + std::to_string(tool_index), call);
|
||||
});
|
||||
|
||||
auto tool_calls = inputs.parallel_tool_calls
|
||||
? tool_choice + p.zero_or_more(start + tool_choice)
|
||||
: tool_choice;
|
||||
auto tool_call = p.trigger_rule("tool-call", tool_calls);
|
||||
|
||||
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
|
||||
return start + p.zero_or_more(analysis + start) + tool_call;
|
||||
}
|
||||
|
||||
return start + p.zero_or_more(analysis + start) + (tool_call | final_msg | p.content(p.rest()));
|
||||
}
|
||||
|
||||
auto unsolicited = p.rule("unsolicited", p.atomic(p.literal(" to=") + p.rest()));
|
||||
|
||||
// output without a channel header is returned as content instead of failing the request
|
||||
return start + p.zero_or_more(analysis + start) + (final_msg | unsolicited | p.content(p.rest()));
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
});
|
||||
|
||||
if (data.grammar_lazy) {
|
||||
data.grammar_triggers = {
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^\\s+to$" },
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>assistant(\\s+to)" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "parsers.h"
|
||||
|
||||
// An assistant turn is rendered as one or more messages, each
|
||||
// "<|start|>assistant to=<recipient><|message|>{content}{END}" where END is
|
||||
// <|eom|> (more messages follow) or <|eot|> (end of turn):
|
||||
// - chain-of-thought: to=self, terminated by <|eom|>
|
||||
// - user facing aside: to=commentary, terminated by <|eom|>
|
||||
// - final answer: to=user, terminated by <|eot|>
|
||||
// A commentary message carries content the user sees while the assistant keeps
|
||||
// working, typically before or between tool calls.
|
||||
// The generation prompt is just "<|start|>assistant"; the model emits its own
|
||||
// " to=...<|message|>".
|
||||
common_chat_params common_chat_params_init_muse_spark(const common_chat_template & tmpl,
|
||||
const autoparser::generation_params & inputs) {
|
||||
common_chat_params data;
|
||||
|
||||
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
|
||||
data.generation_prompt = "<|start|>assistant";
|
||||
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
|
||||
data.supports_thinking = true;
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<|start|>", "<|message|>", "<|eom|>", "<|eot|>",
|
||||
// ATEM tool-call markup emitted on " to=<tool>" turns.
|
||||
"<atem:function_calls>", "<atem:invoke", "<atem:parameter", "</atem:parameter>",
|
||||
"</atem:invoke>", "</atem:function_calls>",
|
||||
};
|
||||
|
||||
data.message_delimiters = {
|
||||
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
|
||||
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
|
||||
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
|
||||
{ COMMON_CHAT_ROLE_TOOL, "<|start|>tool" },
|
||||
};
|
||||
|
||||
if (inputs.has_continuation()) {
|
||||
const auto & msg = inputs.continue_msg;
|
||||
|
||||
data.generation_prompt = "<|start|>assistant to=self<|message|>" + msg.reasoning_content;
|
||||
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
|
||||
data.generation_prompt += "<|eom|><|start|>assistant to=user<|message|>" + msg.render_content();
|
||||
}
|
||||
|
||||
data.prompt.append(data.generation_prompt);
|
||||
}
|
||||
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
// Constrained grammar whenever tools are offered.
|
||||
auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
|
||||
|
||||
data.parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
|
||||
auto start = p.rule("start", p.literal("<|start|>assistant"));
|
||||
|
||||
if (!extract_reasoning && !include_grammar) {
|
||||
return start + p.content(p.rest());
|
||||
}
|
||||
|
||||
if (extract_reasoning) {
|
||||
p.rule("analysis", p.literal(" to=self<|message|>") + p.reasoning(p.until("<|eom|>")) + p.literal("<|eom|>"));
|
||||
} else {
|
||||
p.rule("analysis", p.literal(" to=self<|message|>") + p.content(p.until("<|eom|>")) + p.literal("<|eom|>"));
|
||||
}
|
||||
auto analysis = p.ref("analysis");
|
||||
|
||||
// Commentary is user facing content emitted mid turn, so it reads as
|
||||
// content and always ends on <|eom|>.
|
||||
auto preamble = p.rule("preamble", p.literal(" to=commentary<|message|>") +
|
||||
p.content(p.until("<|eom|>")) + p.literal("<|eom|>"));
|
||||
|
||||
auto aside = p.rule("aside", analysis | preamble);
|
||||
|
||||
auto recipient = p.optional(p.literal(" to=user"));
|
||||
auto final_msg = p.rule("final", recipient + p.literal("<|message|>") +
|
||||
p.content(p.until_one_of({ "<|eot|>", "<|eom|>" })));
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
auto string_value = p.ac(
|
||||
p.tool_arg_string_value(p.until("</atem:parameter>")) + p.tool_arg_close(p.literal("</atem:parameter>")),
|
||||
"</atem:parameter>");
|
||||
|
||||
auto tool_choice = p.choice();
|
||||
foreach_function(inputs.tools, [&](size_t tool_index, const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
const std::string name = function.at("name");
|
||||
|
||||
std::vector<common_peg_parser> arg_rules;
|
||||
foreach_parameter(function, [&](size_t param_index, const common_chat_schema_property & prop, const common_chat_schema_document_ptr & doc) {
|
||||
auto value_parser = p.eps();
|
||||
if (prop.schema->may_be_string()) {
|
||||
value_parser = string_value;
|
||||
} else {
|
||||
value_parser = p.tool_arg_json_value(
|
||||
p.schema(p.json(), "tool-" + std::to_string(tool_index) + "-arg-" + std::to_string(param_index) + "-schema", doc, *prop.schema))
|
||||
+ p.tool_arg_close(p.literal("</atem:parameter>"));
|
||||
}
|
||||
|
||||
arg_rules.push_back(p.tool_arg(
|
||||
p.tool_arg_open(p.literal("<atem:parameter name=\"") + p.tool_arg_name(p.literal(prop.name)) + p.literal("\">")) +
|
||||
value_parser));
|
||||
});
|
||||
|
||||
auto args = p.eps();
|
||||
if (!arg_rules.empty()) {
|
||||
args = p.zero_or_more(p.choice(arg_rules) + p.space());
|
||||
}
|
||||
|
||||
auto tool_parser = p.tool(
|
||||
p.tool_open(p.literal(" to=") + p.until("<|message|>") +
|
||||
p.literal("<|message|><atem:function_calls>") + p.space() +
|
||||
p.literal("<atem:invoke name=\"") + p.tool_name(p.literal(name)) + p.literal("\">") + p.space())
|
||||
<< p.tool_args(args)
|
||||
<< p.tool_close(p.literal("</atem:invoke>") + p.space() + p.literal("</atem:function_calls>")));
|
||||
|
||||
tool_choice |= p.rule("tool-" + std::to_string(tool_index), tool_parser);
|
||||
});
|
||||
|
||||
auto tool_calls = inputs.parallel_tool_calls
|
||||
? p.trigger_rule("tool-call", tool_choice + p.zero_or_more(p.literal("<|eom|>") + start + tool_choice))
|
||||
: p.trigger_rule("tool-call", tool_choice);
|
||||
|
||||
|
||||
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
|
||||
return p.zero_or_more(start + aside) + start + tool_calls;
|
||||
}
|
||||
auto trailing_calls = p.optional(p.literal("<|eom|>") + start + tool_calls);
|
||||
return p.zero_or_more(start + aside) + start + (tool_calls | (final_msg + trailing_calls));
|
||||
}
|
||||
|
||||
return p.zero_or_more(start + aside) + start + final_msg;
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = 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);
|
||||
});
|
||||
data.grammar_triggers = {
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
|
||||
"<\\|start\\|>assistant( to=(?!self<\\|message\\|>)(?!commentary<\\|message\\|>)(?!user<\\|message\\|>)[^<]*?<\\|message\\|>)" },
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -59,6 +59,8 @@ common_chat_params common_chat_params_init_gigachat_v3(const common_chat_templat
|
||||
|
||||
common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_harmony_xml(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_k2_horizon(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
@@ -79,6 +81,7 @@ common_chat_params common_chat_params_init_minimax_m3(const common_chat_template
|
||||
common_chat_params common_chat_params_init_ministral_3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_muse_glimmer(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
common_chat_params common_chat_params_init_muse_spark(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_qwen3_coder(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ set(LLAMA_CHAT_PARSERS_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/gemma4.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/gigachat-v3.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/gpt-oss.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/harmony-xml.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/k2-horizon.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/kimi-k2.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/kimi-k3.cpp
|
||||
@@ -19,6 +20,7 @@ set(LLAMA_CHAT_PARSERS_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/minimax-m3.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ministral3.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/muse-glimmer.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/muse-spark.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/qwen3-coder.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/translate-gemma.cpp
|
||||
)
|
||||
|
||||
+104
-65
@@ -16,6 +16,7 @@
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <cinttypes>
|
||||
@@ -999,6 +1000,12 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
int32_t block_size = 0;
|
||||
llama_token mask_token_id = 0;
|
||||
|
||||
bool is_lilicorr = false;
|
||||
int32_t lilicorr_top_k = 0;
|
||||
size_t lilicorr_rows = 0;
|
||||
std::vector<float> lilicorr_embd;
|
||||
std::vector<llama_pos> lilicorr_pos;
|
||||
|
||||
bool is_dflash2 = false;
|
||||
bool is_mrope = false;
|
||||
int32_t selector_top_k = 0;
|
||||
@@ -1053,6 +1060,16 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
selector_top_k = llama_model_dflash_selector_top_k(model_dft);
|
||||
is_dflash2 = selector_top_k > 0;
|
||||
char lilicorr_meta[32] = {};
|
||||
lilicorr_top_k = llama_model_meta_val_str(model_dft, "lilicorr.candidate_topk", lilicorr_meta, sizeof(lilicorr_meta)) >= 0
|
||||
? std::atoi(lilicorr_meta) : 0;
|
||||
is_lilicorr = lilicorr_top_k > 0;
|
||||
if (is_lilicorr) {
|
||||
lilicorr_rows = (size_t) block_size + 1;
|
||||
const size_t lilicorr_slots = (size_t) n_seq * lilicorr_rows;
|
||||
lilicorr_embd.resize(lilicorr_slots * (size_t) n_embd_dec);
|
||||
lilicorr_pos.assign(lilicorr_slots, -1);
|
||||
}
|
||||
mask_token_id = llama_vocab_mask(llama_model_get_vocab(model_dft));
|
||||
|
||||
if (is_dspark && this->params.p_min > 0.0f) {
|
||||
@@ -1098,7 +1115,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
// offload draft sampling to the backend
|
||||
backend_chains.assign(n_seq, nullptr);
|
||||
if (this->params.backend_sampling && !is_dflash2) {
|
||||
if (this->params.backend_sampling && !is_dflash2 && !is_lilicorr) {
|
||||
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) n_seq; ++seq_id) {
|
||||
llama_sampler * chain = llama_sampler_chain_init(llama_sampler_chain_default_params());
|
||||
llama_sampler_chain_add(chain, llama_sampler_init_top_k(10));
|
||||
@@ -1118,7 +1135,7 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
}
|
||||
|
||||
// DFlash2 reads its selector lattice from h_nextn and never consumes raw logits.
|
||||
llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ !is_dflash2);
|
||||
llama_set_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 +1250,24 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
__func__, rc, (int) n_chunk, (int) offset);
|
||||
return false;
|
||||
}
|
||||
if (is_lilicorr) {
|
||||
// Ordinary DFlash can write to the KV cache, but LiliCorr needs to store embeddings across sequences.
|
||||
const float * projected = llama_get_embeddings_nextn(ctx_dft);
|
||||
if (projected) {
|
||||
const size_t seq_offset = (size_t) seq_id * lilicorr_rows;
|
||||
const llama_pos pos_min = std::max(0, batch_inject.tokens.back().pos[0] - block_size);
|
||||
for (int32_t i = 0; i < n_chunk; ++i) {
|
||||
const llama_pos pos = batch_inject.tokens[i].pos[0];
|
||||
if (pos < pos_min) {
|
||||
continue;
|
||||
}
|
||||
const size_t slot = seq_offset + (size_t) pos % lilicorr_rows;
|
||||
std::memcpy(lilicorr_embd.data() + slot * (size_t) n_embd_dec,
|
||||
projected + (size_t) i * n_embd_dec, (size_t) n_embd_dec * sizeof(float));
|
||||
lilicorr_pos[slot] = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1260,12 +1295,17 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
const int32_t n = (int32_t) dp.pos0;
|
||||
|
||||
const int32_t n_draft = params.n_max;
|
||||
|
||||
const int32_t n_block_tokens = n_draft + (is_dspark && sample_from_anchor ? 0 : 1);
|
||||
const int32_t n_block_tokens = is_lilicorr ? block_size : n_draft + (is_dspark && sample_from_anchor ? 0 : 1);
|
||||
i_block_beg[seq_id] = batch.size();
|
||||
n_block [seq_id] = n_block_tokens;
|
||||
for (int32_t i = 0; i < n_block_tokens; ++i) {
|
||||
batch.add(i == 0 ? dp.id_last : mask_token_id, n + i, seq_id, !is_dflash2);
|
||||
const int32_t idx = batch.add(i == 0 ? dp.id_last : mask_token_id, n + i, seq_id, !is_dflash2 && !is_lilicorr);
|
||||
if (is_lilicorr) {
|
||||
// For LiliCorr drafting batch, set state embeddings for use in the associated transformer head.
|
||||
const llama_pos pos = dp.pos0 - 1;
|
||||
const size_t slot = (size_t) seq_id * lilicorr_rows + (size_t) pos % lilicorr_rows;
|
||||
batch.set_embd_state(idx, { lilicorr_embd.data() + slot * (size_t) n_embd_dec, 1, (size_t) n_embd_dec });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,21 +1333,23 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
|
||||
|
||||
auto & result = *dp.result;
|
||||
|
||||
if (is_dflash2) {
|
||||
if (is_dflash2 || is_lilicorr) {
|
||||
// Both DFlash and LiliCorr greedily walk the candidate lattice.
|
||||
const float * lattice = llama_get_embeddings_nextn(ctx_dft);
|
||||
GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
|
||||
|
||||
int32_t predecessor = 0;
|
||||
for (int32_t i = 1; i < n_block_tokens; ++i) {
|
||||
for (int32_t i = 1; i < (is_dflash2 ? n_block_tokens : params.n_max + 1); ++i) {
|
||||
const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
|
||||
const float * scores = row + selector_top_k + (size_t) predecessor * selector_top_k;
|
||||
const int32_t top_k = is_dflash2 ? selector_top_k : lilicorr_top_k;
|
||||
const float * scores = row + top_k + (size_t) predecessor * top_k;
|
||||
|
||||
predecessor = (int32_t) std::distance(scores,
|
||||
std::max_element(scores, scores + selector_top_k));
|
||||
std::max_element(scores, scores + top_k));
|
||||
if (params.p_min > 0.0f) {
|
||||
// softmax(scores) at the argmax, i.e. 1 / sum(exp(s_k - s_max))
|
||||
float sum = 0.0f;
|
||||
for (int32_t k = 0; k < selector_top_k; ++k) {
|
||||
for (int32_t k = 0; k < top_k; ++k) {
|
||||
sum += std::exp(scores[k] - scores[predecessor]);
|
||||
}
|
||||
if (1.0f / sum < params.p_min) {
|
||||
@@ -2111,9 +2153,6 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
|
||||
|
||||
uint16_t n_draft;
|
||||
|
||||
bool save_dynamic;
|
||||
bool save_static;
|
||||
|
||||
struct seq_info {
|
||||
size_t cache_size = 0; // number of tokens in n-gram cache
|
||||
|
||||
@@ -2127,54 +2166,72 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
|
||||
common_speculative_impl_ngram_cache(
|
||||
const common_params_speculative & params,
|
||||
uint32_t n_seq,
|
||||
uint16_t n_draft,
|
||||
const std::string & path_static,
|
||||
const std::string & path_dynamic,
|
||||
bool save_dynamic,
|
||||
bool save_static)
|
||||
uint16_t n_draft)
|
||||
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_CACHE, n_seq, n_draft)
|
||||
, params(params.ngram_cache)
|
||||
, n_draft(n_draft)
|
||||
, save_dynamic(save_dynamic)
|
||||
, save_static(save_static)
|
||||
{
|
||||
const auto & ngram = params.ngram_cache;
|
||||
|
||||
SPC_TRC("%s", "adding speculative implementation 'ngram-cache'\n");
|
||||
SPC_TRC("- n_draft=%d, cache_static=%s, cache_dynamic=%s\n",
|
||||
n_draft,
|
||||
path_static.empty() ? "none" : path_static.c_str(),
|
||||
path_dynamic.empty() ? "none" : path_dynamic.c_str());
|
||||
ngram.lookup_cache_static.empty() ? "none" : ngram.lookup_cache_static.c_str(),
|
||||
ngram.lookup_cache_dynamic.empty() ? "none" : ngram.lookup_cache_dynamic.c_str());
|
||||
|
||||
sinfos.resize(n_seq);
|
||||
|
||||
if (!path_static.empty()) {
|
||||
if (!ngram.lookup_cache_static.empty()) {
|
||||
try {
|
||||
auto ngram_cache_static = common_ngram_cache_load(path_static);
|
||||
auto ngram_cache_static = common_ngram_cache_load(ngram.lookup_cache_static);
|
||||
|
||||
for (auto & sinfo : sinfos) {
|
||||
sinfo.ngram_cache_static = ngram_cache_static;
|
||||
}
|
||||
} catch (...) {
|
||||
SPC_ERR("failed to open static lookup cache: %s", path_static.c_str());
|
||||
} catch (const std::ifstream::failure &) {
|
||||
SPC_ERR("failed to open static lookup cache: %s\n", ngram.lookup_cache_static.c_str());
|
||||
GGML_ABORT("Couldn't read static lookup cache");
|
||||
}
|
||||
}
|
||||
|
||||
if (!path_dynamic.empty()) {
|
||||
if (!ngram.lookup_cache_dynamic.empty()) {
|
||||
try {
|
||||
auto ngram_cache_dynamic = common_ngram_cache_load(path_dynamic);
|
||||
auto ngram_cache_dynamic = common_ngram_cache_load(ngram.lookup_cache_dynamic);
|
||||
|
||||
for (auto & sinfo : sinfos) {
|
||||
sinfo.ngram_cache_dynamic = ngram_cache_dynamic;
|
||||
}
|
||||
} catch (...) {
|
||||
SPC_ERR("failed to open dynamic lookup cache: %s", path_dynamic.c_str());
|
||||
GGML_ABORT("Couldn't read dynamic lookup cache");
|
||||
} catch (const std::ifstream::failure &) {
|
||||
std::error_code ec;
|
||||
const bool exists = std::filesystem::exists(ngram.lookup_cache_dynamic, ec);
|
||||
if (!exists && !ec) {
|
||||
// missing file is created on destruction
|
||||
SPC_WRN("dynamic lookup cache file does not exist: %s, starting with empty cache\n",
|
||||
ngram.lookup_cache_dynamic.c_str());
|
||||
} else {
|
||||
SPC_ERR("failed to open dynamic lookup cache: %s\n", ngram.lookup_cache_dynamic.c_str());
|
||||
GGML_ABORT("Couldn't read dynamic lookup cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void begin(llama_seq_id /*seq_id*/, const llama_tokens & /*prompt*/) override {
|
||||
// noop
|
||||
~common_speculative_impl_ngram_cache() override {
|
||||
if (params.lookup_cache_dynamic.empty() || sinfos.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto & dynamic_cache = sinfos.front().ngram_cache_dynamic;
|
||||
for (auto & sinfo : sinfos) {
|
||||
common_ngram_cache_merge(dynamic_cache, sinfo.ngram_cache_context);
|
||||
}
|
||||
|
||||
SPC_INF("saving dynamic lookup cache at %s\n", params.lookup_cache_dynamic.c_str());
|
||||
common_ngram_cache_save(dynamic_cache, params.lookup_cache_dynamic);
|
||||
}
|
||||
|
||||
void begin(llama_seq_id seq_id, const llama_tokens & /*prompt*/) override {
|
||||
sinfos[seq_id].cache_size = 0;
|
||||
}
|
||||
|
||||
void draft_one(
|
||||
@@ -2185,22 +2242,6 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
|
||||
|
||||
const auto & prompt = *dparams.prompt;
|
||||
|
||||
if (sinfo.cache_size < prompt.size() + 1) {
|
||||
llama_tokens tokens_new;
|
||||
tokens_new.reserve(prompt.size() + 1 - sinfo.cache_size);
|
||||
for (size_t j = sinfo.cache_size; j < prompt.size(); ++j) {
|
||||
tokens_new.push_back(prompt[j]);
|
||||
}
|
||||
tokens_new.push_back(dparams.id_last); // add the last token
|
||||
|
||||
// Update context ngram cache with new dparams.prompt:
|
||||
common_ngram_cache_update(
|
||||
sinfo.ngram_cache_context,
|
||||
LLAMA_NGRAM_MIN, LLAMA_NGRAM_MAX,
|
||||
tokens_new, tokens_new.size(), false);
|
||||
sinfo.cache_size = prompt.size() + 1;
|
||||
}
|
||||
|
||||
llama_tokens inp;
|
||||
inp.reserve(prompt.size() + 1);
|
||||
for (size_t j = 0; j < prompt.size(); ++j) {
|
||||
@@ -2208,6 +2249,16 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
|
||||
}
|
||||
inp.push_back(dparams.id_last);
|
||||
|
||||
if (sinfo.cache_size < inp.size()) {
|
||||
// Update context ngram cache with the full history so n-grams
|
||||
// ending at the new tokens have their preceding context
|
||||
common_ngram_cache_update(
|
||||
sinfo.ngram_cache_context,
|
||||
LLAMA_NGRAM_MIN, LLAMA_NGRAM_MAX,
|
||||
inp, inp.size() - sinfo.cache_size, false);
|
||||
sinfo.cache_size = inp.size();
|
||||
}
|
||||
|
||||
result.push_back(dparams.id_last);
|
||||
|
||||
common_ngram_cache_draft(
|
||||
@@ -2268,20 +2319,12 @@ static common_ngram_map get_common_ngram_map(
|
||||
return common_ngram_map(size_key, size_value, key_only, min_hits);
|
||||
}
|
||||
|
||||
static common_speculative_impl_ngram_cache create_state_ngram_cache(
|
||||
static std::unique_ptr<common_speculative_impl_ngram_cache> create_state_ngram_cache(
|
||||
const common_speculative_config & config,
|
||||
uint32_t n_seq,
|
||||
const std::string & path_static,
|
||||
const std::string & path_dynamic) {
|
||||
uint32_t n_seq) {
|
||||
uint16_t n_draft = 8; // TODO get from config?
|
||||
|
||||
// TODO bool param in common/common.h to set save_static/save_dynamic?
|
||||
bool save_static = false;
|
||||
bool save_dynamic = false;
|
||||
|
||||
common_speculative_impl_ngram_cache state(config.params, n_seq, n_draft, path_static, path_dynamic, save_static, save_dynamic);
|
||||
|
||||
return state;
|
||||
return std::make_unique<common_speculative_impl_ngram_cache>(config.params, n_seq, n_draft);
|
||||
}
|
||||
|
||||
std::string common_speculative_type_name_str(const std::vector<common_speculative_type> & types) {
|
||||
@@ -2781,11 +2824,7 @@ common_speculative * common_speculative_init(common_params_speculative & params,
|
||||
break;
|
||||
}
|
||||
case COMMON_SPECULATIVE_TYPE_NGRAM_CACHE: {
|
||||
auto state = create_state_ngram_cache(
|
||||
config, n_seq,
|
||||
params.ngram_cache.lookup_cache_static,
|
||||
params.ngram_cache.lookup_cache_dynamic);
|
||||
impls.push_back(std::make_unique<common_speculative_impl_ngram_cache>(state));
|
||||
impls.push_back(create_state_ngram_cache(config, n_seq));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -24,6 +24,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"ArcticForCausalLM": "arctic",
|
||||
"AudioFlamingo3ForConditionalGeneration": "qwen",
|
||||
"BaiChuanForCausalLM": "baichuan",
|
||||
"BeamForCausalLM": "beam",
|
||||
"BaichuanForCausalLM": "baichuan",
|
||||
"BailingMoeForCausalLM": "bailingmoe",
|
||||
"BailingMoeV2ForCausalLM": "bailingmoe",
|
||||
@@ -59,6 +60,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"DeepseekV32ForCausalLM": "deepseek",
|
||||
"DFlashDraftModel": "qwen",
|
||||
"DFlash2DraftModel": "qwen",
|
||||
"LiLiCorrDraftModel": "nemotron",
|
||||
"Qwen3DSparkModel": "qwen",
|
||||
"DSparkDraftModel": "qwen",
|
||||
"DSparkSpeculator": "qwen",
|
||||
@@ -144,6 +146,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"JinaBertModel": "bert",
|
||||
"JinaEmbeddingsV5Model": "bert",
|
||||
"K2HorizonForCausalLM": "k2_horizon",
|
||||
"Kolibri1ForCausalLM": "kolibri1",
|
||||
"KORMoForCausalLM": "qwen",
|
||||
"KimiK25ForConditionalGeneration": "deepseek",
|
||||
"KimiK3ForConditionalGeneration": "kimi_k3",
|
||||
@@ -198,6 +201,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"MiniMaxM3SparseForConditionalGeneration": "minimax",
|
||||
"Ministral3ForCausalLM": "mistral3",
|
||||
"Mistral3ForConditionalGeneration": "mistral3",
|
||||
"VoxtralRealtimeForConditionalGeneration": "mistral3",
|
||||
"MistralForCausalLM": "llama",
|
||||
"MixtralForCausalLM": "llama",
|
||||
"ModernBertDecisionModel": "bert",
|
||||
@@ -219,6 +223,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"OlmoeForCausalLM": "olmo",
|
||||
"MuseGlimmerAssistantModel": "muse_glimmer",
|
||||
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
|
||||
"MuseSparkForConditionalGeneration": "muse_spark",
|
||||
"OpenELMForCausalLM": "openelm",
|
||||
"OpenJevModel": "qwen",
|
||||
"OrionForCausalLM": "orion",
|
||||
@@ -352,8 +357,10 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
|
||||
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
|
||||
"Mistral3ForConditionalGeneration": "llava",
|
||||
"NemotronH_Omni_Reasoning_V3": "nemotron",
|
||||
"NemotronH_Nano_VL_V2": "nemotron",
|
||||
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
|
||||
"MuseSparkForConditionalGeneration": "muse_spark",
|
||||
"PaddleOCRVisionModel": "ernie",
|
||||
"Phi4ForCausalLMV": "phi",
|
||||
"Qwen2AudioForConditionalGeneration": "ultravox",
|
||||
@@ -379,6 +386,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"UltravoxModel": "ultravox",
|
||||
"UnlimitedOCRForCausalLM": "deepseek",
|
||||
"VoxtralForConditionalGeneration": "ultravox",
|
||||
"VoxtralRealtimeForConditionalGeneration": "ultravox",
|
||||
"YoutuVLForConditionalGeneration": "youtuvl",
|
||||
}
|
||||
|
||||
|
||||
+42
-12
@@ -10,6 +10,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
@@ -568,7 +569,7 @@ class ModelBase:
|
||||
tensors_to_remove.append(base_name + "_zero_point")
|
||||
else:
|
||||
raise NotImplementedError(f"Quant format {quant_format!r} for method {quant_method!r} is not yet supported")
|
||||
elif quant_method == "modelopt":
|
||||
elif quant_method in ("modelopt", "modelopt_mixed"):
|
||||
dequant_fp8()
|
||||
elif quant_method is not None:
|
||||
raise NotImplementedError(f"Quant method is not yet supported: {quant_method!r}")
|
||||
@@ -1625,7 +1626,7 @@ class TextModel(ModelBase):
|
||||
self.gguf_writer.add_head_count_kv(n_head_kv)
|
||||
logger.info(f"gguf: key-value head count = {n_head_kv}")
|
||||
|
||||
if self.hparams.get("is_causal") is False:
|
||||
if self.hparams.get("is_causal") is False or self.hparams.get("use_bidirectional_attention") is True:
|
||||
self.gguf_writer.add_causal_attention(False)
|
||||
logger.info("gguf: causal attention = False")
|
||||
|
||||
@@ -1869,6 +1870,12 @@ class TextModel(ModelBase):
|
||||
if chkhsh == "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273":
|
||||
# ref: https://huggingface.co/alvarobartt/grok-2-tokenizer
|
||||
res = "grok-2"
|
||||
if chkhsh == "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc":
|
||||
# ref: https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B
|
||||
res = "gpt-4o"
|
||||
if chkhsh == "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78":
|
||||
# ref: https://huggingface.co/Aleph-Alpha/Kolibri-1
|
||||
res = "qwen2"
|
||||
if chkhsh == "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df":
|
||||
# ref: https://huggingface.co/aari1995/German_Semantic_V3
|
||||
res = "jina-v2-de"
|
||||
@@ -2163,20 +2170,34 @@ class TextModel(ModelBase):
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def _is_hybriddna(self) -> bool:
|
||||
tokenizer_config_file = self.dir_model / "tokenizer_config.json"
|
||||
if not tokenizer_config_file.is_file():
|
||||
return False
|
||||
with open(tokenizer_config_file, "r", encoding="utf-8") as f:
|
||||
return json.load(f).get("tokenizer_class") == "HybridDNATokenizer"
|
||||
|
||||
def _set_vocab_hybriddna(self):
|
||||
from transformers import AutoTokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
|
||||
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]
|
||||
vocab = tokenizer.get_vocab()
|
||||
vocab_size = self.hparams.get("vocab_size", len(vocab))
|
||||
assert max(vocab.values()) < vocab_size
|
||||
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in tokenizer.vocab.items()} # ty: ignore[unresolved-attribute]
|
||||
# k-mers can share text with a base-vocab BPE token (e.g. CCCCCC) and get
|
||||
# dropped by get_vocab(); a reserved marker suffix (U+E000) keeps each
|
||||
# k-mer's own id (llama.cpp strips it on detokenization)
|
||||
for kmer in tokenizer.kmers: # ty: ignore[unresolved-attribute]
|
||||
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in vocab.items()}
|
||||
# k-mers can share text with a base-vocab BPE token (e.g. CCCCCC) and get dropped by get_vocab()
|
||||
# a reserved marker suffix (U+E000) keeps each k-mer's own id (llama.cpp strips it on detokenization)
|
||||
# tail="pad_n" (Carbon-14B) adds N-padded tail tokens ("CT" -> "CTNNNN") that get the same marker
|
||||
# llama.cpp detects them to pad with N and strips the padding on detokenization, tail="char" is unsupported
|
||||
tail = getattr(tokenizer, "tail", "pad_a")
|
||||
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]
|
||||
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
|
||||
added_tokens_decoder = tokenizer.added_tokens_decoder # ty: ignore[unresolved-attribute]
|
||||
# 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]
|
||||
|
||||
tokens: list[str] = []
|
||||
toktypes: list[int] = []
|
||||
@@ -2186,7 +2207,7 @@ class TextModel(ModelBase):
|
||||
toktypes.append(gguf.TokenType.UNUSED)
|
||||
else:
|
||||
token: str = reverse_vocab[i]
|
||||
if token in added_vocab:
|
||||
if i in added_tokens_decoder:
|
||||
if added_tokens_decoder[i].special or self.does_token_look_special(token):
|
||||
toktypes.append(gguf.TokenType.CONTROL)
|
||||
else:
|
||||
@@ -2198,10 +2219,19 @@ class TextModel(ModelBase):
|
||||
tokpre = self.get_vocab_base_pre(tokenizer)
|
||||
self.gguf_writer.add_tokenizer_model("hybriddna")
|
||||
self.gguf_writer.add_tokenizer_pre(tokpre)
|
||||
self.gguf_writer.add_dna_auto_tags(getattr(tokenizer, "auto_dna_tags", False))
|
||||
self.gguf_writer.add_token_list(tokens)
|
||||
self.gguf_writer.add_token_types(toktypes)
|
||||
|
||||
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
|
||||
if not special_vocab.merges:
|
||||
# Carbon-500M/3B/8B ship only tokenizer.py, no tokenizer.json
|
||||
# take the merges and special token ids from the base BPE tokenizer, keeping the model's own chat template (if any)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base_tokenizer.save_pretrained(tmp)
|
||||
base_special_vocab = gguf.SpecialVocab(Path(tmp), load_merges=True)
|
||||
base_special_vocab.chat_template = special_vocab.chat_template
|
||||
special_vocab = base_special_vocab
|
||||
special_vocab.add_to_gguf(self.gguf_writer)
|
||||
|
||||
def _set_vocab_qwen(self):
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import ModelBase, TextModel, gguf, logger
|
||||
|
||||
|
||||
@ModelBase.register("BeamForCausalLM")
|
||||
class BeamModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.BEAM
|
||||
|
||||
# reference NoPE query-scale constants; not in config.json
|
||||
NOPE_SCALE_POWER = 0.1
|
||||
NOPE_SCALE_LENGTH = 2048
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hp = self.hparams
|
||||
rope = hp.get("rope_parameters") or {}
|
||||
if rope.get("full_attention") is not None:
|
||||
raise ValueError("Beam: expected NoPE full-attention layers (rope_parameters.full_attention = null)")
|
||||
if "sliding_attention" in rope:
|
||||
swa_rope = rope["sliding_attention"] or {}
|
||||
else:
|
||||
# flat rope_theta / rope_scaling, already normalized by TextModel
|
||||
swa_rope = {k: v for k, v in self.rope_parameters.items() if k not in ("sliding_attention", "full_attention", "type")}
|
||||
swa_rope.update(self.rope_parameters.get("sliding_attention") or {})
|
||||
if swa_rope.get("rope_type", "default") != "default":
|
||||
raise ValueError(f"Beam: unsupported sliding-attention rope type {swa_rope.get('rope_type')!r}")
|
||||
# full layers are NoPE, so mirror the sliding rope params for the base class
|
||||
swa_rope = {k: v for k, v in swa_rope.items() if not (k == "rope_type" and v == "default")}
|
||||
self.rope_parameters = {"sliding_attention": dict(swa_rope), "full_attention": dict(swa_rope)}
|
||||
|
||||
layer_types = hp["layer_types"]
|
||||
mlp_types = hp["mlp_layer_types"]
|
||||
n_layer = hp["num_hidden_layers"]
|
||||
if len(layer_types) != n_layer or len(mlp_types) != n_layer:
|
||||
raise ValueError("Beam: layer_types / mlp_layer_types must have num_hidden_layers entries")
|
||||
if any(t not in ("sliding_attention", "full_attention") for t in layer_types):
|
||||
raise ValueError(f"Beam: unknown layer type in {layer_types}")
|
||||
if "sliding_attention" not in layer_types:
|
||||
raise ValueError("Beam: expected at least one sliding_attention layer")
|
||||
n_dense = 0
|
||||
while n_dense < n_layer and mlp_types[n_dense] == "dense":
|
||||
n_dense += 1
|
||||
if any(t != "sparse" for t in mlp_types[n_dense:]):
|
||||
raise ValueError("Beam: llama.cpp supports dense MLP layers only as a leading block")
|
||||
self._n_dense_lead = n_dense
|
||||
if hp.get("attention_bias", False):
|
||||
raise ValueError("Beam: attention_bias=True is not supported")
|
||||
self._experts: dict[tuple[int, str], dict[int, Tensor]] = {}
|
||||
|
||||
def set_vocab(self):
|
||||
self._set_vocab_gpt2()
|
||||
# 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]
|
||||
if isinstance(ret_id, int) and ret_id >= 0:
|
||||
self.gguf_writer.add_eot_token_id(ret_id)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hp = self.hparams
|
||||
|
||||
head_dim = hp["head_dim"]
|
||||
self.gguf_writer.add_rope_dimension_count(head_dim)
|
||||
|
||||
self.gguf_writer.add_sliding_window(hp["sliding_window"])
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in hp["layer_types"]])
|
||||
|
||||
self.gguf_writer.add_attn_temperature_scale(self.NOPE_SCALE_POWER)
|
||||
self.gguf_writer.add_attn_temperature_length(self.NOPE_SCALE_LENGTH)
|
||||
|
||||
self.gguf_writer.add_leading_dense_block_count(self._n_dense_lead)
|
||||
self.gguf_writer.add_expert_feed_forward_length(hp["moe_intermediate_size"])
|
||||
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
|
||||
self.gguf_writer.add_expert_weights_norm(bool(hp.get("norm_topk_prob", True)))
|
||||
# equivalent to the reference's post-aggregation scaling
|
||||
self.gguf_writer.add_expert_weights_scale(float(hp["routed_scaling_factor"]))
|
||||
|
||||
if (softcap := hp.get("final_logit_softcapping")) is not None:
|
||||
self.gguf_writer.add_final_logit_softcapping(float(softcap))
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
if name.endswith(".self_attn.gate_bias"):
|
||||
name = name.replace(".self_attn.gate_bias", ".self_attn.gate_proj.bias")
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
hp = self.hparams
|
||||
if name.endswith(".self_attn.gate_proj.bias"):
|
||||
assert tuple(data_torch.shape) == (hp["num_attention_heads"],), f"{name}: one gate bias per head expected, got {tuple(data_torch.shape)}"
|
||||
elif name.endswith(".self_attn.gate_proj.weight"):
|
||||
assert tuple(data_torch.shape) == (hp["num_attention_heads"] * hp["head_dim"], hp["hidden_size"]), f"{name}: {tuple(data_torch.shape)}"
|
||||
elif name.endswith(".mlp.gate.e_score_correction.bias"):
|
||||
assert tuple(data_torch.shape) == (hp["num_experts"],), f"{name}: {tuple(data_torch.shape)}"
|
||||
elif name.endswith((".self_attn.q_norm.weight", ".self_attn.k_norm.weight")):
|
||||
assert tuple(data_torch.shape) == (hp["head_dim"],), f"{name}: per-head q/k norm expected, got {tuple(data_torch.shape)}"
|
||||
elif bid is not None and bid < self._n_dense_lead and ".mlp.experts." in name:
|
||||
raise ValueError(f"Beam: expert tensor {name} in a dense layer")
|
||||
if ".mlp.experts." in name:
|
||||
assert bid is not None
|
||||
n_experts = self.hparams["num_experts"]
|
||||
parts = name.split(".")
|
||||
xid = int(parts[parts.index("experts") + 1])
|
||||
proj = parts[parts.index("experts") + 2]
|
||||
if proj not in ("gate_proj", "up_proj", "down_proj"):
|
||||
raise ValueError(f"Beam: unexpected expert tensor {name}")
|
||||
bucket = self._experts.setdefault((bid, proj), {})
|
||||
if xid in bucket:
|
||||
raise ValueError(f"Beam: duplicate expert tensor {name}")
|
||||
bucket[xid] = data_torch
|
||||
if len(bucket) < n_experts:
|
||||
return
|
||||
del self._experts[(bid, proj)]
|
||||
data = torch.stack([bucket[e] for e in range(n_experts)], dim=0)
|
||||
yield from super().modify_tensors(data, f"model.layers.{bid}.mlp.experts.{proj}.weight", bid)
|
||||
return
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def prepare_tensors(self):
|
||||
super().prepare_tensors()
|
||||
if self._experts:
|
||||
missing = sorted(f"layer {b} {p} ({len(v)} experts)" for (b, p), v in self._experts.items())
|
||||
raise ValueError(f"Beam: unprocessed experts: {missing}")
|
||||
logger.info("Beam: all routed experts stacked")
|
||||
@@ -243,7 +243,7 @@ class DeepseekV2Model(TextModel):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hparams: dict = ModelBase.load_hparams(self.dir_model, is_mistral_format=False)
|
||||
hparams: dict = ModelBase.load_hparams(self.dir_model, is_mistral_format=self.is_mistral_format)
|
||||
self.origin_hf_arch = hparams.get('architectures', [None])[0]
|
||||
|
||||
# special handling for Deepseek OCR
|
||||
|
||||
@@ -999,6 +999,8 @@ class Gemma4VisionAudioModel(MmprojModel):
|
||||
assert self.hparams_vision is not None
|
||||
self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.GEMMA4V)
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("layer_norm_eps", 1e-6))
|
||||
assert self.hparams_vision.get("hidden_activation", "gelu_pytorch_tanh") == "gelu_pytorch_tanh"
|
||||
self.gguf_writer.add_vision_use_gelu(True)
|
||||
|
||||
# audio params
|
||||
if self.has_audio_encoder:
|
||||
|
||||
+46
-3
@@ -8,7 +8,7 @@ import torch
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import MmprojModel, ModelBase, gguf, logger
|
||||
from .base import MmprojModel, ModelBase, gguf, jinja_str_or_json, logger
|
||||
|
||||
from .llama import LlamaModel
|
||||
from .mamba import Mamba2Model
|
||||
@@ -239,8 +239,21 @@ class GraniteSwitchModel(GraniteMoeModel):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# the weightless switch reserves one cache slot: one fewer block than num_hidden_layers
|
||||
self.block_count = self.block_count - 1
|
||||
# The weightless switch layer(s) are counted in num_hidden_layers but carry no tensors.
|
||||
# The count is not fixed, nor is it present in hparams, so we derive it here based on tensor block names.
|
||||
layer_ids = {
|
||||
int(m.group(1))
|
||||
for name in self.model_tensors
|
||||
if (m := re.match(r"(?:model\.)?layers\.(\d+)\.", name))
|
||||
}
|
||||
if not layer_ids:
|
||||
raise ValueError("graniteswitch: no 'model.layers.N' tensors found in checkpoint")
|
||||
n_weight_layers = max(layer_ids) + 1
|
||||
logger.info(
|
||||
"gguf: (graniteswitch) num_hidden_layers %s counts %d weightless switch layer(s); using %d blocks",
|
||||
self.hparams["num_hidden_layers"], self.block_count - n_weight_layers, n_weight_layers,
|
||||
)
|
||||
self.block_count = n_weight_layers
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
|
||||
|
||||
self._n_adapters = int(self.hparams["num_adapters"])
|
||||
@@ -271,10 +284,40 @@ class GraniteSwitchModel(GraniteMoeModel):
|
||||
self.gguf_writer.add_adapter_lora_rank(self._max_lora_rank)
|
||||
self.gguf_writer.add_adapter_token_ids_activate(self.hparams["adapter_token_ids"])
|
||||
self.gguf_writer.add_adapter_token_ids_substitute(self.hparams["adapter_substitute_token_ids"])
|
||||
if self._has_decision_adapter():
|
||||
self.gguf_writer.add_decision_type(gguf.DecisionType.GRANITE_SWITCH)
|
||||
router_gain = float(self.hparams.get("control_token_gain", 15.0))
|
||||
self.gguf_writer.add_adapter_router_gain(router_gain)
|
||||
logger.info("gguf: (graniteswitch) num_adapters=%s max_lora_rank=%s n_slots=%s router_gain=%s", self._n_adapters, self._max_lora_rank, self._n_slots, router_gain)
|
||||
|
||||
def _has_decision_adapter(self) -> bool:
|
||||
return "decision" in self.hparams.get("adapter_names", [])
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
if self._has_decision_adapter():
|
||||
systemone_template = self._systemone_template()
|
||||
logger.info("System One Template: %s", systemone_template)
|
||||
self.gguf_writer.add_chat_template([{"name": "systemone", "template": systemone_template}])
|
||||
|
||||
def _systemone_template(self) -> str:
|
||||
# prompt of the decision aLoRA: a state turn, then a question turn that opens with the control token
|
||||
# (it replaces the first token of "### DECISION"), the answer is the label token that comes next
|
||||
description = jinja_str_or_json("o.description")
|
||||
option = (
|
||||
"{% if type != 'noul' %}{{ o.label }}: {% if o.description %}" + description + "{% endif %}"
|
||||
"{% elif o.key == 'true' %}A: {% if o.description %}" + description + "{% else %}The statement is true.{% endif %}"
|
||||
"{% else %}B: {% if o.description %}" + description + "{% else %}The statement is false.{% endif %}{% endif %}"
|
||||
)
|
||||
return (
|
||||
"<|start_of_role|>user<|end_of_role|>State:\n" + jinja_str_or_json("state") + "<|end_of_text|>\n"
|
||||
"<|start_of_role|>user<|end_of_role|><|decision|> DECISION\nQuestion:\n" + jinja_str_or_json("instructions")
|
||||
+ "{% if type == 'score' %} Rate along the ordered levels below (lowest first).{% endif %}"
|
||||
"{{ '\n\nOptions:\n' }}"
|
||||
"{% for o in options %}" + option + "{{ '\n' }}{% endfor %}"
|
||||
"{{ '\nReply with exactly one option label.<|end_of_text|>\n<|start_of_role|>assistant<|end_of_role|>' }}"
|
||||
)
|
||||
|
||||
def _lora_a(self, data: Tensor) -> Tensor:
|
||||
# on-disk A: [n_adapters, 1, max_rank, in] -> [n_adapters+1, max_rank, in]
|
||||
a = data.squeeze(1)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .afmoe import AfmoeModel
|
||||
from .base import ModelBase, gguf
|
||||
|
||||
|
||||
@ModelBase.register("Kolibri1ForCausalLM")
|
||||
@ModelBase.example("Aleph-Alpha/Kolibri-1")
|
||||
class Kolibri1Model(AfmoeModel):
|
||||
model_arch = gguf.MODEL_ARCH.KOLIBRI1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# AfmoeModel reads these keys, this config stores the same values under other names
|
||||
self.hparams["num_shared_experts"] = 1
|
||||
self.hparams["route_norm"] = self.hparams["norm_topk_prob"]
|
||||
self.hparams["intermediate_size"] = self.hparams["moe_intermediate_size"]
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_expert_shared_feed_forward_length(self.hparams["shared_expert_intermediate_size"])
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in self.hparams["layer_types"]])
|
||||
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
|
||||
@@ -374,6 +374,16 @@ class LFM2VLModel(MmprojModel):
|
||||
if resize_algo is None:
|
||||
raise ValueError(f"unsupported resample: {resample}")
|
||||
self.gguf_writer.add_vision_image_resize_algo(resize_algo)
|
||||
# tiling params from processor_config.json (do_image_splitting=False -> single tile, i.e. no tiling)
|
||||
preproc = self.preprocessor_config
|
||||
if preproc.get("do_image_splitting", True):
|
||||
self.gguf_writer.add_vision_preproc_min_tiles(preproc.get("min_tiles", 2))
|
||||
self.gguf_writer.add_vision_preproc_max_tiles(preproc.get("max_tiles", 10))
|
||||
else:
|
||||
self.gguf_writer.add_vision_preproc_min_tiles(1)
|
||||
self.gguf_writer.add_vision_preproc_max_tiles(1)
|
||||
self.gguf_writer.add_vision_preproc_tile_size(preproc.get("tile_size", 512))
|
||||
self.gguf_writer.add_vision_preproc_max_pixels_tolerance(preproc.get("max_pixels_tolerance", 2.0))
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
|
||||
+3
-2
@@ -129,8 +129,9 @@ class LlamaModel(TextModel):
|
||||
tokenizer_config_json = json.load(f)
|
||||
if (add_prefix_space := tokenizer_config_json.get("add_prefix_space")) is not None:
|
||||
self.gguf_writer.add_add_space_prefix(add_prefix_space)
|
||||
if tokenizer_config_json.get("tokenizer_class") == "HybridDNATokenizer":
|
||||
return self._set_vocab_hybriddna()
|
||||
|
||||
if self._is_hybriddna():
|
||||
return self._set_vocab_hybriddna()
|
||||
|
||||
try:
|
||||
self._set_vocab_sentencepiece()
|
||||
|
||||
+73
-4
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, TYPE_CHECKING
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
@@ -14,6 +14,54 @@ from .base import ModelBase, TextModel, gguf, logger
|
||||
@ModelBase.example("JetBrains/Mellum2-12B-A2.5B-Base")
|
||||
class MellumModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.MELLUM
|
||||
supports_mtp_export = True
|
||||
|
||||
_n_main_layers: int | None = None
|
||||
|
||||
_experts: list[dict[str, Tensor]] | None = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# number of exported MTP blocks, appended after the trunk layers
|
||||
self.n_nextn = 0 if self.no_mtp else (self.find_hparam(["num_nextn_predict_layers", "num_mtp_layers"], optional=True) or 0)
|
||||
if self.mtp_only and self.n_nextn == 0:
|
||||
raise ValueError("--mtp requested, but the checkpoint has no MTP layers (num_nextn_predict_layers is 0)")
|
||||
if self.n_nextn > 0:
|
||||
if any(t != "sparse" for t in self.hparams.get("mtp_mlp_layer_types") or []):
|
||||
raise ValueError("Mellum MTP layers with dense MLP are not supported")
|
||||
self.block_count += self.n_nextn
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None):
|
||||
type(self)._n_main_layers = self.hparams["num_hidden_layers"]
|
||||
return super().index_tensors(remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
if (titem := super().filter_tensors(item)) is None:
|
||||
return None
|
||||
name, gen = titem
|
||||
|
||||
if name.startswith("mtp."):
|
||||
if cls.no_mtp:
|
||||
return None
|
||||
assert cls._n_main_layers is not None
|
||||
# mtp.layers.{i}.* -> model.layers.{n_main + i}.*, mtp.{fc,norm,...} -> nextn names of the first MTP block
|
||||
remapper = {
|
||||
"fc": "eh_proj",
|
||||
"pre_fc_norm_embedding": "enorm",
|
||||
"pre_fc_norm_hidden": "hnorm",
|
||||
"norm": "shared_head.norm",
|
||||
}
|
||||
parts = name.split(".", 3)
|
||||
if len(parts) == 4 and parts[1] == "layers" and parts[2].isdecimal():
|
||||
name = f"model.layers.{cls._n_main_layers + int(parts[2])}.{parts[3]}"
|
||||
elif len(parts) == 3 and parts[1] in remapper:
|
||||
name = f"model.layers.{cls._n_main_layers}.{remapper[parts[1]]}.{parts[2]}"
|
||||
elif cls.mtp_only and name not in ("model.embed_tokens.weight", "model.norm.weight", "lm_head.weight"):
|
||||
return None
|
||||
|
||||
return name, gen
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
@@ -26,10 +74,31 @@ class MellumModel(TextModel):
|
||||
if (use_sliding_window is True or use_sliding_window is None) and sliding_window is not None:
|
||||
self.gguf_writer.add_sliding_window(sliding_window)
|
||||
logger.info(f"gguf: sliding window = {sliding_window}")
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in self.hparams["layer_types"]])
|
||||
logger.info(f"gguf: sliding window pattern length = {len(self.hparams['layer_types'])}")
|
||||
layer_types = list(self.hparams["layer_types"])
|
||||
# MTP blocks default to sliding window attention, as in transformers
|
||||
if self.n_nextn > 0:
|
||||
layer_types += self.hparams.get("mtp_layer_types") or ["sliding_attention"] * self.n_nextn
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in layer_types])
|
||||
logger.info(f"gguf: sliding window pattern length = {len(layer_types)}")
|
||||
|
||||
_experts: list[dict[str, Tensor]] | None = None
|
||||
if (norm_topk_prob := self.hparams.get("norm_topk_prob")) is not None:
|
||||
self.gguf_writer.add_expert_weights_norm(norm_topk_prob)
|
||||
|
||||
if self.n_nextn > 0:
|
||||
self.gguf_writer.add_nextn_predict_layers(self.n_nextn)
|
||||
|
||||
def prepare_metadata(self, vocab_only: bool):
|
||||
from_dir = self.fname_out.is_dir()
|
||||
super().prepare_metadata(vocab_only=vocab_only)
|
||||
|
||||
if not self.mtp_only or not from_dir:
|
||||
return
|
||||
|
||||
output_type: str = self.ftype.name.partition("_")[2]
|
||||
fname_default: str = gguf.naming_convention(
|
||||
self.metadata.name, self.metadata.basename, self.metadata.finetune,
|
||||
self.metadata.version, size_label=None, output_type=output_type, model_type=None)
|
||||
self.fname_out = self.fname_out.parent / f"mtp-{fname_default}.gguf"
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.find("experts") != -1:
|
||||
|
||||
@@ -66,3 +66,13 @@ class Mistral3Model(TextModel):
|
||||
|
||||
def write(self):
|
||||
self.impl.write()
|
||||
|
||||
|
||||
@ModelBase.register("VoxtralRealtimeForConditionalGeneration")
|
||||
@ModelBase.example("mistralai/Voxtral-Mini-4B-Realtime-2602")
|
||||
class VoxtralRealtimeModel(LlamaModel):
|
||||
model_arch = gguf.MODEL_ARCH.MISTRAL3
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_realtime_delay_tokens(self.hparams["default_num_delay_tokens"])
|
||||
|
||||
@@ -94,7 +94,7 @@ class MuseGlimmerVisionModel(MmprojModel):
|
||||
assert self.hparams_vision is not None
|
||||
c = self.hparams_vision # enriched vision_config from get_vision_config()
|
||||
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.MUSE_GLIMMER)
|
||||
self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.MUSE_GLIMMER)
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(float(c["layer_norm_eps"]))
|
||||
self.gguf_writer.add_vision_spatial_merge_size(int(c["merge_size"]))
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import ModelBase, MmprojModel, TextModel, gguf
|
||||
from .muse_glimmer import MuseGlimmerVisionModel
|
||||
|
||||
|
||||
# The reference clamps the squared stream coefficient with this floor.
|
||||
RESIDUAL_GATE_EPS = 1e-3
|
||||
|
||||
# Block norms hold a delta around one; the output norm holds an absolute gain.
|
||||
OFFSET_NORM_SUFFIXES = (
|
||||
"input_layernorm.weight",
|
||||
"pre_feedforward_layernorm.weight",
|
||||
"post_feedforward_layernorm.weight",
|
||||
"pre_expert_norm.weight",
|
||||
"post_expert_norm.weight",
|
||||
)
|
||||
|
||||
|
||||
def _unpermute_for_rope(tensor: "Tensor", n_heads: int) -> "Tensor":
|
||||
"""Invert transformers' `_permute_for_rope`: HF stores Q/K in rotate_half layout,
|
||||
llama.cpp consumes the interleaved (NORM) layout. Vision Q/K carry a bias too."""
|
||||
if tensor.ndim == 1:
|
||||
dim1, = tensor.shape
|
||||
return tensor.view(n_heads, 2, dim1 // n_heads // 2).transpose(1, 2).reshape(dim1)
|
||||
dim1, dim2 = tensor.shape
|
||||
return tensor.view(n_heads, 2, dim1 // n_heads // 2, dim2).transpose(1, 2).reshape(dim1, dim2)
|
||||
|
||||
|
||||
@ModelBase.register("MuseSparkForConditionalGeneration")
|
||||
@ModelBase.example("meta-models/Muse-Spark-Open-816B-A42B")
|
||||
class MuseSparkModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.MUSE_SPARK
|
||||
|
||||
def set_vocab(self):
|
||||
self._set_vocab_gpt2()
|
||||
|
||||
from transformers import AutoTokenizer
|
||||
tok = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
eot_id = tok.convert_tokens_to_ids("<|eot|>") # ty: ignore[unresolved-attribute]
|
||||
if isinstance(eot_id, int) and eot_id >= 0:
|
||||
self.gguf_writer.add_eot_token_id(eot_id)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hparams = self.hparams
|
||||
|
||||
# MetaP resolves to two scalars
|
||||
# The output multiplier lands here and the attention scale rides on the synthesized QK-norm weights
|
||||
self.gguf_writer.add_logit_scale(hparams["output_multiplier"])
|
||||
self.gguf_writer.add_final_logit_softcapping(hparams["final_logit_softcapping"])
|
||||
|
||||
self.gguf_writer.add_post_norm_eps(hparams["post_norm_eps"])
|
||||
|
||||
# Only the sliding layers carry rope, so the graph takes it from the pattern
|
||||
sliding = [t == "sliding_attention" for t in hparams["layer_types"]]
|
||||
theta = hparams["rope_parameters"]["rope_theta"]
|
||||
assert sliding == [bool(t) for t in hparams["layer_rope_theta"]]
|
||||
assert all(t in (0, theta) for t in hparams["layer_rope_theta"])
|
||||
assert all(t == "sparse" for t in hparams["mlp_layer_types"])
|
||||
|
||||
self.gguf_writer.add_sliding_window(hparams["sliding_window"])
|
||||
self.gguf_writer.add_rope_freq_base_swa(theta)
|
||||
self.gguf_writer.add_sliding_window_pattern(sliding)
|
||||
|
||||
# Experts live in a latent space narrower than the residual stream.
|
||||
self.gguf_writer.add_moe_latent_size(hparams["moe_hidden_size"])
|
||||
self.gguf_writer.add_expert_feed_forward_length(hparams["moe_intermediate_size"])
|
||||
self.gguf_writer.add_expert_weights_scale(hparams["routed_scaling_factor"])
|
||||
self.gguf_writer.add_expert_weights_norm(True)
|
||||
# the config carries no score function, the reference hardcodes sigmoid
|
||||
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(OFFSET_NORM_SUFFIXES):
|
||||
data_torch = data_torch + 1.0
|
||||
|
||||
if name.endswith(".self_attn.q_proj.weight"):
|
||||
assert bid is not None
|
||||
n_head = self.hparams["num_attention_heads"]
|
||||
head_dim = self.hparams["head_dim"]
|
||||
|
||||
yield self.map_tensor_name(name), _unpermute_for_rope(data_torch, n_head)
|
||||
|
||||
# Synthesize QK-norm to carry the MetaP scale so the graph keeps the plain 1 / sqrt(head_dim)
|
||||
ones = torch.ones(head_dim, dtype=torch.float32)
|
||||
q_norm = ones * self.hparams["qk_scale_factor"]
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_Q_NORM, bid), q_norm
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_K_NORM, bid), ones
|
||||
return
|
||||
|
||||
if name.endswith(".self_attn.k_proj.weight"):
|
||||
n_head_kv = self.hparams["num_key_value_heads"]
|
||||
yield self.map_tensor_name(name), _unpermute_for_rope(data_torch, n_head_kv)
|
||||
return
|
||||
|
||||
# gate_up_proj fuses both projections over the output dim, gate first.
|
||||
if name.endswith(".mlp.experts.gate_up_proj"):
|
||||
assert bid is not None
|
||||
n_ff = data_torch.shape[1] // 2
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.FFN_GATE_EXP, bid), data_torch[:, :n_ff, :]
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.FFN_UP_EXP, bid), data_torch[:, n_ff:, :]
|
||||
return
|
||||
|
||||
if name.endswith(".mlp.experts.down_proj"):
|
||||
assert bid is not None
|
||||
yield self.format_tensor_name(gguf.MODEL_TENSOR.FFN_DOWN_EXP, bid), data_torch
|
||||
return
|
||||
|
||||
# The router scales its logits by the MetaP output multiplier before the sigmoid
|
||||
# Fold it into the weight to keep a plain matmul
|
||||
if name.endswith(".mlp.gate.weight"):
|
||||
data_torch = data_torch.float() * self.hparams["output_multiplier"]
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
return
|
||||
|
||||
# Norm preserving residual gate: alpha scales the stream, beta the branch, with alpha^2 + beta^2 = 1
|
||||
# Both follow from the weight, so resolve them here
|
||||
if name.endswith("_residual_gate.gate"):
|
||||
assert bid is not None
|
||||
attn = "post_attention" in name
|
||||
|
||||
w = data_torch.float() / self.hparams["residual_gate_temperature"]
|
||||
beta = torch.sigmoid(w)
|
||||
alpha_sq = torch.sigmoid(-w) * (1.0 + beta)
|
||||
alpha = torch.sqrt(torch.clamp(alpha_sq, min=RESIDUAL_GATE_EPS))
|
||||
|
||||
t_alpha = gguf.MODEL_TENSOR.ATTN_RESID_ALPHA if attn else gguf.MODEL_TENSOR.FFN_RESID_ALPHA
|
||||
t_beta = gguf.MODEL_TENSOR.ATTN_RESID_BETA if attn else gguf.MODEL_TENSOR.FFN_RESID_BETA
|
||||
yield self.format_tensor_name(t_alpha, bid), alpha
|
||||
yield self.format_tensor_name(t_beta, bid), beta
|
||||
return
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("MuseSparkForConditionalGeneration")
|
||||
@ModelBase.example("meta-models/Muse-Spark-Open-816B-A42B")
|
||||
class MuseSparkVisionModel(MuseGlimmerVisionModel):
|
||||
has_vision_encoder = True # same vision encoder as Muse Glimmer
|
||||
has_audio_encoder = True
|
||||
|
||||
def get_audio_config(self) -> dict[str, Any]:
|
||||
c = self.global_config["audio_config"]
|
||||
# the base class wants one width and one layer count: use the last stage width and the total number of conv blocks
|
||||
return {
|
||||
**c,
|
||||
"hidden_size": c["hidden_sizes"][-1],
|
||||
"intermediate_size": int(c["hidden_sizes"][-1] * c["mlp_ratio"]),
|
||||
"num_attention_heads": 1,
|
||||
"num_hidden_layers": sum(c["depths"]),
|
||||
}
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
|
||||
assert self.hparams_audio
|
||||
a = self.hparams_audio
|
||||
|
||||
self.gguf_writer.add_clip_audio_projector_type(gguf.VisionProjectorType.MUSE_SPARK_AUDIO)
|
||||
# the input is the raw waveform, one channel takes the place of the mel bins
|
||||
self.gguf_writer.add_audio_num_mel_bins(1)
|
||||
self.gguf_writer.add_audio_attention_layernorm_eps(float(a["rms_norm_eps"]))
|
||||
self.gguf_writer.add_audio_conv_kernel_size(int(a["conv_kernel_size"]))
|
||||
self.gguf_writer.add_audio_hidden_sizes([int(x) for x in a["hidden_sizes"]])
|
||||
self.gguf_writer.add_audio_patch_sizes([int(x) for x in a["patch_sizes"]])
|
||||
self.gguf_writer.add_audio_depths([int(x) for x in a["depths"]])
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item):
|
||||
name, gen = item
|
||||
if name.startswith("model.audio_tower."):
|
||||
return MmprojModel.filter_tensors((name, gen))
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
_AUDIO_HEAD = {
|
||||
"model.audio_tower.norm": gguf.MODEL_TENSOR.A_PRE_NORM,
|
||||
"model.audio_tower.proj": gguf.MODEL_TENSOR.A_MMPROJ_FC,
|
||||
"model.audio_tower.post_proj_norm": gguf.MODEL_TENSOR.A_POST_NORM,
|
||||
}
|
||||
|
||||
def _audio_tensor(self, data_torch, name, suffix):
|
||||
assert self.hparams_audio
|
||||
|
||||
stem = name[: -len(suffix) - 1]
|
||||
|
||||
if stem in self._AUDIO_HEAD:
|
||||
return self.format_tensor_name(self._AUDIO_HEAD[stem], suffix="." + suffix), data_torch
|
||||
|
||||
parts = stem.split(".")
|
||||
stage = int(parts[3])
|
||||
|
||||
if parts[4] == "patch_embedder":
|
||||
key = (gguf.MODEL_TENSOR.A_ENC_PATCH_PROJ if parts[5] == "proj"
|
||||
else gguf.MODEL_TENSOR.A_ENC_PATCH_NORM)
|
||||
return self.format_tensor_name(key, bid=stage, suffix="." + suffix), data_torch
|
||||
|
||||
# conv blocks are numbered per stage upstream and flat here
|
||||
depths = [int(d) for d in self.hparams_audio["depths"]]
|
||||
bid = sum(depths[:stage]) + int(parts[5])
|
||||
tail = ".".join(parts[6:])
|
||||
key = {
|
||||
"conv": gguf.MODEL_TENSOR.A_ENC_CONV1D,
|
||||
"norm": gguf.MODEL_TENSOR.A_ENC_CONV1D_NORM,
|
||||
"mlp.fc1": gguf.MODEL_TENSOR.A_ENC_FFN_UP,
|
||||
"mlp.fc2": gguf.MODEL_TENSOR.A_ENC_FFN_DOWN,
|
||||
}[tail]
|
||||
return self.format_tensor_name(key, bid=bid, suffix="." + suffix), data_torch
|
||||
|
||||
def modify_tensors(self, data_torch, name, bid):
|
||||
if name.startswith("model.audio_tower."):
|
||||
yield self._audio_tensor(data_torch, name, name.rpartition(".")[2])
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
+221
-2
@@ -1,16 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
from typing import Any, Callable, Iterable, Sequence, TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
from torch import Tensor
|
||||
|
||||
from .base import MmprojModel, ModelBase, TextModel, gguf, logger
|
||||
from .base import MmprojModel, ModelBase, ModelType, TextModel, get_model_architecture, gguf, logger
|
||||
|
||||
from .granite import GraniteHybridModel
|
||||
from .qwen import DFlashModel
|
||||
|
||||
|
||||
@ModelBase.register("LiLiCorrDraftModel")
|
||||
class LiLiCorrDraftModel(DFlashModel):
|
||||
model_arch = gguf.MODEL_ARCH.DFLASH
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
prefix = "model.lilicorr."
|
||||
if not any(name.startswith(prefix) for name in self.model_tensors):
|
||||
raise ValueError("no lilicorr.* tensors found")
|
||||
dflash_config = self.hparams["dflash_config"]
|
||||
self.lilicorr_geometry = {
|
||||
"hidden_size": int(dflash_config["lilicorr_hidden_size"]),
|
||||
"candidate_topk": int(dflash_config["lilicorr_candidate_topk"]),
|
||||
"num_heads": int(dflash_config["lilicorr_num_heads"]),
|
||||
"num_layers": int(dflash_config["lilicorr_num_layers"]),
|
||||
"factor_dim": int(dflash_config["lilicorr_factor_dim"]),
|
||||
"mlp_hidden_size": self.model_tensors[prefix + "layers.0.mlp.up_proj.weight"]().shape[0],
|
||||
}
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count + self.lilicorr_geometry["num_layers"] + 1)
|
||||
block = int(dflash_config.get("block_size", self.hparams.get("block_size", 16)))
|
||||
topk = self.lilicorr_geometry["candidate_topk"]
|
||||
heads = self.lilicorr_geometry["num_heads"]
|
||||
nodes = (block - 1) * topk
|
||||
relative = gguf.LazyBase.to_eager(self.model_tensors.pop(prefix + "relative_slot_bias")()).float().cpu().numpy()
|
||||
same = gguf.LazyBase.to_eager(self.model_tensors.pop(prefix + "same_slot_bias")()).float().cpu().numpy()
|
||||
bias = np.empty((heads, nodes, nodes), dtype=np.float32)
|
||||
for q in range(nodes):
|
||||
q_slot = q // topk
|
||||
for k in range(nodes):
|
||||
k_slot = k // topk
|
||||
bias[:, q, k] = relative[:, q_slot - k_slot + block - 1]
|
||||
if q_slot == k_slot:
|
||||
bias[:, q, k] += same
|
||||
self.model_tensors[prefix + "attention_bias.bias"] = lambda bias=bias: torch.from_numpy(bias)
|
||||
for name in ("slot_embedding", "rank_embedding"):
|
||||
gen = self.model_tensors.pop(prefix + name)
|
||||
self.model_tensors[prefix + name + ".weight"] = lambda gen=gen: gen().reshape(-1, self.lilicorr_geometry["hidden_size"])
|
||||
for i in range(self.lilicorr_geometry["num_layers"]):
|
||||
layer_prefix = prefix + f"layers.{i}.attn.in_proj"
|
||||
for suffix in ("weight", "bias"):
|
||||
self.model_tensors[layer_prefix + "." + suffix] = self.model_tensors.pop(layer_prefix + "_" + suffix)
|
||||
dflash_config["selector_rank"] = 0
|
||||
dflash_config["selector_top_k"] = 0
|
||||
|
||||
def set_vocab(self):
|
||||
if self.target_model_dir is None:
|
||||
raise ValueError("LiLiCorr draft requires --target-model-dir")
|
||||
from . import get_model_class
|
||||
target_text_config = ModelBase.load_hparams(self.target_model_dir, False)
|
||||
target_text_config = {**target_text_config, **target_text_config.get("text_config", {})}
|
||||
target_arch = get_model_architecture(target_text_config, ModelType.TEXT)
|
||||
target_cls = get_model_class(target_arch)
|
||||
original_dir = self.dir_model
|
||||
self.dir_model = self.target_model_dir
|
||||
try:
|
||||
if target_arch == "NemotronHForCausalLM":
|
||||
self.is_moe = "num_experts_per_tok" in target_text_config
|
||||
target_cls.set_vocab(self) # ty: ignore[unresolved-attribute]
|
||||
finally:
|
||||
self.dir_model = original_dir
|
||||
self.gguf_writer.add_mask_token_id(self.hparams["dflash_config"]["mask_token_id"])
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
dflash_config = self.hparams["dflash_config"]
|
||||
self.gguf_writer.add_lilicorr_hidden_size(int(self.lilicorr_geometry["hidden_size"]))
|
||||
self.gguf_writer.add_lilicorr_candidate_topk(int(self.lilicorr_geometry["candidate_topk"]))
|
||||
self.gguf_writer.add_lilicorr_num_heads(int(self.lilicorr_geometry["num_heads"]))
|
||||
self.gguf_writer.add_lilicorr_num_layers(int(self.lilicorr_geometry["num_layers"]))
|
||||
self.gguf_writer.add_lilicorr_factor_dim(int(self.lilicorr_geometry["factor_dim"]))
|
||||
self.gguf_writer.add_lilicorr_mlp_hidden_size(int(self.lilicorr_geometry["mlp_hidden_size"]))
|
||||
self.gguf_writer.add_lilicorr_vector_eps(float(dflash_config["lilicorr_vector_eps"]))
|
||||
self.gguf_writer.add_lilicorr_logit_scale(float(dflash_config["lilicorr_logit_scale"]))
|
||||
|
||||
def map_tensor_name(self, name: str, try_suffixes: Sequence[str] = (".weight", ".bias")) -> str:
|
||||
if name.startswith("model.lilicorr.layers."):
|
||||
parts = name.split(".", 4)
|
||||
if not parts[4].startswith("attn.in_proj."):
|
||||
parts[3] = str(self.block_count + int(parts[3]))
|
||||
name = ".".join(parts)
|
||||
elif name.startswith("model.lilicorr.feature_norm."):
|
||||
feature_bid = self.block_count + self.lilicorr_geometry["num_layers"]
|
||||
name = name.replace("model.lilicorr.feature_norm.", f"model.lilicorr.feature_mlp.{feature_bid}.norm.")
|
||||
elif name.startswith("model.lilicorr.feature_mlp."):
|
||||
feature_bid = self.block_count + self.lilicorr_geometry["num_layers"]
|
||||
name = name.replace("model.lilicorr.feature_mlp.", f"model.lilicorr.feature_mlp.{feature_bid}.")
|
||||
return super().map_tensor_name(name, try_suffixes)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.startswith("model.lilicorr."):
|
||||
yield self.map_tensor_name(name), data_torch
|
||||
return
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
if name.startswith("model.lilicorr."):
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
|
||||
@ModelBase.register(
|
||||
@@ -152,6 +254,113 @@ class NemotronNanoV2VLModel(MmprojModel):
|
||||
yield mapped_name, tensor
|
||||
|
||||
|
||||
@ModelBase.register("NemotronH_Omni_Reasoning_V3")
|
||||
class NemotronOmniVLModel(NemotronNanoV2VLModel):
|
||||
_omni_tensor_prefixes = (
|
||||
"vision_model.embeddings.",
|
||||
"vision_model.encoder.layer.",
|
||||
"vision_projector.mlp1.",
|
||||
)
|
||||
|
||||
def get_vision_config(self) -> dict[str, Any] | None:
|
||||
vision_config = dict(self.global_config["vision_config"])
|
||||
vision_config["intermediate_size"] = int(vision_config["hidden_size"] * vision_config["mlp_ratio"])
|
||||
vision_config["image_size"] = 512 # warmup only; the full position table is resized at runtime
|
||||
return vision_config
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
preprocessor_config = self.preprocessor_config
|
||||
|
||||
preprocessor_config["image_mean"] = preprocessor_config["norm_mean"]
|
||||
preprocessor_config["image_std"] = preprocessor_config["norm_std"]
|
||||
|
||||
MmprojModel.set_gguf_parameters(self)
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.NEMOTRON_OMNI)
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(1e-6)
|
||||
self.gguf_writer.add_vision_use_gelu(True)
|
||||
downsample_ratio = self.global_config.get("downsample_ratio", 0.5)
|
||||
self.gguf_writer.add_vision_projector_scale_factor(int(1.0 / downsample_ratio))
|
||||
patch_area = self.hparams["patch_size"] ** 2
|
||||
self.gguf_writer.add_vision_min_pixels(preprocessor_config["min_num_patches"] * patch_area)
|
||||
self.gguf_writer.add_vision_max_pixels(preprocessor_config["max_num_patches"] * patch_area)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
# Transformers can rename RADIO and projector tensors in Omni checkpoints.
|
||||
# Accept both checkpoint layouts.
|
||||
if name == "vision_model.summary_idxs":
|
||||
# This buffer is only used by RadioModel.summary. The multimodal
|
||||
# projector consumes RadioModel.features for both images and video.
|
||||
return None
|
||||
if name.startswith(cls._omni_tensor_prefixes):
|
||||
return name, gen
|
||||
|
||||
if "radio_model.model.patch_generator.video_embedder" in name or name.startswith("vision_projector.vision_final_layernorm."):
|
||||
return MmprojModel.filter_tensors((name, gen))
|
||||
|
||||
return super().filter_tensors(item)
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if new_name in ("v.position_embd.weight", "v.class_embd"):
|
||||
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]]:
|
||||
if name.startswith(self._omni_tensor_prefixes):
|
||||
if name == "vision_model.embeddings.video_patch_projection.weight":
|
||||
data_torch = data_torch.reshape(
|
||||
self.hparams["hidden_size"],
|
||||
6,
|
||||
self.hparams["patch_size"],
|
||||
self.hparams["patch_size"],
|
||||
)
|
||||
yield gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.V_ENC_EMBD_PATCH] + ".weight.1", data_torch
|
||||
return
|
||||
|
||||
if name.startswith("vision_projector.mlp1."):
|
||||
projector_mapping = {
|
||||
"vision_projector.mlp1.norm.weight": "mlp1.0.weight",
|
||||
"vision_projector.mlp1.linear1.weight": "mlp1.1.weight",
|
||||
"vision_projector.mlp1.linear2.weight": "mlp1.3.weight",
|
||||
}
|
||||
name = projector_mapping[name]
|
||||
elif name == "vision_model.embeddings.patch_projection.weight":
|
||||
data_torch = data_torch.reshape(
|
||||
self.hparams["hidden_size"],
|
||||
3,
|
||||
self.hparams["patch_size"],
|
||||
self.hparams["patch_size"],
|
||||
)
|
||||
elif name == "vision_model.embeddings.position_embedding":
|
||||
data_torch = data_torch.squeeze(0)
|
||||
name += ".weight"
|
||||
elif ".layer_scale" in name and name.endswith(".lambda1"):
|
||||
name = name.removesuffix("lambda1") + "weight"
|
||||
|
||||
yield from MmprojModel.modify_tensors(self, data_torch, name, bid)
|
||||
return
|
||||
|
||||
if "patch_generator.pos_embed" in name:
|
||||
# Keep the full table for runtime interpolation.
|
||||
yield from MmprojModel.modify_tensors(self, data_torch, name, bid)
|
||||
return
|
||||
|
||||
if "patch_generator.video_embedder" in name:
|
||||
patch_size = self.hparams["patch_size"]
|
||||
n_embd = self.hparams["hidden_size"]
|
||||
temporal_patch_size = self.global_config["video_temporal_patch_size"]
|
||||
if temporal_patch_size != 2:
|
||||
raise ValueError("Current implementation only supports a temporal patch size of 2")
|
||||
data_torch = data_torch.reshape(n_embd, temporal_patch_size * 3, patch_size, patch_size)
|
||||
new_name = gguf.TENSOR_NAMES[gguf.MODEL_TENSOR.V_ENC_EMBD_PATCH] + ".weight.1"
|
||||
yield new_name, data_torch
|
||||
return
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("NemotronForCausalLM")
|
||||
@ModelBase.example("nvidia/Minitron-4B-Base")
|
||||
class NemotronModel(TextModel):
|
||||
@@ -283,6 +492,8 @@ class NemotronHModel(GraniteHybridModel):
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
if name.startswith("language_model."):
|
||||
name = name[len("language_model."):]
|
||||
if name.startswith("mtp."):
|
||||
# --no-mtp: drop the MTP head entirely
|
||||
if cls.no_mtp:
|
||||
@@ -432,6 +643,14 @@ class NemotronHModel(GraniteHybridModel):
|
||||
"mtp.layers.1.final_layernorm.weight": "model.layers.{bid}.shared_head.norm.weight",
|
||||
}
|
||||
|
||||
def _generate_nvfp4_tensors(self):
|
||||
if self._mtp_bid is not None:
|
||||
for name in list(self.model_tensors):
|
||||
if name.startswith("mtp.layers.1.mixer"):
|
||||
new_name = f"backbone.layers.{self._mtp_bid}.{name.split('.', 3)[3]}"
|
||||
self.model_tensors[new_name] = self.model_tensors.pop(name)
|
||||
super()._generate_nvfp4_tensors()
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
# mtp.layers.0: NextN input fusion + attention
|
||||
# mtp.layers.1: MoE + final head norm
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
from typing import Any, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
@@ -35,6 +35,7 @@ def _load_pplx_decider_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
|
||||
@ModelBase.register("PplxDeciderModel")
|
||||
@ModelBase.example("perplexity-ai/pplx-decider-v1-27b")
|
||||
@ModelBase.example("FINAL-Bench/Darwin-27B-ZTC-v5")
|
||||
class PplxDeciderModel(Qwen3_5TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
no_mtp = True # the checkpoint has no MTP head
|
||||
@@ -77,14 +78,6 @@ class PplxDeciderModel(Qwen3_5TextModel):
|
||||
for name in ("choice", "score", "noul"):
|
||||
self.gguf_writer.add_decision_temperature(name, self.hparams["decision"]["temperature"])
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
# the checkpoint is the bare backbone, its text tensors have no "model." prefix
|
||||
if name.startswith("language_model."):
|
||||
name = "model." + name
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
yield from super().generate_extra_tensors()
|
||||
from safetensors.torch import load_file
|
||||
@@ -92,7 +85,7 @@ class PplxDeciderModel(Qwen3_5TextModel):
|
||||
# the readout has one row per option label, store it as an LM head that is zero for the other tokens
|
||||
readout = load_file(self.dir_model / "readout.safetensors")["weight"]
|
||||
token_ids = self.hparams["decision"]["token_ids"]
|
||||
n_vocab = self.hparams["text_config"]["vocab_size"]
|
||||
n_vocab = self.hparams["vocab_size"]
|
||||
assert readout.shape[0] == len(token_ids) == len(set(token_ids))
|
||||
lm_head = torch.zeros(n_vocab, readout.shape[1], dtype=readout.dtype)
|
||||
lm_head[token_ids] = readout
|
||||
|
||||
@@ -59,6 +59,10 @@ class Qwen2Model(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN2
|
||||
|
||||
def set_vocab(self):
|
||||
# HuggingFaceBio/Carbon-14B: Qwen3 with the HybridDNATokenizer (Qwen3 BPE plus DNA 6-mers)
|
||||
if self._is_hybriddna():
|
||||
self._set_vocab_hybriddna()
|
||||
return
|
||||
try:
|
||||
self._set_vocab_sentencepiece()
|
||||
except FileNotFoundError:
|
||||
@@ -253,6 +257,11 @@ class Qwen3Model(Qwen2Model):
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
return
|
||||
|
||||
# output embedding projection (voyage-4-nano)
|
||||
if name == "linear.weight":
|
||||
self.gguf_writer.add_embedding_length_out(data_torch.shape[0])
|
||||
name = "dense_2_out.weight"
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import MmprojModel, ModelBase, TextModel, gguf
|
||||
from .llama import LlamaModel
|
||||
|
||||
|
||||
@ModelBase.register("UltravoxModel")
|
||||
@@ -208,3 +209,47 @@ class AudioFlamingo3WhisperEncoderModel(WhisperEncoderModel):
|
||||
# Was trained in BF16, being safe, avoiding quantizing to FP16
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
|
||||
@ModelBase.register("VoxtralRealtimeForConditionalGeneration")
|
||||
@ModelBase.example("mistralai/Voxtral-Mini-4B-Realtime-2602")
|
||||
class VoxtralRealtimeEncoderModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = True
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
assert self.hparams_audio is not None
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.VOXTRAL_RT)
|
||||
self.gguf_writer.add_audio_num_mel_bins(self.hparams_audio["num_mel_bins"])
|
||||
self.gguf_writer.add_audio_head_dim(self.hparams_audio["head_dim"])
|
||||
self.gguf_writer.add_audio_attention_layernorm_eps(self.hparams_audio.get("rms_norm_eps", 1e-5))
|
||||
self.gguf_writer.add_audio_window_size(self.hparams_audio["sliding_window"])
|
||||
self.gguf_writer.add_audio_stack_factor(self.global_config["downsample_factor"])
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if ".conv" in name and ".weight" in name:
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
# prevent clash naming with vision tensors
|
||||
if name.startswith("multi_modal_projector"):
|
||||
name = "audio." + name
|
||||
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if "conv1.bias" in name or "conv2.bias" in name:
|
||||
data_torch = data_torch.unsqueeze(-1)
|
||||
|
||||
# HF stores q/k in rotate-half layout, the encoder uses interleaved (normal) RoPE
|
||||
if ".self_attn.q_proj." in name or ".self_attn.k_proj." in name:
|
||||
assert self.hparams_audio is not None
|
||||
n_head = self.hparams_audio["num_attention_heads"]
|
||||
data_torch = LlamaModel.permute(data_torch, n_head, n_head)
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
@@ -189,6 +189,10 @@ pre_computed_hashes = [
|
||||
{"name": "qwen2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/Qwen/Qwen3-Embedding-0.6B", "chkhsh": "d4540891389ea895b53b399da6ac824becc30f2fba0e9ddbb98f92e55ca0e97c"},
|
||||
{"name": "qwen35", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/openbmb/MiniCPM-V-4_6", "chkhsh": "1444df51289cfa8063b96f0e62b1125440111bc79a52003ea14b6eac7016fd5f"},
|
||||
{"name": "grok-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/alvarobartt/grok-2-tokenizer", "chkhsh": "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273"},
|
||||
# muse-spark uses the l4_202k tokenizer, whose pre-tokenizer regex is gpt-4o's
|
||||
{"name": "gpt-4o", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B", "chkhsh": "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc"},
|
||||
# kolibri1 uses a 128k vocabulary with the qwen2 pre-tokenizer regex
|
||||
{"name": "qwen2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/Aleph-Alpha/Kolibri-1", "chkhsh": "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78"},
|
||||
# jina-v2-de variants
|
||||
{"name": "jina-v2-de", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/aari1995/German_Semantic_V3", "chkhsh": "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df"},
|
||||
{"name": "gpt-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/evilfreelancer/ruGPT3XL", "chkhsh": "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4"},
|
||||
|
||||
@@ -6,8 +6,8 @@ The cross-compilation toolchain images are provided by the
|
||||
[Qualcomm Snapdragon Toolchain registry](https://github.com/snapdragon-toolchain).
|
||||
These Docker images include the Android NDK, OpenCL SDK, Hexagon SDK, CMake, and the necessary cross-compilers:
|
||||
|
||||
* **Android toolchain**: `ghcr.io/snapdragon-toolchain/arm64-android:v0.7`
|
||||
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.7`
|
||||
* **Android toolchain**: `ghcr.io/snapdragon-toolchain/arm64-android:v0.8`
|
||||
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.8`
|
||||
|
||||
The unified build utility (`scripts/snapdragon/build.py`) automatically pulls
|
||||
and orchestrates these containers to perform target compilation.
|
||||
@@ -40,7 +40,7 @@ Alternatively, you can build llama.cpp manually by entering the cross-compilatio
|
||||
|
||||
```bash
|
||||
# Start the cross-compilation container manually:
|
||||
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.7
|
||||
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.8
|
||||
|
||||
# Inside the container, build the project using presets:
|
||||
[d]/workspace> cp docs/backend/snapdragon/CMakeUserPresets.json .
|
||||
@@ -53,7 +53,7 @@ Preset CMake variables:
|
||||
GGML_HEXAGON="ON"
|
||||
GGML_OPENCL="ON"
|
||||
GGML_OPENMP="OFF"
|
||||
HEXAGON_SDK_ROOT="/opt/hexagon/6.6.0.0"
|
||||
HEXAGON_SDK_ROOT="/opt/hexagon/6.7.0.0"
|
||||
...
|
||||
-- Including OpenCL backend
|
||||
-- Including Hexagon backend
|
||||
|
||||
@@ -48,6 +48,157 @@ Alternatively, users can partition and split the model across multiple virtual s
|
||||
tensor-splitting, or row-splitting modes. For user-facing execution modes and examples, see the
|
||||
[Snapdragon user guide](README.md#multi-device-execution-modes).
|
||||
|
||||
## Tiled Weight Formats
|
||||
|
||||
Weight tensors in the Hexagon backend are repacked from flat row-major GGML layouts into 2D tiles in memory.
|
||||
Tiling groups 2D matrix blocks contiguously:
|
||||
- Eliminates strided row reads when fetching 2D tiles into VTCM via DMA.
|
||||
- Matches native HVX vector width (128 bytes) and HMX systolic array dimensions (32x32).
|
||||
- Repacking happens on the host during model load (`set_tensor()`) or via runtime repacking (`rt-repack`).
|
||||
Implementation: [`htp-repack.cpp`](../../../ggml/src/ggml-hexagon/htp-repack.cpp).
|
||||
|
||||
### Tile Dimensions and Grid Layout
|
||||
|
||||
Each tile represents a submatrix of `N_rows = 32` rows and `K_cols` columns:
|
||||
- Standard types (`Q4_0`, `Q4_1`, `Q8_0`, `IQ4_NL`, `MXFP4`, and K-quants): `K_cols = 32` (`HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- `Q2_0`: `K_cols = 64` (`HTP_TENSOR_TILE_K_Q2_0 = 64`).
|
||||
- `Q1_0`: `K_cols = 128` (`HTP_TENSOR_TILE_K_Q1_0 = 128`).
|
||||
|
||||
For a weight matrix of logical shape `[ne0, ne1]` (where `ne0 = K`, `ne1 = N`):
|
||||
- `n_k_tiles = ne0 / K_cols`
|
||||
- `n_col_tiles = (ne1 + 31) / 32`
|
||||
- Tile index for column-tile `kt` and row-tile `ct`: `tile_idx = ct * n_k_tiles + kt`
|
||||
- Byte offset in matrix buffer: `tile_offset = tile_idx * tile_size`
|
||||
|
||||
### Block-32 Quantized Formats
|
||||
|
||||
Block-32 formats use a tile size of 32 rows x 32 columns (`K_cols = 32`):
|
||||
|
||||
- **Q4_0** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 32 columns of 4-bit quants.
|
||||
Divided into 16 column pairs `cp = 0..15` covering columns `2*cp` and `2*cp + 1`.
|
||||
For each column pair `cp`, 32 bytes are stored consecutively for rows `row = 0..31`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
Low nibble holds the even column; high nibble holds the odd column.
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
|
||||
- **Q8_0** (1088 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q8_0`):
|
||||
- **Quants** (bytes 0..1023, 1024 bytes):
|
||||
Contains 32 rows x 32 columns of signed 8-bit quants (`int8_t`).
|
||||
Divided into 16 column pairs `cp = 0..15` covering columns `col0 = 2*cp` and `col1 = 2*cp + 1`.
|
||||
For each column pair `cp`, 64 bytes store interleaved column elements for each row `row = 0..31`:
|
||||
`tile[cp * 64 + 2 * row + 0] = quant[row][col0]`
|
||||
`tile[cp * 64 + 2 * row + 1] = quant[row][col1]`
|
||||
- **Scales** (bytes 1024..1087, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
|
||||
- **IQ4_NL** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_IQ4_NL`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same nibble transposition and storage layout as `Q4_0`.
|
||||
Contains 32 rows x 32 columns of non-linear 4-bit indices (values 0..15).
|
||||
Divided into 16 column pairs `cp = 0..15`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
- **Dequantization**:
|
||||
In HVX/HMX kernels, each 4-bit index is looked up in the 16-entry non-linear table (`iq4_nl_to_fp16_lut`) to produce an FP16 value, then multiplied by the row scale `d`.
|
||||
|
||||
- **MXFP4** (544 bytes per tile, `HTP_TENSOR_TILE_SIZE_MXFP4`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same nibble transposition and storage layout as `Q4_0`.
|
||||
Contains 32 rows x 32 columns of 4-bit OCP Microscaling FP4 (E2M1) values.
|
||||
Divided into 16 column pairs `cp = 0..15`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
- **Scales** (bytes 512..543, 32 bytes):
|
||||
32 unsigned 8-bit values (`uint8_t`), storing one 8-bit biased exponent (E8M0, bias 127) scale `e` per row `row = 0..31`.
|
||||
- **Dequantization**:
|
||||
Kernel converts E8M0 bias to IEEE FP16 bias via `vh = clamp(e - 112, 0, 30) << 10` to form a power-of-two FP16 scale.
|
||||
Each 4-bit FP4 nibble is unpacked via `mxfp4_to_fp16_lut` and multiplied by this scale.
|
||||
|
||||
- **Q4_1** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_1`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same 4-bit nibble transposition and storage layout as `Q4_0`.
|
||||
- **Scales** (bytes 512..639, 128 bytes):
|
||||
32 pairs of FP16 values (`ggml_half`): `scale[2 * row + 0] = d` (scale) and `scale[2 * row + 1] = m` (min) for each row `row = 0..31`.
|
||||
|
||||
### Q1_0 Format (1-Bit Quantization)
|
||||
|
||||
`Q1_0` packs 128 columns per tile (`K_cols = 128`, `HTP_TENSOR_TILE_K_Q1_0 = 128`):
|
||||
|
||||
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q1_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 128 columns of 1-bit quants (-1 or +1).
|
||||
Divided into 4 quarters (`quarter = 0..3`), where each quarter covers 32 columns in K.
|
||||
Each quarter occupies 128 bytes (1 HVX vector) in `vrmpy` byte order.
|
||||
Within a quarter, columns `kq = 0..31` map into 8 groups `g = kq / 4` (0..7) of 4 consecutive columns:
|
||||
`byte_offset = quarter * 128 + 4 * row + (kq % 4)`
|
||||
`bit = g` (0..7)
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
One scale per row applies to all 128 columns (all 4 quarters) of the tile.
|
||||
|
||||
- **HMX Mapping (1-to-4 Tile Expansion)**:
|
||||
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q1_0`) expands each 32x128 Q1_0 tile (576 bytes) into 4 contiguous 32x32 FP16 HMX tiles (4 x 2048 bytes = 8192 bytes total) in VTCM.
|
||||
- HMX systolic array processes the 4 resulting 32x32 FP16 tiles over 4 compute steps along K.
|
||||
|
||||
### Q2_0 Format (2-Bit Quantization)
|
||||
|
||||
`Q2_0` packs 64 columns per tile (`K_cols = 64`, `HTP_TENSOR_TILE_K_Q2_0 = 64`):
|
||||
|
||||
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 64 columns of 2-bit quants (codes 0..3 representing signed values -1..2).
|
||||
Divided into 16 column groups `group = 0..15` of 4 columns each.
|
||||
Stored in `vrmpy` byte order across 4 HVX vectors (vec 0..3):
|
||||
Vector `group >> 2` (0..3) holds `group & 3` at bit offset `2 * (group & 3)`.
|
||||
Byte `4 * row + byte` holds row `row = 0..31` and column byte `0..3`.
|
||||
Vec 0..1 holds the first 32 columns (half 0), vec 2..3 holds the next 32 columns (half 1).
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31` (at offset `512 + 2 * row`).
|
||||
One scale per row applies to all 64 columns of the tile.
|
||||
|
||||
- **HMX Mapping (1-to-2 Tile Expansion)**:
|
||||
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q2_0`) expands each 32x64 Q2_0 tile (576 bytes) into 2 contiguous 32x32 FP16 HMX tiles (2 x 2048 bytes = 4096 bytes total) in VTCM.
|
||||
- HMX systolic array processes the 2 resulting 32x32 FP16 tiles over 2 compute steps along K.
|
||||
|
||||
- **HVX Vector Dot-Product**:
|
||||
- `tiled_vec_dot_q2_0_32x1` and `tiled_vec_dot_q2_0_32x2` unpack 2-bit quants using `unpack_q3_k_low2`, subtract 1, and accumulate against Q8_0 activation tiles using `Q6_Vw_vrmpyacc_VwVbVb`.
|
||||
|
||||
### K-Quants Formats
|
||||
|
||||
In GGML, K-quants pack 256 elements per super-block.
|
||||
On Hexagon, each 256-element super-block splits along K into 8 tiles of 32 columns (`K_cols = 32`).
|
||||
|
||||
Byte ordering: `Q2_K`, `Q3_K`, and `Q6_K` use `vrmpy` byte order within each 128B HVX vector:
|
||||
`k = 0..31` is split into 8 groups `g = k/4` of 4 `k` each; byte `4*r + (k%4)` holds row `r`.
|
||||
|
||||
- **Q2_K** (336 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_K`, lossless tiling with same size as `block_q2_K`):
|
||||
- `bytes 0..255` (vec 0..1): unsigned 2-bit quants; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 256..319`: raw `scales[]` bytes (`min << 4 | sc`): `[0..31]` for k 0..15, `[32..63]` for k 16..31.
|
||||
- `bytes 320..335`: 8 FP16 in tile `j` (0..7) of super-block: `d` (`j < 4`) or `-dmin` (`j >= 4`) for rows `8*(j%4)..8*(j%4)+7`.
|
||||
|
||||
- **Q3_K** (512 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q3_K`):
|
||||
- `bytes 0..255` (vec 0..1): low 2 bits; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 256..383` (vec 2): sign mask, bit `g` set when `hmask` is 0 (quant = `low2 - 4`).
|
||||
- `bytes 384..511` (vec 3): FP16 scales `d * (scales[] - 32)`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
|
||||
|
||||
- **Q4_K** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_K`, same layout as Q4_1 tile; each 256-k super-block splits into 8 tiles of 32 k):
|
||||
- `bytes 0..511` (vec 0..3): 4-bit quants; byte `32*cp + r` holds `k = 2*cp` (low nibble) and `2*cp + 1` (high nibble).
|
||||
- `bytes 512..639` (vec 4): 32x2 FP16 per row: `(d * sc, -dmin * m)`.
|
||||
|
||||
- **Q5_K** (768 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q5_K`):
|
||||
- `bytes 0..639` (vec 0..4): Q4_K tile (low 4 bits + FP16 scale/min pairs).
|
||||
- `bytes 640..767` (vec 5): 5th-bit plane; byte `l` holds the 8 high bits for lane `l` across vec `i = 0..3` (`bit 2i = lo, 2i+1 = hi`).
|
||||
|
||||
- **Q6_K** (896 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q6_K`):
|
||||
- `bytes 0..511` (vec 0..3): low 4 bits; vec `i` holds group `2i` (low nibble) and `2i+1` (high nibble).
|
||||
- `bytes 512..767` (vec 4..5): high 2 bits; vec `4+m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 768..895` (vec 6): FP16 scales `d * scales[]`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
|
||||
|
||||
## Op and Kernel Development Guidelines
|
||||
|
||||
Writing high-performance operators for Hexagon requires following specific guidelines.
|
||||
@@ -134,6 +285,18 @@ Writing high-performance operators for Hexagon requires following specific guide
|
||||
- **Distinction**: `octx->src[N]` pointers *can* be NULL by design and must be checked when optional.
|
||||
Examples include attention masks, optional bias or weights in fused kernels, and frequency factors.
|
||||
|
||||
### `supports_op` Independence from Buffer and Repack State
|
||||
|
||||
- `supports_op` (and op-specific checkers such as `supported_mul_mat()`, `supported_get_rows()`) runs during GGML graph scheduling **before** buffers are allocated and before tensor data is populated.
|
||||
- **Never check buffer usage or repacking state in `supports_op`**:
|
||||
- `t->buffer` may be NULL or uninitialized.
|
||||
- `t->data` may be NULL.
|
||||
- `buffer->usage` is unknown at scheduling time.
|
||||
- `is_tiled` or `extra->flags & GGML_HEXAGON_TENSOR_TILED` is not set yet.
|
||||
- Operator support decisions must depend strictly on static tensor properties: operand types (`t->type`), shapes (`t->ne`), strides (`t->nb`), and operation parameters.
|
||||
- Repacking (converting weights to tiled layout) is an internal optimization applied during `set_tensor()` or rt-repack. Non-weight tensors or test harness tensors with the same type remain in flat layout.
|
||||
- Therefore, any supported type (such as BF16, F16, quantized types) must be supported both when repacked into tiles and when stored flat in memory.
|
||||
|
||||
### Multiline Macro Formatting
|
||||
|
||||
- Keep trailing backslashes in multiline `#define` macros cleanly aligned to a consistent column.
|
||||
@@ -190,7 +353,7 @@ sessions.
|
||||
Common partitioning logic is factored into reusable inline helpers in
|
||||
[`htp-tensor.h`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h):
|
||||
|
||||
1. [`htp_tensor_mdev_rows_per_chunk`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L71):
|
||||
1. [`htp_tensor_mdev_rows_per_chunk`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L104):
|
||||
Determines the minimum number of rows per chunk so that the chunk byte size is a multiple of 128 bytes:
|
||||
|
||||
```
|
||||
@@ -201,7 +364,7 @@ Common partitioning logic is factored into reusable inline helpers in
|
||||
Returns `false` if the tensor cannot be safely row-partitioned (such as unaligned base pointer, permuted layout,
|
||||
or non-128-byte aligned outer strides).
|
||||
|
||||
2. [`htp_tensor_mdev_partition`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L98):
|
||||
2. [`htp_tensor_mdev_partition`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L131):
|
||||
Calculates the per-device work range `struct htp_tensor_mdev_range { uint32_t start; uint32_t count; }` given
|
||||
`total_units`, `units_per_chunk`, `mdev_idx`, `mdev_count`, and the precomputed `mdev_count_div`.
|
||||
Handles chunk distribution across devices, assigns remainder units to the last device, and automatically triggers
|
||||
@@ -240,11 +403,11 @@ if (nrows == 0) {
|
||||
For flat element-wise operations that write directly to DDR:
|
||||
- Partition total linear elements N = ne0 * ne1 * ne2 * ne3 in 128-byte cache line chunks (`elems_per_line = (elem_size == 4) ? 32 : 64`).
|
||||
- Requires strict 1D contiguity:
|
||||
[`htp_tensor_is_contiguous(dst, elem_size)`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L32)
|
||||
[`htp_tensor_is_contiguous(dst, elem_size)`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L65)
|
||||
and 128-byte aligned destination pointer
|
||||
[`htp_tensor_mdev_data_aligned(dst)`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L51).
|
||||
[`htp_tensor_mdev_data_aligned(dst)`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L84).
|
||||
- If contiguous and aligned, pass `elems_per_line` to
|
||||
[`htp_tensor_mdev_partition`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L98);
|
||||
[`htp_tensor_mdev_partition`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h#L131);
|
||||
otherwise pass 0 to trigger Device 0 fallback.
|
||||
|
||||
### Single-Device Fallback (Device 0)
|
||||
@@ -312,7 +475,7 @@ Multi-device execution synchronizes worker sessions through atomic fence slots a
|
||||
(Output Slice 0) (Output Slice 1)
|
||||
| |
|
||||
Tensor Cache Flush Tensor Cache Flush
|
||||
(htp_tensor_flush_all) (htp_tensor_flush_all)
|
||||
(htp_dcache_flush_dirty_tensors) (htp_dcache_flush_dirty_tensors)
|
||||
| |
|
||||
Post-Op/Batch Barrier ---------------------- Post-Op/Batch Barrier
|
||||
(htp_mdev_group_barrier) (htp_mdev_group_barrier)
|
||||
@@ -350,8 +513,8 @@ Multi-device execution synchronizes worker sessions through atomic fence slots a
|
||||
### Tensor Cache Flush and Pipeline Completion
|
||||
|
||||
- In the kernel, ensure all pushed DMA operations have been popped in strict FIFO order to drain the queue.
|
||||
- Use [`htp_tensor_flush_all()`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h) to flush specific dirty tensors back to DDR:
|
||||
- [`htp_tensor_flush_all()`](../../../ggml/src/ggml-hexagon/htp/htp-tensor.h) flushes modified tensor address ranges, or the
|
||||
- Use [`htp_dcache_flush_dirty_tensors()`](../../../ggml/src/ggml-hexagon/htp/htp-dcache.h) to flush specific dirty tensors back to DDR:
|
||||
- [`htp_dcache_flush_dirty_tensors()`](../../../ggml/src/ggml-hexagon/htp/htp-dcache.h) flushes modified tensor address ranges, or the
|
||||
full D-cache when their total size exceeds the flush threshold, ensuring peer devices and the host CPU observe consistent
|
||||
data in DDR.
|
||||
- Never signal completion before all DMA transfers are drained and dirty tensor flushes have completed.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
The cross-compilation is performed using the Snapdragon Linux Docker toolchain image (see
|
||||
[github.com/snapdragon-toolchain](https://github.com/snapdragon-toolchain)):
|
||||
|
||||
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.7`
|
||||
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.8`
|
||||
|
||||
The unified build utility (`scripts/snapdragon/build.py`) automatically pulls
|
||||
and orchestrates this container to perform target compilation. You only need to
|
||||
@@ -29,7 +29,7 @@ Alternatively, you can build llama.cpp manually by entering the cross-compilatio
|
||||
|
||||
```bash
|
||||
# Start the cross-compilation container manually:
|
||||
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-linux:v0.7
|
||||
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-linux:v0.8
|
||||
|
||||
# Inside the container, build the project using presets:
|
||||
[d]/workspace> cp docs/backend/snapdragon/CMakeUserPresets.json .
|
||||
|
||||
@@ -8,7 +8,7 @@ Native Windows 11 arm64 builds have the following tool dependencies:
|
||||
- UCRT and Driver Kit
|
||||
- LLVM core libraries and Clang compiler (winget)
|
||||
- CMake, Git, Python (winget)
|
||||
- Hexagon SDK Community Edition 6.6 or later (see below)
|
||||
- Hexagon SDK Community Edition 6.7 or later (see below)
|
||||
- OpenCL SDK 2.3 or later (see below)
|
||||
|
||||
Note: The rest of the **Windows** build process assumes that you're running natively in Powershell.
|
||||
@@ -54,15 +54,15 @@ c:\Qualcomm\OpenCL_SDK\2.3.2
|
||||
|
||||
Either use the trimmed down version (optimized for CI) from
|
||||
|
||||
https://github.com/snapdragon-toolchain/hexagon-sdk/releases/download/v6.6.0.0/hexagon-sdk-v6.6.0.0-arm64-wos.tar.xz
|
||||
https://github.com/snapdragon-toolchain/hexagon-sdk/releases/download/v6.7.0.0/hexagon-sdk-v6.7.0.0-arm64-wos.tar.xz
|
||||
|
||||
Or download the complete official version from
|
||||
|
||||
https://softwarecenter.qualcomm.com/catalog/item/Hexagon_SDK?version=6.6.0.0
|
||||
https://softwarecenter.qualcomm.com/catalog/item/Hexagon_SDK?version=6.7.0.0
|
||||
|
||||
Unzip/untar the archive into
|
||||
```
|
||||
c:\Qualcomm\Hexagon_SDK\6.6.0.0
|
||||
c:\Qualcomm\Hexagon_SDK\6.7.0.0
|
||||
```
|
||||
|
||||
## Install the latest Adreno GPU driver
|
||||
@@ -150,8 +150,8 @@ The overall Hexagon backend build procedure for Windows on Snapdragon is the sam
|
||||
However, additional settings are required for generating and signing HTP Ops libraries.
|
||||
```
|
||||
> $env:OPENCL_SDK_ROOT="C:\Qualcomm\OpenCL_SDK\2.3.2"
|
||||
> $env:HEXAGON_SDK_ROOT="C:\Qualcomm\Hexagon_SDK\6.6.0.0"
|
||||
> $env:HEXAGON_TOOLS_ROOT="C:\Qualcomm\Hexagon_SDK\6.6.0.0\tools\HEXAGON_Tools\19.0.07"
|
||||
> $env:HEXAGON_SDK_ROOT="C:\Qualcomm\Hexagon_SDK\6.7.0.0"
|
||||
> $env:HEXAGON_TOOLS_ROOT="C:\Qualcomm\Hexagon_SDK\6.7.0.0\tools\HEXAGON_Tools\21.0.04"
|
||||
> $env:HEXAGON_HTP_CERT="c:\Users\MyUsers\Certs\ggml-htp-v1.pfx"
|
||||
> $env:WINDOWS_SDK_BIN="C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0"
|
||||
|
||||
|
||||
+34
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
> [!WARNING]
|
||||
> **Note:** zDNN is **not** the same as ZenDNN.
|
||||
>
|
||||
> - **zDNN** (this page): IBM's Deep Neural Network acceleration library for IBM Z & LinuxONE Mainframes
|
||||
> - **ZenDNN**: AMD's deep learning library for AMD EPYC CPUs ([see ZenDNN documentation](ZenDNN.md))
|
||||
|
||||
@@ -46,9 +47,33 @@ The IBM zDNN backend has the following CMake options that control the behaviour
|
||||
| --------------------- | ------------- | -------------------------------------------------------------------------------------- |
|
||||
| `GGML_ZDNN_HIGH_PREC` | `0` | Set to `1` to requantize Q8_0 weights in smaller groups, for lower loss at lower speed |
|
||||
|
||||
## Performance Tunables
|
||||
|
||||
| Flag Option | Default Value | Description |
|
||||
| ----------------------- | ------------- | ------------------------------------------------------ |
|
||||
| `--flash-attn` or `-fa` | `auto` | Set to `on` to run attention on the NNPA (Recommended) |
|
||||
|
||||
Note: Set `-fa on` explicitly. Flash Attention fuses each layer's attention (the KQ matrix multiplication, softmax and KQV matrix multiplication) into one `FLASH_ATTN_EXT` operation, which zDNN runs on the NNPA instead of the CPU. The speedup grows with the context length. With `auto`, llama.cpp currently turns Flash Attention off, because the operation runs on a different device than the layer. zDNN runs it when the KV cache is F16 (the default), the model uses no attention sinks, ALiBi or logit softcapping, and there is one KV cache stream (one sequence, or `--kv-unified`). Otherwise it falls back to the CPU.
|
||||
|
||||
## 1. Install zDNN Library
|
||||
|
||||
Note: Using the zDNN library provided via `apt` or `yum` may not work correctly as reported in [#15772](https://github.com/ggml-org/llama.cpp/issues/15772). It is preferred that you compile from source.
|
||||
Note: A minimum of libzdnn 1.1.2 is required for the IBM zDNN backend to link correctly.
|
||||
|
||||
### Install using apt
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install -y libzdnn-dev
|
||||
```
|
||||
|
||||
### Install using yum
|
||||
|
||||
```sh
|
||||
sudo yum update
|
||||
sudo yum install -y libzdnn-devel
|
||||
```
|
||||
|
||||
### Install from source
|
||||
|
||||
```sh
|
||||
git clone --recurse-submodules https://github.com/IBM/zDNN
|
||||
@@ -73,3 +98,11 @@ cmake -S . -G Ninja -B build \
|
||||
-DZDNN_ROOT=/opt/zdnn-libs
|
||||
cmake --build build --config Release -j$(nproc)
|
||||
```
|
||||
|
||||
## 3. Run a model
|
||||
|
||||
```sh
|
||||
build/bin/llama-server -hf taronaeo/Granite-3.3-2B-Instruct-BE-GGUF:Q4_K_M -fa on
|
||||
```
|
||||
|
||||
Note: FlashAttention (`-fa on`) is strongly recommended as it forces attention to run on the IBM zAIU accelerator instead of the CPU ([see PR](https://github.com/ggml-org/llama.cpp/pull/30512)). By default, FlashAttention is turned off.
|
||||
|
||||
@@ -852,6 +852,36 @@ WebGPU allows cross-platform access to the GPU from supported browsers. We utili
|
||||
|
||||
Follow the instructions [here](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/) to download or build the emdawnwebgpu package (Note that it might be safer to build the emdawnwebgpu package locally, so that it stays in sync with the version of Dawn you have installed above). When building using CMake, the path to the emdawnwebgpu port file needs to be set with the flag `EMDAWNWEBGPU_DIR`.
|
||||
|
||||
## WASI
|
||||
|
||||
Requires [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) 34+ with `WASI_SDK_PATH` set. Only the libraries build (`llama-common` does not support WASI yet), so link your program against them:
|
||||
|
||||
```bash
|
||||
FLAGS="-msimd128 -D_WASI_EMULATED_SIGNAL -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
|
||||
cmake -B build-wasi -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-p2.cmake \
|
||||
-DCMAKE_C_FLAGS="$FLAGS" -DCMAKE_CXX_FLAGS="$FLAGS" -DCMAKE_HAVE_LIBC_PTHREAD=1 \
|
||||
-DBUILD_SHARED_LIBS=OFF -DGGML_NATIVE=OFF -DGGML_OPENMP=OFF -DLLAMA_BUILD_COMMON=OFF
|
||||
cmake --build build-wasi --target llama
|
||||
|
||||
$WASI_SDK_PATH/bin/clang++ --target=wasm32-wasip3 -O3 $FLAGS -I include -I ggml/include examples/simple/simple.cpp \
|
||||
build-wasi/src/libllama.a build-wasi/ggml/src/libggml*.a -lunwind -ldl -Wl,-z,stack-size=8388608 -o llama-simple.wasm
|
||||
wasmtime run -W exceptions=y --dir models llama-simple.wasm -m models/model.gguf "Hello"
|
||||
```
|
||||
|
||||
### wasi:webgpu
|
||||
|
||||
The WebGPU backend can target [wasi:webgpu](https://github.com/WebAssembly/wasi-webgpu) via [wasi-webgpu-headers](https://github.com/wasi-gfx/wasi-webgpu-headers) (with its submodule) and Dawn's `dawn-headers` release. Point `Dawn_DIR` at a directory containing this `DawnConfig.cmake` next to both:
|
||||
|
||||
```cmake
|
||||
set(_wasi_webgpu "${CMAKE_CURRENT_LIST_DIR}/wasi-webgpu-headers")
|
||||
add_library(dawn::webgpu_dawn INTERFACE IMPORTED)
|
||||
set_target_properties(dawn::webgpu_dawn PROPERTIES
|
||||
INTERFACE_SOURCES "${_wasi_webgpu}/webgpu.c;${_wasi_webgpu}/async_futures.c;${_wasi_webgpu}/imports.c;${_wasi_webgpu}/imports_component_type.o"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/dawn-headers/include/webgpu_upstream;${_wasi_webgpu}")
|
||||
```
|
||||
|
||||
Then build as above, but with `wasi-sdk-p3.cmake` / `--target=wasm32-wasip3`, `-DGGML_WEBGPU=ON -DDawn_DIR=...` at configure, and `build-wasi/ggml/src/ggml-webgpu/libggml-webgpu.a` added at link. See `.github/workflows/build-wasi.yml` for a more complete example.
|
||||
|
||||
## IBM Z & LinuxONE
|
||||
|
||||
To read documentation for how to build on IBM Z & LinuxONE, [click here](./build-s390x.md)
|
||||
|
||||
@@ -24,3 +24,267 @@ python3 llama-eval.py \
|
||||
--dataset aime2025 --n_cases 240 \
|
||||
--grader-type regex
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
A server started with `--api-key` (or any OpenAI-compatible endpoint behind a
|
||||
token) needs one here too. `--api-key` is sent as `Authorization: Bearer` on
|
||||
every eval request; it falls back to `$LLAMA_API_KEY`, which keeps the secret
|
||||
out of your shell history and off the process list.
|
||||
|
||||
```bash
|
||||
# One key for every server
|
||||
python3 llama-eval.py --server http://localhost:8033 --api-key sk-... ...
|
||||
|
||||
# From the environment
|
||||
export LLAMA_API_KEY=sk-...
|
||||
python3 llama-eval.py --server http://localhost:8033 ...
|
||||
|
||||
# One key per server, in --server order
|
||||
python3 llama-eval.py \
|
||||
--server http://server1:8033,http://server2:8033 \
|
||||
--api-key sk-one,sk-two --threads 16,16 ...
|
||||
```
|
||||
|
||||
The LLM grader authenticates too. It reuses the key of the server it runs on,
|
||||
so `--grader-type llm` without `--grader-server` needs nothing extra; a grader
|
||||
on a separate endpoint takes `--grader-api-key`.
|
||||
|
||||
Endpoints on an opencode host additionally get an `x-opencode-session` header,
|
||||
generated fresh for every task. An agentic episode sends the same id on all of
|
||||
its turns, so opencode sees one session per task instead of one per request;
|
||||
other servers never see the header.
|
||||
|
||||
## Agentic suite
|
||||
|
||||
`--dataset agentic` is a different shape from the other code suites. It runs
|
||||
**SACB** (Simple Agent Coding Benchmark), hosted at
|
||||
[`ilintar/SACB`](https://huggingface.co/datasets/ilintar/SACB) -- 60 tasks over
|
||||
three hand-written repositories, in Python and TypeScript. Rather than
|
||||
answering one prompt, the model is given a small but real repository, a
|
||||
deliberately narrow tool set, and a bug report, and drives its own multi-turn
|
||||
conversation until it declares itself finished. It is then graded by running a
|
||||
test suite it was never allowed to see.
|
||||
|
||||
```bash
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset agentic --threads 1 --temperature 0
|
||||
```
|
||||
|
||||
### The tools
|
||||
|
||||
| tool | purpose |
|
||||
|---|---|
|
||||
| `list_files` | enumerate the tree |
|
||||
| `read_file` | read, with 1-based line numbers and optional line-range narrowing |
|
||||
| `search` | regular-expression search across the repository |
|
||||
| `edit_lines` | replace an inclusive line range |
|
||||
| `edit_replace` | replace an exact snippet, which must be unique |
|
||||
| `write_file` | write a whole file |
|
||||
| `lint` | run the language's linter and type checker |
|
||||
| `finish` | declare the change complete |
|
||||
|
||||
Three constraints are deliberate:
|
||||
|
||||
**There is no shell and no way to run the tests.** The only feedback channel is
|
||||
`lint`. A syntax or type error can be driven out mechanically; a logic error has
|
||||
to be reasoned about. A harness that lets an agent iterate against the test
|
||||
suite measures something closer to search than to understanding.
|
||||
|
||||
**There are two edit tools**, one line-addressed and one content-addressed.
|
||||
Which one a model reaches for, and whether it keeps line numbers straight after
|
||||
its own earlier edit, is itself a signal.
|
||||
|
||||
**The tests never touch the working tree.** They are held outside it and copied
|
||||
into a throwaway copy only after the agent has stopped, so they can be neither
|
||||
read nor edited. Every tool path is resolved and confinement-checked against the
|
||||
repository root, so a symlink planted inside the tree cannot reach outside it.
|
||||
|
||||
### Scoring
|
||||
|
||||
A task is **resolved** when every test that was failing now passes *and* nothing
|
||||
that was already passing broke. Both sets are derived by running the tests twice
|
||||
when the corpus is built -- once against the defective tree and once against the
|
||||
reference fix -- never hand-written, so a task whose defect no test exercises is
|
||||
rejected rather than shipped.
|
||||
|
||||
Because many tasks contain several independent defects, the fraction of target
|
||||
tests passed is reported alongside, which separates "fixed two of the three
|
||||
faults" from "changed nothing". Process metrics are reported too: turns, tool
|
||||
calls, rejected calls, edits, and the peak conversation size.
|
||||
|
||||
### Dependencies
|
||||
|
||||
Nothing here is installed unless you select this suite, and then only for the
|
||||
language you actually run. `--agentic-lang python` provisions ruff and mypy;
|
||||
`--agentic-lang typescript` provisions the TypeScript compiler and uses node's
|
||||
own test runner. Neither touches the other, and no other suite touches either.
|
||||
|
||||
### Useful flags
|
||||
|
||||
| flag | meaning |
|
||||
|---|---|
|
||||
| `--agentic-lang` | restrict to one language, and provision only its toolchain |
|
||||
| `--agentic-max-turns` | give up after this many assistant turns (default 50) |
|
||||
| `--agentic-max-tokens` | cap generation per turn (default: no cap); bounds a model that falls into a repetition loop, but also cuts reasoning off mid-thought. Truncated turns are counted as `truncated_turns` |
|
||||
| `--agentic-context-limit` | abandon an episode once its context exceeds this many tokens |
|
||||
| `--agentic-transcripts DIR` | save each episode's full message history to `DIR/<task_id>.json`, plus a `DIR/<task_id>.html` showing every reasoning block, reply, tool call and tool result; the main report links to it |
|
||||
|
||||
|
||||
## Code suites (HumanEval, ClassEval)
|
||||
|
||||
`--dataset humaneval` runs the 164 OpenAI HumanEval problems; `--dataset
|
||||
classeval` runs the 100 ClassEval class-generation tasks. Unlike the other
|
||||
suites these are not graded by matching text: the model's reply is parsed for a
|
||||
```` ```python ```` block, that code is combined with the task's own unit tests,
|
||||
and the result is **executed**.
|
||||
|
||||
ClassEval asks for a whole class rather than one function, so it is scored the
|
||||
way upstream does - **class level** (every one of the task's tests passes, which
|
||||
is what feeds pass@k) and **method level** (what fraction of all 2196 test
|
||||
methods passed). The second number separates "generated nothing usable" from
|
||||
"got most of the class right", and both are printed at the end of a run.
|
||||
|
||||
Give it more room to generate than HumanEval - a class with ten methods does not
|
||||
fit in a short budget:
|
||||
|
||||
```bash
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset classeval --threads 8 --n_predict 4096 --temperature 0
|
||||
```
|
||||
|
||||
```bash
|
||||
# pass@1, greedy
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset humaneval --threads 8 --n_predict 1024 --temperature 0
|
||||
|
||||
# pass@10 -- each extra multiple of 164 is another sample per problem
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset humaneval --n_cases 1640 --temperature 0.8 --top-p 0.95
|
||||
```
|
||||
|
||||
`--grader-type exec` is the default for code suites, so neither it nor
|
||||
`--dataset-source` normally needs to be passed.
|
||||
|
||||
### Execution sandbox - no Docker required
|
||||
|
||||
Generated code is untrusted, but these suites need far less than a container to
|
||||
run safely. **The package set is derived from the dataset's own imports at
|
||||
startup**, not hardcoded and not taken from the upstream `requirements.txt` -
|
||||
which for ClassEval lists `torch`, `openai` and `transformers` that its *tests*
|
||||
never touch. So each suite installs exactly what it needs:
|
||||
|
||||
| suite | third-party packages |
|
||||
|---|---|
|
||||
| HumanEval | **none** - every problem imports only stdlib (`typing, math, random, copy, string, collections, hashlib, re`) |
|
||||
| ClassEval | 13: `beautifulsoup4, gensim, lxml, netifaces, nltk, numpy, openpyxl, pandas, pillow, pypdf2, python-docx, reportlab, scipy` |
|
||||
|
||||
Two of ClassEval's are *declared* rather than derived, because no import
|
||||
statement mentions them: `lxml` is selected by string
|
||||
(`BeautifulSoup(html, 'lxml')`, ClassEval_44 - without it that task silently
|
||||
drops from 23/23 to 7/23), and `scipy` is a hard gensim requirement. Note also
|
||||
that `PyPDF2` must **not** be modernised to `pypdf`: ClassEval_69's test imports
|
||||
`PdfFileReader`, a name pypdf removed.
|
||||
|
||||
The venv is cached under `~/.cache/llama-eval/` (override with
|
||||
`LLAMA_EVAL_CACHE`) and rebuilt only when the package set, interpreter version
|
||||
or provisioning steps change; force one with `--rebuild-sandbox`. `uv` is used
|
||||
when available, which makes even the ClassEval venv build in seconds.
|
||||
|
||||
**Interpreter selection.** A suite can prefer a different Python than the one
|
||||
running the tool, and declares it: ClassEval asks for 3.13, because gensim
|
||||
publishes no wheel past cp313 and its generated C no longer compiles on 3.14
|
||||
(it still uses `PyLongObject.ob_digit`). That interpreter is found automatically,
|
||||
including via `uv python`; `--sandbox-python` overrides it.
|
||||
|
||||
3.14 is fully supported anyway. ClassEval uses exactly two gensim APIs
|
||||
(`utils.decode_htmlentities`, `matutils.unitvec`), both pure Python upstream, so
|
||||
on 3.14+ the suite drops gensim from the install set and provisions a small shim
|
||||
instead. Both paths score **100/100**.
|
||||
|
||||
**Offline provisioning.** A suite that would otherwise fetch data at test time
|
||||
declares a provisioning step that runs once at venv build time, while the
|
||||
network is still up. ClassEval uses this to pre-seed exactly three nltk corpora
|
||||
(`punkt_tab`, `averaged_perceptron_tagger_eng`, `wordnet`, ~33 MB), so the tests
|
||||
themselves run with no network at all. Those are the *current* names - the
|
||||
dataset asks for `punkt` and `averaged_perceptron_tagger`, which nltk has since
|
||||
renamed, and seeding the legacy names too is not harmless: with a network
|
||||
available nltk considers them satisfied and re-fetches the modern ones anyway.
|
||||
The corpora are written *inside* the venv, not `~/nltk_data`, because `$HOME` is
|
||||
a tmpfs under the strongest isolation tier.
|
||||
|
||||
Each candidate program runs in a throwaway directory as its own process group,
|
||||
under the strongest isolation the host offers:
|
||||
|
||||
| tier | requires | isolates |
|
||||
|---|---|---|
|
||||
| `bwrap` | bubblewrap installed | read-only root, tmpfs `/tmp` and `$HOME`, **no network** |
|
||||
| `unshare` | `unshare --map-current-user` | **no network** |
|
||||
| `unshare-root` | any `unshare -r` | no network, but **runs as uid 0** - see below |
|
||||
| `rlimit` | always available | cpu/memory/file-size/process caps only |
|
||||
|
||||
**Candidate code must not run as root**, which is why `unshare -r` is a
|
||||
last-resort tier that prints a warning. Root bypasses file permission bits, so a
|
||||
test asserting that writing to a `chmod 0444` file *fails* instead sees it
|
||||
succeed - ClassEval_50 scores 14/16 under uid 0 and 16/16 under uid 1000, with
|
||||
nothing in the output to suggest the sandbox caused it. Install bubblewrap.
|
||||
|
||||
All tiers additionally cap CPU time, address space (4 GiB), file size and core
|
||||
dumps, and kill the whole process group on timeout (`--exec-timeout`, default
|
||||
15 s - raise it for ClassEval, whose tests do real work). Writes to the
|
||||
throwaway working directory are allowed, which is all the file-handling tasks in
|
||||
ClassEval need.
|
||||
|
||||
Note that the weakest tier does not contain filesystem access. If you are
|
||||
scoring output from a model you do not trust, check the tier printed at startup.
|
||||
|
||||
### Reproducibility
|
||||
|
||||
Several tasks draw their test inputs from the *global, unseeded* RNG, so a
|
||||
verdict is not reproducible run to run - which adds noise exactly when you are
|
||||
comparing two models or two quantisations. `--exec-seed` (default `0`) seeds the
|
||||
RNG before each test; pass `--exec-seed -1` for the stock upstream behaviour.
|
||||
This does not change what the model is asked to compute.
|
||||
|
||||
It matters more than it sounds. HumanEval/38, /50 and /53 are affected, and
|
||||
ClassEval_58 (duplicate mine placement) fails about 30% of the time on its own
|
||||
*reference* solution - measured here at 21/30 passes unseeded versus **30/30
|
||||
seeded**.
|
||||
|
||||
### Dataset fidelity
|
||||
|
||||
The canonical dataset runs **unmodified on current Python** - all 164 reference
|
||||
solutions pass on CPython 3.14 with no patches, so no forked or re-hosted copy
|
||||
is needed. `--dataset-source` accepts a local `.jsonl` or a HuggingFace repo id
|
||||
if you do want to point at a modified copy.
|
||||
|
||||
HumanEval does contain a handful of long-standing defects where the *prompt*
|
||||
contradicts the graded test (notably HumanEval/47, /116 and /148). Those
|
||||
penalise a model that reads the docstring carefully, but correcting them makes
|
||||
the benchmark marginally easier and the resulting scores no longer comparable
|
||||
with published HumanEval numbers, so nothing is patched by default.
|
||||
|
||||
ClassEval is different: the upstream data has defects that cap the achievable
|
||||
score below 100% for reasons unrelated to the model, so `--dataset classeval`
|
||||
defaults to a **patched fork**,
|
||||
[`ilintar/ClassEval`](https://huggingface.co/datasets/ilintar/ClassEval).
|
||||
Six edits over five tasks lift the reference solutions from 96/100 classes and
|
||||
2182/2196 test methods to **100/100 and 2196/2196**. Three repair tests nothing
|
||||
could pass:
|
||||
|
||||
- **ClassEval_17** was a time bomb - a hard-coded 2024 date checked against
|
||||
`datetime.now()`, unpassable since January 2024.
|
||||
- **ClassEval_48** asserted `get_hostname('0.0.0.0') == 'LAPTOP-2CS86KUM'`, the
|
||||
dataset author's own machine name.
|
||||
- **ClassEval_31** compared floats with `assertEqual`, failing on a 4-ULP numpy
|
||||
difference.
|
||||
|
||||
The other three fix only the *reference* solutions (NumPy 2.0 removals in
|
||||
ClassEval_51, a 30%-flaky mine generator in ClassEval_58) and cannot affect a
|
||||
model's score. No `skeleton` was touched, so what the model is asked to write is
|
||||
byte-identical to upstream. Pass `--dataset-source FudanSELab/ClassEval` for the
|
||||
unpatched original.
|
||||
|
||||
@@ -0,0 +1,918 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Multi-turn agentic coding eval: tools over a real working tree.
|
||||
|
||||
The existing code suites are single-shot -- one prompt, one reply, execute,
|
||||
grade. This one is not. The model is given a small but real repository and a
|
||||
deliberately narrow tool set, and has to find its way to the defect before it
|
||||
can fix it. That shape is what puts a run in the tens-of-thousands-of-tokens
|
||||
range without any artificial padding: the context grows because the model
|
||||
chose to read things.
|
||||
|
||||
Three deliberate constraints:
|
||||
|
||||
* No shell, and no way to run the tests. The only feedback channel is `lint`.
|
||||
A syntax or type error can be driven out mechanically; a logic error has to
|
||||
be reasoned about. Suites that let an agent iterate against the test suite
|
||||
measure something closer to search than to understanding.
|
||||
* Two edit tools, one line-addressed and one content-addressed. Which one a
|
||||
model reaches for, and whether it keeps line numbers straight after its own
|
||||
earlier edit, is itself a signal.
|
||||
* Tests never touch the working tree. They are held outside it and copied in
|
||||
after the agent has stopped, so they can be neither read nor edited.
|
||||
|
||||
Everything here is imported lazily by llama-eval.py, and every dependency it
|
||||
needs is scoped to the language actually under test, so none of it is
|
||||
installed for anyone running the other suites.
|
||||
"""
|
||||
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
MAX_READ_LINES = 400
|
||||
MAX_TOOL_CHARS = 12000
|
||||
MAX_SEARCH_HITS = 60
|
||||
|
||||
|
||||
@dataclass
|
||||
class LangSpec:
|
||||
"""Everything language-specific: how to lint, how to test, what to install.
|
||||
|
||||
Kept as data so adding a language is a corpus concern rather than a harness
|
||||
change, and so a run only ever provisions the toolchain for the language it
|
||||
is actually evaluating.
|
||||
"""
|
||||
name: str
|
||||
exts: Tuple[str, ...]
|
||||
packages: Tuple[str, ...] = ()
|
||||
provision: Tuple[str, ...] = ()
|
||||
env: Dict[str, str] = field(default_factory=dict)
|
||||
address_space_limit: Optional[int] = 4 * 1024 * 1024 * 1024
|
||||
|
||||
def source_files(self, root: Path) -> List[Path]:
|
||||
return sorted(p for p in root.rglob("*")
|
||||
if p.is_file() and p.suffix in self.exts)
|
||||
|
||||
|
||||
PYTHON = LangSpec(
|
||||
name="python",
|
||||
exts=(".py",),
|
||||
packages=("ruff==0.14.2", "mypy==1.18.2"),
|
||||
)
|
||||
|
||||
TYPESCRIPT = LangSpec(
|
||||
name="typescript",
|
||||
exts=(".ts",),
|
||||
address_space_limit=16 * 1024 * 1024 * 1024,
|
||||
provision=(
|
||||
"import os, subprocess, shutil, sys\n"
|
||||
"npm = shutil.which('npm')\n"
|
||||
"if not npm: sys.exit('npm not found on PATH; needed for the typescript suite')\n"
|
||||
"dest = os.path.join(os.environ['SANDBOX_VENV'], 'node')\n"
|
||||
"os.makedirs(dest, exist_ok=True)\n"
|
||||
"r = subprocess.run([npm, 'install', '--silent', '--no-audit', '--no-fund',\n"
|
||||
" '--prefix', dest, 'typescript@5.9.3'],\n"
|
||||
" capture_output=True, text=True)\n"
|
||||
"sys.exit(0 if r.returncode == 0 else (r.stderr or r.stdout)[-2000:])\n",
|
||||
),
|
||||
)
|
||||
|
||||
LANGS: Dict[str, LangSpec] = {"python": PYTHON, "typescript": TYPESCRIPT}
|
||||
|
||||
|
||||
class PathEscape(Exception):
|
||||
"""A tool was handed a path outside the repository."""
|
||||
|
||||
|
||||
class Workspace:
|
||||
"""A private, writable copy of one task's repository.
|
||||
|
||||
Every tool path is resolved through here. Confinement is checked on the
|
||||
fully-resolved path, so a symlink planted inside the tree cannot be used to
|
||||
read or write outside it.
|
||||
"""
|
||||
|
||||
def __init__(self, root: Path):
|
||||
self.root = root.resolve()
|
||||
self.escape_attempts = 0
|
||||
|
||||
def resolve(self, rel: str) -> Path:
|
||||
if rel is None or not str(rel).strip():
|
||||
raise PathEscape("no path given")
|
||||
raw = Path(str(rel))
|
||||
base = raw if raw.is_absolute() else (self.root / raw)
|
||||
candidate = base.resolve()
|
||||
if candidate != self.root and self.root not in candidate.parents:
|
||||
self.escape_attempts += 1
|
||||
raise PathEscape(f"path {rel!r} is outside the repository")
|
||||
return candidate
|
||||
|
||||
def rel(self, path: Path) -> str:
|
||||
return str(path.relative_to(self.root))
|
||||
|
||||
def files(self) -> List[Path]:
|
||||
return sorted(p for p in self.root.rglob("*")
|
||||
if p.is_file() and "__pycache__" not in p.parts)
|
||||
|
||||
def snapshot(self) -> Dict[str, str]:
|
||||
out = {}
|
||||
for p in self.files():
|
||||
try:
|
||||
out[self.rel(p)] = p.read_text()
|
||||
except (UnicodeDecodeError, OSError):
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def _clip(text: str, limit: int = MAX_TOOL_CHARS) -> str:
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
return text[:limit] + f"\n... [truncated, {len(text) - limit} more characters]"
|
||||
|
||||
|
||||
def _numbered(lines: Sequence[str], start: int) -> str:
|
||||
width = len(str(start + len(lines) - 1))
|
||||
return "\n".join(f"{start + i:>{width}}\t{ln}" for i, ln in enumerate(lines))
|
||||
|
||||
|
||||
def tool_schemas() -> List[Dict[str, Any]]:
|
||||
"""OpenAI-format function schemas for the whole tool set."""
|
||||
def fn(name, desc, props, required=()):
|
||||
return {"type": "function", "function": {
|
||||
"name": name, "description": desc,
|
||||
"parameters": {"type": "object", "properties": props,
|
||||
"required": list(required)}}}
|
||||
|
||||
return [
|
||||
fn("list_files", "List files in the repository.",
|
||||
{"path": {"type": "string",
|
||||
"description": "Directory relative to the repository root. "
|
||||
"Omit for the whole tree."}}),
|
||||
fn("read_file",
|
||||
"Read a file. Output is prefixed with 1-based line numbers. Give "
|
||||
"start_line/end_line to read only part of a large file.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"start_line": {"type": "integer", "description": "First line to read, 1-based, inclusive."},
|
||||
"end_line": {"type": "integer", "description": "Last line to read, 1-based, inclusive."}},
|
||||
["path"]),
|
||||
fn("search",
|
||||
"Search the repository with a regular expression. Returns matching "
|
||||
"lines with their file and line number.",
|
||||
{"pattern": {"type": "string", "description": "Python regular expression."},
|
||||
"path": {"type": "string", "description": "Limit the search to this subdirectory."},
|
||||
"glob": {"type": "string", "description": "Limit to files matching this glob, e.g. '*.py'."}},
|
||||
["pattern"]),
|
||||
fn("edit_lines",
|
||||
"Replace an inclusive 1-based line range with new text. Line numbers "
|
||||
"refer to the file as it is now, so re-read it after any edit that "
|
||||
"changed the number of lines.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"start_line": {"type": "integer", "description": "First line to replace, 1-based, inclusive."},
|
||||
"end_line": {"type": "integer", "description": "Last line to replace, 1-based, inclusive."},
|
||||
"new_text": {"type": "string", "description": "Replacement text. Use an empty string to delete the range."}},
|
||||
["path", "start_line", "end_line", "new_text"]),
|
||||
fn("edit_replace",
|
||||
"Replace an exact snippet of text. The snippet must occur exactly "
|
||||
"once in the file unless replace_all is set.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"old_text": {"type": "string", "description": "Exact text to find, including indentation."},
|
||||
"new_text": {"type": "string", "description": "Replacement text."},
|
||||
"replace_all": {"type": "boolean", "description": "Replace every occurrence instead of requiring exactly one."}},
|
||||
["path", "old_text", "new_text"]),
|
||||
fn("write_file",
|
||||
"Write a whole file, creating or overwriting it. Prefer the edit "
|
||||
"tools for changes to existing files.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"content": {"type": "string", "description": "Full new file contents."}},
|
||||
["path", "content"]),
|
||||
fn("lint",
|
||||
"Run the language's linter and type checker over the repository and "
|
||||
"return the diagnostics. This is the only feedback available; the "
|
||||
"test suite cannot be run.",
|
||||
{"path": {"type": "string", "description": "Limit to this file or directory."}}),
|
||||
fn("finish",
|
||||
"Declare the task complete and end the session. This is the only "
|
||||
"way to end it; call this once the change is made.",
|
||||
{"summary": {"type": "string", "description": "Brief description of the change made."}}),
|
||||
]
|
||||
|
||||
|
||||
class ToolBox:
|
||||
"""Executes tool calls against one workspace. Returns plain text results.
|
||||
|
||||
Failures are reported as ordinary text rather than raised, because how a
|
||||
model recovers from a rejected edit is part of what is being measured.
|
||||
"""
|
||||
|
||||
def __init__(self, ws: Workspace, linter=None):
|
||||
self.ws = ws
|
||||
self.linter = linter
|
||||
self.calls = 0
|
||||
self.errors = 0
|
||||
self.edits = 0
|
||||
self.finished = False
|
||||
self.finish_summary: Optional[str] = None
|
||||
self.per_tool: Dict[str, int] = {}
|
||||
|
||||
def dispatch(self, name: str, args: Dict[str, Any]) -> str:
|
||||
self.calls += 1
|
||||
self.per_tool[name] = self.per_tool.get(name, 0) + 1
|
||||
handler = getattr(self, f"_t_{name}", None)
|
||||
if handler is None:
|
||||
result = f"Error: unknown tool {name!r}."
|
||||
else:
|
||||
try:
|
||||
result = handler(args)
|
||||
except PathEscape as e:
|
||||
result = f"Error: {e}"
|
||||
except KeyError as e:
|
||||
result = f"Error: missing required argument {e}."
|
||||
except Exception as e:
|
||||
result = f"Error: {type(e).__name__}: {e}"
|
||||
if result.startswith("Error:"):
|
||||
self.errors += 1
|
||||
return _clip(result)
|
||||
|
||||
def _t_list_files(self, a):
|
||||
base = self.ws.resolve(a.get("path") or ".")
|
||||
if not base.exists():
|
||||
return f"Error: {a.get('path')!r} does not exist."
|
||||
paths = [p for p in self.ws.files() if p == base or base in p.parents]
|
||||
if not paths:
|
||||
return "(no files)"
|
||||
return "\n".join(f"{self.ws.rel(p)}\t{p.stat().st_size} bytes" for p in paths)
|
||||
|
||||
def _t_read_file(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
lines = path.read_text().splitlines()
|
||||
start = max(1, int(a.get("start_line") or 1))
|
||||
end = int(a.get("end_line") or len(lines))
|
||||
end = min(end, len(lines))
|
||||
if start > len(lines):
|
||||
return f"Error: {a['path']!r} has only {len(lines)} lines."
|
||||
window = lines[start - 1:end]
|
||||
note = ""
|
||||
if len(window) > MAX_READ_LINES:
|
||||
window = window[:MAX_READ_LINES]
|
||||
note = (f"\n... [showing {MAX_READ_LINES} of {end - start + 1} requested "
|
||||
f"lines; re-read with start_line/end_line for the rest]")
|
||||
header = f"{a['path']} ({len(lines)} lines total)\n"
|
||||
return header + _numbered(window, start) + note
|
||||
|
||||
def _t_search(self, a):
|
||||
try:
|
||||
rx = re.compile(a["pattern"])
|
||||
except re.error as e:
|
||||
return f"Error: bad regular expression: {e}"
|
||||
base = self.ws.resolve(a.get("path") or ".")
|
||||
glob = a.get("glob")
|
||||
hits, scanned = [], 0
|
||||
for p in self.ws.files():
|
||||
if not (p == base or base in p.parents):
|
||||
continue
|
||||
if glob and not p.match(glob):
|
||||
continue
|
||||
try:
|
||||
text = p.read_text()
|
||||
except (UnicodeDecodeError, OSError):
|
||||
continue
|
||||
scanned += 1
|
||||
for i, line in enumerate(text.splitlines(), 1):
|
||||
if rx.search(line):
|
||||
hits.append(f"{self.ws.rel(p)}:{i}:{line.strip()[:200]}")
|
||||
if len(hits) >= MAX_SEARCH_HITS:
|
||||
return ("\n".join(hits) +
|
||||
f"\n... [stopped at {MAX_SEARCH_HITS} matches; narrow the pattern]")
|
||||
if not hits:
|
||||
return f"No matches for {a['pattern']!r} in {scanned} file(s)."
|
||||
return "\n".join(hits)
|
||||
|
||||
def _t_edit_lines(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
lines = path.read_text().splitlines()
|
||||
start, end = int(a["start_line"]), int(a["end_line"])
|
||||
if start < 1 or start > len(lines):
|
||||
return (f"Error: start_line {start} out of range; "
|
||||
f"{a['path']!r} has {len(lines)} lines.")
|
||||
if end < start or end > len(lines):
|
||||
return (f"Error: end_line {end} out of range; must be between "
|
||||
f"{start} and {len(lines)}.")
|
||||
new = str(a["new_text"]).splitlines()
|
||||
path.write_text("\n".join(lines[:start - 1] + new + lines[end:]) + "\n")
|
||||
self.edits += 1
|
||||
delta = len(new) - (end - start + 1)
|
||||
return (f"Replaced lines {start}-{end} of {a['path']} "
|
||||
f"({end - start + 1} -> {len(new)} lines, {delta:+d}). "
|
||||
f"Later line numbers have shifted." if delta else
|
||||
f"Replaced lines {start}-{end} of {a['path']}.")
|
||||
|
||||
def _t_edit_replace(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
text = path.read_text()
|
||||
old, new = str(a["old_text"]), str(a["new_text"])
|
||||
if not old:
|
||||
return "Error: old_text must not be empty."
|
||||
n = text.count(old)
|
||||
if n == 0:
|
||||
return (f"Error: old_text not found in {a['path']}. It must match "
|
||||
f"exactly, including indentation.")
|
||||
if n > 1 and not a.get("replace_all"):
|
||||
return (f"Error: old_text occurs {n} times in {a['path']}. Include "
|
||||
f"more surrounding context to make it unique, or set "
|
||||
f"replace_all.")
|
||||
path.write_text(text.replace(old, new))
|
||||
self.edits += 1
|
||||
return f"Replaced {n} occurrence(s) in {a['path']}."
|
||||
|
||||
def _t_write_file(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
existed = path.is_file()
|
||||
path.write_text(str(a["content"]))
|
||||
self.edits += 1
|
||||
return f"{'Overwrote' if existed else 'Created'} {a['path']}."
|
||||
|
||||
def _t_lint(self, a):
|
||||
if self.linter is None:
|
||||
return "Error: no linter is configured for this task."
|
||||
target = a.get("path")
|
||||
if target:
|
||||
self.ws.resolve(target)
|
||||
return self.linter(target)
|
||||
|
||||
def _t_finish(self, a):
|
||||
self.finished = True
|
||||
self.finish_summary = a.get("summary")
|
||||
return "Task marked complete."
|
||||
|
||||
|
||||
PY_TEST_RUNNER = r'''
|
||||
import json, os, random, sys, unittest
|
||||
sys.path.insert(0, os.getcwd())
|
||||
random.seed(0)
|
||||
|
||||
class Collect(unittest.TestResult):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.outcomes = {}
|
||||
def addSuccess(self, t): self.outcomes[t.id()] = "pass"
|
||||
def addFailure(self, t, e): self.outcomes[t.id()] = "fail"
|
||||
def addError(self, t, e): self.outcomes[t.id()] = "error"
|
||||
def addSkip(self, t, r): self.outcomes[t.id()] = "skip"
|
||||
def addExpectedFailure(self, t, e): self.outcomes[t.id()] = "pass"
|
||||
def addUnexpectedSuccess(self, t): self.outcomes[t.id()] = "fail"
|
||||
|
||||
def flatten(s):
|
||||
for t in s:
|
||||
if isinstance(t, unittest.TestSuite):
|
||||
yield from flatten(t)
|
||||
else:
|
||||
yield t
|
||||
|
||||
out = {"tests": {}}
|
||||
try:
|
||||
suite = unittest.TestLoader().discover(os.environ.get("TEST_DIR", "tests"),
|
||||
top_level_dir=".")
|
||||
res = Collect()
|
||||
for t in flatten(suite):
|
||||
tid = t.id()
|
||||
try:
|
||||
t.run(res)
|
||||
except Exception:
|
||||
pass
|
||||
res.outcomes.setdefault(tid, "error")
|
||||
out["tests"] = res.outcomes
|
||||
except Exception as e:
|
||||
out["error"] = f"{type(e).__name__}: {e}"
|
||||
|
||||
print("###RESULT###")
|
||||
print(json.dumps(out))
|
||||
'''
|
||||
|
||||
|
||||
def _node_roots() -> List[Path]:
|
||||
"""Directories that must stay visible for node to start under bwrap."""
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
return []
|
||||
real = Path(node).resolve()
|
||||
return [Path(node).parent.parent, real.parent.parent]
|
||||
|
||||
|
||||
class Runner:
|
||||
"""Runs the linter and the hidden tests for one language, in the sandbox."""
|
||||
|
||||
def __init__(self, sandbox, lang: LangSpec, timeout: float = 90.0):
|
||||
self.sandbox = sandbox
|
||||
self.lang = lang
|
||||
self.timeout = timeout
|
||||
|
||||
def _binds(self) -> List[Path]:
|
||||
return _node_roots() if self.lang.name == "typescript" else []
|
||||
|
||||
def _env(self) -> Dict[str, str]:
|
||||
if self.lang.name != "typescript":
|
||||
return {}
|
||||
node = shutil.which("node")
|
||||
return {"PATH": f"{Path(node).parent}:/usr/bin:/bin"} if node else {}
|
||||
|
||||
def lint(self, workdir: Path, target: Optional[str] = None) -> str:
|
||||
if self.lang.name == "python":
|
||||
return self._lint_python(workdir, target)
|
||||
return self._lint_typescript(workdir, target)
|
||||
|
||||
def _lint_python(self, workdir: Path, target: Optional[str]) -> str:
|
||||
venv = self.sandbox.venv_dir
|
||||
tgt = target or "."
|
||||
chunks = []
|
||||
r = self.sandbox.run_argv(
|
||||
[str(venv / "bin" / "ruff"), "check", "--no-cache",
|
||||
"--output-format", "concise", tgt],
|
||||
workdir, timeout=self.timeout)
|
||||
text = (r.stdout or r.stderr).strip()
|
||||
if text and "All checks passed" not in text:
|
||||
chunks.append(text)
|
||||
r = self.sandbox.run_argv(
|
||||
[str(venv / "bin" / "mypy"), "--no-color-output", "--no-error-summary",
|
||||
"--cache-dir", "/dev/null", "--ignore-missing-imports", tgt],
|
||||
workdir, timeout=self.timeout)
|
||||
text = (r.stdout or r.stderr).strip()
|
||||
if text and not text.startswith("Success:"):
|
||||
chunks.append(text)
|
||||
return "\n".join(chunks) if chunks else "No problems found."
|
||||
|
||||
def _lint_typescript(self, workdir: Path, target: Optional[str]) -> str:
|
||||
tsc = self.sandbox.venv_dir / "node" / "node_modules" / "typescript" / "bin" / "tsc"
|
||||
files = [str(p.relative_to(workdir)) for p in sorted(workdir.rglob("*.ts"))
|
||||
if "node_modules" not in p.parts]
|
||||
if target:
|
||||
t = (workdir / target).resolve()
|
||||
files = [f for f in files if (workdir / f).resolve() == t
|
||||
or t in (workdir / f).resolve().parents]
|
||||
if not files:
|
||||
return "No TypeScript files to check."
|
||||
r = self.sandbox.run_argv(
|
||||
["node", str(tsc), "--noEmit", "--strict", "--target", "es2022",
|
||||
"--module", "esnext", "--moduleResolution", "bundler",
|
||||
"--allowImportingTsExtensions", *files],
|
||||
workdir, timeout=self.timeout, env=self._env(), ro_binds=self._binds())
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
return out.strip() or "No problems found."
|
||||
|
||||
def test(self, workdir: Path) -> Dict[str, Any]:
|
||||
if self.lang.name == "python":
|
||||
return self._test_python(workdir)
|
||||
return self._test_typescript(workdir)
|
||||
|
||||
def _test_python(self, workdir: Path) -> Dict[str, Any]:
|
||||
runner = workdir / "__run_tests.py"
|
||||
runner.write_text(PY_TEST_RUNNER)
|
||||
r = self.sandbox.run_argv(
|
||||
[str(self.sandbox.python), "-I", "-B", str(runner)],
|
||||
workdir, timeout=self.timeout, env={"TEST_DIR": "tests"})
|
||||
return self._parse_json_result(r)
|
||||
|
||||
def _parse_json_result(self, r) -> Dict[str, Any]:
|
||||
if r.status == "timeout":
|
||||
return {"tests": {}, "error": "timeout"}
|
||||
marker = "###RESULT###"
|
||||
if marker not in (r.stdout or ""):
|
||||
return {"tests": {},
|
||||
"error": f"no result: {(r.stderr or r.stdout or '')[-400:]}"}
|
||||
try:
|
||||
return json.loads(r.stdout.split(marker, 1)[1].strip())
|
||||
except Exception as e:
|
||||
return {"tests": {}, "error": f"unparseable result: {e}"}
|
||||
|
||||
def _test_typescript(self, workdir: Path) -> Dict[str, Any]:
|
||||
tests = sorted(p for p in (workdir / "tests").rglob("*.ts")) \
|
||||
if (workdir / "tests").is_dir() else []
|
||||
if not tests:
|
||||
return {"tests": {}, "error": "no test files"}
|
||||
r = self.sandbox.run_argv(
|
||||
["node", "--test", "--experimental-strip-types",
|
||||
"--test-reporter=tap",
|
||||
*[str(p.relative_to(workdir)) for p in tests]],
|
||||
workdir, timeout=self.timeout, env=self._env(), ro_binds=self._binds())
|
||||
return self._parse_tap(r)
|
||||
|
||||
@staticmethod
|
||||
def _parse_tap(r) -> Dict[str, Any]:
|
||||
if r.status == "timeout":
|
||||
return {"tests": {}, "error": "timeout"}
|
||||
text = (r.stdout or "") + "\n" + (r.stderr or "")
|
||||
outcomes: Dict[str, str] = {}
|
||||
for line in text.splitlines():
|
||||
m = re.match(r"\s*(not )?ok\s+\d+\s*-\s*(.+?)\s*$", line)
|
||||
if not m:
|
||||
continue
|
||||
name = m.group(2).strip()
|
||||
if name.endswith(" # SKIP") or " # SKIP" in name:
|
||||
outcomes[name.split(" # ")[0]] = "skip"
|
||||
continue
|
||||
verdict = "fail" if m.group(1) else "pass"
|
||||
if outcomes.get(name) != "fail":
|
||||
outcomes[name] = verdict
|
||||
if not outcomes:
|
||||
return {"tests": {},
|
||||
"error": f"no TAP output: {text.strip()[-400:]}"}
|
||||
return {"tests": outcomes}
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """You are an expert {lang} engineer working in an existing repository.
|
||||
|
||||
Resolve the user's request by editing files with the tools provided.
|
||||
|
||||
How this session works:
|
||||
- You are working autonomously. There is no human watching; nobody will answer
|
||||
questions, approve a plan, or read progress reports.
|
||||
- Every reply you send must contain at least one tool call until the task is
|
||||
done. A reply that contains only text does nothing: no work happens and the
|
||||
turn is wasted. Do not stop to describe what you are about to do; do it.
|
||||
- The session ends only when you call `finish`. Call it once the change is
|
||||
made, and not before. Only the files as you leave them are evaluated.
|
||||
|
||||
Important constraints:
|
||||
- You cannot run the code, the tests, or any shell command. The only feedback
|
||||
available is the `lint` tool, which reports syntax and type problems.
|
||||
- The repository already has a test suite that will be run against your work
|
||||
after you finish, but you cannot see it or run it. Do not create test files.
|
||||
- Read enough of the code to understand it before editing. A change that only
|
||||
makes the symptom go away will usually fail the tests.
|
||||
- Make the smallest correct change. Do not reformat or refactor unrelated code.
|
||||
|
||||
Keep calling tools until the change is complete, then call `finish`."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgenticTask:
|
||||
task_id: str
|
||||
lang: str
|
||||
category: str
|
||||
difficulty: int
|
||||
instruction: str
|
||||
files: Dict[str, str]
|
||||
tests: Dict[str, str]
|
||||
gold: Dict[str, str] = field(default_factory=dict)
|
||||
fail_to_pass: List[str] = field(default_factory=list)
|
||||
pass_to_pass: List[str] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_record(cls, rec: Dict[str, Any]) -> "AgenticTask":
|
||||
def as_map(v):
|
||||
return v if isinstance(v, dict) else json.loads(v or "{}")
|
||||
def as_list(v):
|
||||
return v if isinstance(v, list) else json.loads(v or "[]")
|
||||
return cls(
|
||||
task_id=rec["task_id"], lang=rec["lang"], category=rec["category"],
|
||||
difficulty=int(rec.get("difficulty", 3)),
|
||||
instruction=rec["instruction"],
|
||||
files=as_map(rec["files"]), tests=as_map(rec["tests"]),
|
||||
gold=as_map(rec.get("gold")),
|
||||
fail_to_pass=as_list(rec.get("fail_to_pass")),
|
||||
pass_to_pass=as_list(rec.get("pass_to_pass")),
|
||||
)
|
||||
|
||||
@property
|
||||
def spec(self) -> LangSpec:
|
||||
return LANGS[self.lang]
|
||||
|
||||
|
||||
def materialise(files: Dict[str, str], dest: Path) -> Path:
|
||||
"""Write a file map out as a real directory tree."""
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
for rel, content in files.items():
|
||||
p = dest / rel
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(content)
|
||||
return dest
|
||||
|
||||
|
||||
def repo_overview(files: Dict[str, str]) -> str:
|
||||
"""A compact tree, so a run is not lost merely to not finding the files."""
|
||||
lines = []
|
||||
for rel in sorted(files):
|
||||
n = files[rel].count("\n") + 1
|
||||
lines.append(f" {rel} ({n} lines)")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def user_prompt(task: AgenticTask) -> str:
|
||||
return (f"Repository files:\n{repo_overview(task.files)}\n\n"
|
||||
f"Task:\n{task.instruction}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class EpisodeResult:
|
||||
stop_reason: str = "unknown"
|
||||
turns: int = 0
|
||||
tool_calls: int = 0
|
||||
tool_errors: int = 0
|
||||
edits: int = 0
|
||||
escape_attempts: int = 0
|
||||
finished: bool = False
|
||||
bad_tool_json: int = 0
|
||||
nudges: int = 0
|
||||
prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
peak_context: int = 0
|
||||
truncated_turns: int = 0
|
||||
per_tool: Dict[str, int] = field(default_factory=dict)
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
NUDGE = ("Your last reply contained no tool call, so nothing happened. There is "
|
||||
"no human in this session to respond to text. If your change is "
|
||||
"complete, call `finish`. Otherwise continue working by calling the "
|
||||
"tools; do not reply with text alone.")
|
||||
|
||||
|
||||
def run_episode(task: AgenticTask, toolbox: ToolBox, chat,
|
||||
max_turns: int = 50,
|
||||
context_limit: Optional[int] = None,
|
||||
max_nudges: int = 3) -> Tuple[EpisodeResult, List[Dict]]:
|
||||
"""Drive one multi-turn tool-calling episode.
|
||||
|
||||
`chat(messages, tools) -> response dict` is supplied by the caller so this
|
||||
module stays free of any HTTP concerns.
|
||||
"""
|
||||
tools = tool_schemas()
|
||||
messages: List[Dict[str, Any]] = [
|
||||
{"role": "system", "content": SYSTEM_PROMPT.format(lang=task.spec.name)},
|
||||
{"role": "user", "content": user_prompt(task)},
|
||||
]
|
||||
res = EpisodeResult()
|
||||
|
||||
for turn in range(max_turns):
|
||||
res.turns = turn + 1
|
||||
try:
|
||||
reply = chat(messages, tools)
|
||||
except Exception as e:
|
||||
res.stop_reason, res.error = "request_failed", f"{type(e).__name__}: {e}"
|
||||
break
|
||||
|
||||
usage = reply.get("usage") or {}
|
||||
pt = int(usage.get("prompt_tokens") or 0)
|
||||
ct = int(usage.get("completion_tokens") or 0)
|
||||
res.prompt_tokens += pt
|
||||
res.completion_tokens += ct
|
||||
res.peak_context = max(res.peak_context, pt + ct)
|
||||
|
||||
choices = reply.get("choices") or []
|
||||
if not choices:
|
||||
res.stop_reason, res.error = "no_choices", json.dumps(reply)[:300]
|
||||
break
|
||||
msg = choices[0].get("message") or {}
|
||||
if choices[0].get("finish_reason") == "length":
|
||||
res.truncated_turns += 1
|
||||
calls = msg.get("tool_calls") or []
|
||||
|
||||
reasoning = msg.get("reasoning_content")
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": msg.get("content") or "",
|
||||
**({"reasoning_content": reasoning} if reasoning else {}),
|
||||
**({"tool_calls": calls} if calls else {}),
|
||||
})
|
||||
|
||||
if not calls:
|
||||
if toolbox.finished:
|
||||
res.stop_reason = "finished"
|
||||
break
|
||||
if res.nudges >= max_nudges:
|
||||
res.stop_reason = "no_tool_calls"
|
||||
break
|
||||
res.nudges += 1
|
||||
messages.append({"role": "user", "content": NUDGE})
|
||||
continue
|
||||
|
||||
for call in calls:
|
||||
fn = call.get("function") or {}
|
||||
name = fn.get("name") or ""
|
||||
raw = fn.get("arguments")
|
||||
if isinstance(raw, dict):
|
||||
args = raw
|
||||
else:
|
||||
try:
|
||||
args = json.loads(raw or "{}")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
res.bad_tool_json += 1
|
||||
args = None
|
||||
output = (toolbox.dispatch(name, args) if args is not None else
|
||||
f"Error: arguments for {name!r} were not valid JSON.")
|
||||
messages.append({"role": "tool",
|
||||
"tool_call_id": call.get("id") or f"call_{res.tool_calls}",
|
||||
"content": output})
|
||||
|
||||
if toolbox.finished:
|
||||
res.stop_reason = "finished"
|
||||
break
|
||||
if context_limit and res.peak_context > context_limit:
|
||||
res.stop_reason = "context_limit"
|
||||
break
|
||||
else:
|
||||
res.stop_reason = "max_turns"
|
||||
|
||||
res.tool_calls = toolbox.calls
|
||||
res.tool_errors = toolbox.errors
|
||||
res.edits = toolbox.edits
|
||||
res.escape_attempts = toolbox.ws.escape_attempts
|
||||
res.finished = toolbox.finished
|
||||
res.per_tool = dict(toolbox.per_tool)
|
||||
return res, messages
|
||||
|
||||
|
||||
def run_tests_against(files: Dict[str, str], task: AgenticTask, runner: Runner,
|
||||
workroot: Path, label: str) -> Dict[str, str]:
|
||||
"""Materialise a tree, drop the hidden tests in, and run them.
|
||||
|
||||
The tests are only ever written into this throwaway copy, never into the
|
||||
tree the agent worked in, so there is no window in which they could have
|
||||
been read or edited.
|
||||
"""
|
||||
dest = workroot / f"grade-{label}"
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
materialise(files, dest)
|
||||
materialise(task.tests, dest)
|
||||
return runner.test(dest).get("tests", {})
|
||||
|
||||
|
||||
def score(task: AgenticTask, outcomes: Dict[str, str]) -> Dict[str, Any]:
|
||||
"""Resolved means every target test passes and nothing previously passing broke."""
|
||||
def passing(tid):
|
||||
return outcomes.get(tid) == "pass"
|
||||
|
||||
f2p_ok = [t for t in task.fail_to_pass if passing(t)]
|
||||
p2p_ok = [t for t in task.pass_to_pass if passing(t)]
|
||||
regressions = [t for t in task.pass_to_pass if not passing(t)]
|
||||
resolved = (len(f2p_ok) == len(task.fail_to_pass)
|
||||
and not regressions and bool(task.fail_to_pass))
|
||||
return {
|
||||
"resolved": resolved,
|
||||
"f2p_passed": len(f2p_ok), "f2p_total": len(task.fail_to_pass),
|
||||
"p2p_passed": len(p2p_ok), "p2p_total": len(task.pass_to_pass),
|
||||
"regressions": regressions[:10],
|
||||
"n_regressions": len(regressions),
|
||||
"partial": (len(f2p_ok) / len(task.fail_to_pass)) if task.fail_to_pass else 0.0,
|
||||
}
|
||||
|
||||
|
||||
def make_sandbox(lang_name: str, sandbox_cls, cache_root=None, base_python=None):
|
||||
"""A sandbox carrying only the toolchain for `lang_name`.
|
||||
|
||||
Keyed per language rather than per suite: running the Python tasks must
|
||||
never provision node, and vice versa.
|
||||
"""
|
||||
lang = LANGS[lang_name]
|
||||
return sandbox_cls(
|
||||
name=f"agentic-{lang_name}",
|
||||
packages=list(lang.packages),
|
||||
provision=list(lang.provision),
|
||||
env=dict(lang.env),
|
||||
address_space_limit=lang.address_space_limit,
|
||||
cache_root=cache_root,
|
||||
base_python=base_python,
|
||||
)
|
||||
|
||||
|
||||
def run_task(task: AgenticTask, chat, sandbox, workroot: Optional[Path] = None,
|
||||
max_turns: int = 50, context_limit: Optional[int] = None,
|
||||
test_timeout: float = 90.0) -> Dict[str, Any]:
|
||||
"""One full attempt: fresh workspace, agent episode, then hidden tests."""
|
||||
root = Path(tempfile.mkdtemp(prefix=f"agentic-{task.task_id}-",
|
||||
dir=str(workroot) if workroot else None))
|
||||
try:
|
||||
ws_root = root / "repo"
|
||||
materialise(task.files, ws_root)
|
||||
ws = Workspace(ws_root)
|
||||
runner = Runner(sandbox, task.spec, timeout=test_timeout)
|
||||
toolbox = ToolBox(ws, linter=lambda t: runner.lint(ws_root, t))
|
||||
|
||||
episode, messages = run_episode(task, toolbox, chat,
|
||||
max_turns=max_turns,
|
||||
context_limit=context_limit)
|
||||
final = ws.snapshot()
|
||||
changed = sorted(k for k in final
|
||||
if final[k] != task.files.get(k)) + \
|
||||
sorted(k for k in task.files if k not in final)
|
||||
|
||||
outcomes = run_tests_against(final, task, runner, root, "final")
|
||||
result = score(task, outcomes)
|
||||
result.update({
|
||||
"task_id": task.task_id, "lang": task.lang,
|
||||
"category": task.category, "difficulty": task.difficulty,
|
||||
"changed_files": changed,
|
||||
"episode": {k: v for k, v in vars(episode).items()},
|
||||
"messages": messages,
|
||||
})
|
||||
return result
|
||||
finally:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
|
||||
|
||||
TRANSCRIPT_CSS = """
|
||||
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #fff; color: #222; max-width: 1100px; }
|
||||
h1 { font-size: 16px; margin: 0 0 4px; }
|
||||
.meta { font-size: 13px; color: #555; font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; margin-bottom: 12px; white-space: pre-wrap; }
|
||||
.msg { border: 1px solid #e1e4e8; border-left-width: 4px; margin: 8px 0; padding: 6px 10px; background: #fafbfc; }
|
||||
.msg .role { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 4px; }
|
||||
.system { border-left-color: #888; }
|
||||
.user { border-left-color: #0969da; }
|
||||
.nudge { border-left-color: #9a6700; }
|
||||
.assistant { border-left-color: #1a7f37; background: #fff; }
|
||||
.tool { border-left-color: #8250df; }
|
||||
.tool.err { border-left-color: #cf222e; }
|
||||
pre { font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; font-size: 12px; background: #fff; border: 1px solid #e1e4e8; padding: 6px 8px; margin: 4px 0; white-space: pre-wrap; word-wrap: break-word; }
|
||||
.reasoning pre { color: #57606a; background: #f6f8fa; }
|
||||
.call { font-size: 12px; font-weight: 600; color: #8250df; margin-top: 6px; }
|
||||
details > summary { cursor: pointer; font-size: 12px; color: #555; }
|
||||
.empty { font-size: 12px; color: #888; font-style: italic; }
|
||||
"""
|
||||
|
||||
|
||||
def _pre(text: str) -> str:
|
||||
return f"<pre>{html.escape(text)}</pre>"
|
||||
|
||||
|
||||
def _render_call(call: Dict[str, Any]) -> str:
|
||||
fn = call.get("function") or {}
|
||||
raw = fn.get("arguments")
|
||||
try:
|
||||
args = raw if isinstance(raw, dict) else json.loads(raw or "{}")
|
||||
shown = json.dumps(args, indent=2, ensure_ascii=False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
shown = f"(invalid JSON)\n{raw}"
|
||||
return (f'<div class="call">→ {html.escape(fn.get("name") or "?")} '
|
||||
f'<span style="font-weight:400;color:#888">{html.escape(call.get("id") or "")}</span></div>'
|
||||
+ _pre(shown))
|
||||
|
||||
|
||||
def render_transcript_html(messages: List[Dict[str, Any]], title: str,
|
||||
meta: str = "") -> str:
|
||||
"""One page per episode: every turn, with reasoning, tool calls and results."""
|
||||
names = {}
|
||||
for m in messages:
|
||||
for c in m.get("tool_calls") or []:
|
||||
names[c.get("id")] = (c.get("function") or {}).get("name") or "?"
|
||||
|
||||
parts = []
|
||||
turn = 0
|
||||
for m in messages:
|
||||
role = m.get("role") or "?"
|
||||
content = m.get("content") or ""
|
||||
if role == "assistant":
|
||||
turn += 1
|
||||
body = ""
|
||||
if m.get("reasoning_content"):
|
||||
body += ('<details class="reasoning" open><summary>reasoning '
|
||||
f'({len(m["reasoning_content"])} chars)</summary>'
|
||||
+ _pre(m["reasoning_content"]) + "</details>")
|
||||
body += _pre(content) if content.strip() else ""
|
||||
body += "".join(_render_call(c) for c in m.get("tool_calls") or [])
|
||||
if not body:
|
||||
body = '<div class="empty">(empty reply)</div>'
|
||||
parts.append(f'<div class="msg assistant"><div class="role">assistant '
|
||||
f'· turn {turn}</div>{body}</div>')
|
||||
elif role == "tool":
|
||||
name = names.get(m.get("tool_call_id"), "?")
|
||||
err = " err" if content.startswith("Error") else ""
|
||||
parts.append(f'<div class="msg tool{err}"><div class="role">tool result '
|
||||
f'· {html.escape(name)}</div>{_pre(content)}</div>')
|
||||
elif role == "system":
|
||||
parts.append('<div class="msg system"><details><summary>system prompt'
|
||||
f'</summary>{_pre(content)}</details></div>')
|
||||
else:
|
||||
cls = "nudge" if content == NUDGE else role
|
||||
label = "user (nudge)" if content == NUDGE else role
|
||||
parts.append(f'<div class="msg {html.escape(cls)}"><div class="role">'
|
||||
f'{html.escape(label)}</div>{_pre(content)}</div>')
|
||||
|
||||
return (f'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n'
|
||||
f'<title>{html.escape(title)}</title>\n<style>{TRANSCRIPT_CSS}</style>\n'
|
||||
f'</head>\n<body>\n<h1>{html.escape(title)}</h1>\n'
|
||||
f'<div class="meta">{html.escape(meta)}</div>\n'
|
||||
+ "\n".join(parts) + "\n</body>\n</html>\n")
|
||||
|
||||
|
||||
def write_transcript(messages: List[Dict[str, Any]], dest: Path, title: str,
|
||||
meta: str = "") -> Path:
|
||||
"""Write `<dest>.json` (the raw message history) and `<dest>.html`."""
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
json_path = dest.with_name(dest.name + ".json")
|
||||
with open(json_path, "w") as f:
|
||||
json.dump(messages, f, indent=2, ensure_ascii=False)
|
||||
with open(dest.with_name(dest.name + ".html"), "w") as f:
|
||||
f.write(render_transcript_html(messages, title, meta))
|
||||
return json_path
|
||||
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Lightweight sandboxed execution for code-generation evals (HumanEval, ClassEval).
|
||||
|
||||
Replaces the usual Docker-per-suite setup with a cached venv plus whatever
|
||||
process isolation the host can give us without root:
|
||||
|
||||
tier 1 bubblewrap read-only root, tmpfs /tmp and $HOME, no network
|
||||
tier 2 unshare -rn no network (home still visible)
|
||||
tier 3 rlimits only last resort, still capped on cpu/memory/procs/files
|
||||
|
||||
Every tier also applies rlimits and runs the candidate program in a throwaway
|
||||
working directory as its own process group, so a runaway solution is killed
|
||||
along with anything it spawned.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import resource
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import venv
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
DEFAULT_CACHE_ROOT = Path(
|
||||
os.environ.get("LLAMA_EVAL_CACHE", Path.home() / ".cache" / "llama-eval")
|
||||
)
|
||||
|
||||
LIMIT_AS_BYTES = 4 * 1024 * 1024 * 1024
|
||||
LIMIT_CPU_SECONDS_SLACK = 5
|
||||
LIMIT_NPROC = 256
|
||||
LIMIT_FSIZE_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExecResult:
|
||||
passed: bool
|
||||
status: str
|
||||
stdout: str = ""
|
||||
stderr: str = ""
|
||||
returncode: Optional[int] = None
|
||||
duration: float = 0.0
|
||||
|
||||
def summary(self, max_chars: int = 400) -> str:
|
||||
"""Short human-readable reason, for grader logs."""
|
||||
if self.passed:
|
||||
return "ok"
|
||||
if self.status == "timeout":
|
||||
return "timeout"
|
||||
tail = (self.stderr or self.stdout).strip()
|
||||
if not tail:
|
||||
return f"{self.status} (rc={self.returncode})"
|
||||
lines = [ln for ln in tail.splitlines() if ln.strip()]
|
||||
reason = lines[-1] if lines else tail
|
||||
return reason[:max_chars]
|
||||
|
||||
|
||||
def resolve_python(spec: Optional[str]) -> Optional[str]:
|
||||
"""Turn '3.13' or a path into a usable interpreter, or None if unavailable.
|
||||
|
||||
Suites can need an older interpreter than the one running this tool, because
|
||||
a dependency has no wheel (or no working source build) for the newest
|
||||
CPython. Accepts an explicit path, a bare version, or an executable name.
|
||||
"""
|
||||
if not spec:
|
||||
return None
|
||||
|
||||
candidate = Path(spec).expanduser()
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return str(candidate)
|
||||
|
||||
uv = shutil.which("uv")
|
||||
if uv and re.fullmatch(r"\d+\.\d+(\.\d+)?", spec):
|
||||
try:
|
||||
r = subprocess.run([uv, "python", "find", spec],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
path = r.stdout.strip()
|
||||
if r.returncode == 0 and path and Path(path).exists():
|
||||
return path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return shutil.which(spec) or shutil.which(f"python{spec}")
|
||||
|
||||
|
||||
def _detect_isolation(probe_python: Optional[str] = None,
|
||||
ro_binds: Sequence[Path] = ()) -> str:
|
||||
"""Pick the strongest isolation tier this host supports, once.
|
||||
|
||||
The probe must run the *same* interpreter the suite will, with the same
|
||||
binds. Probing a bare sys.executable silently downgrades the tier whenever
|
||||
this tool is itself run from a venv under $HOME -- which is the usual case,
|
||||
since it needs `datasets`. bwrap tmpfs-mounts the home directory, the
|
||||
interpreter disappears, the probe fails, and the sandbox falls back to a
|
||||
tier that does not hide $HOME at all. Nothing about the results looks wrong
|
||||
when that happens, so it has to be got right here.
|
||||
"""
|
||||
py = probe_python or sys.executable
|
||||
probe = "import sys; sys.exit(0)"
|
||||
|
||||
if shutil.which("bwrap"):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
_bwrap_argv(Path.cwd(), [py, "-I", "-c", probe], ro_binds=ro_binds),
|
||||
capture_output=True, timeout=20,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
return "bwrap"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if shutil.which("unshare"):
|
||||
for tier, argv in (
|
||||
("unshare", ["unshare", "--user", "--map-current-user", "--net"]),
|
||||
("unshare-root", ["unshare", "-rn"]),
|
||||
):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[*argv, py, "-I", "-c", probe],
|
||||
capture_output=True, timeout=20,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
return tier
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return "rlimit"
|
||||
|
||||
|
||||
UNSHARE_ARGV = {
|
||||
"unshare": ["unshare", "--user", "--map-current-user", "--net"],
|
||||
"unshare-root": ["unshare", "-rn"],
|
||||
}
|
||||
|
||||
|
||||
def _under(path: Path, parent: str) -> bool:
|
||||
return bool(parent) and (str(path) == parent
|
||||
or str(path).startswith(parent.rstrip("/") + os.sep))
|
||||
|
||||
|
||||
def _bwrap_argv(workdir: Path, inner: Sequence[str],
|
||||
ro_binds: Sequence[Path] = ()) -> List[str]:
|
||||
home = os.path.expanduser("~")
|
||||
argv = [
|
||||
"bwrap",
|
||||
"--ro-bind", "/", "/",
|
||||
"--dev", "/dev",
|
||||
"--proc", "/proc",
|
||||
"--tmpfs", "/tmp",
|
||||
]
|
||||
if home and home != "/":
|
||||
argv += ["--tmpfs", home]
|
||||
for path in ro_binds:
|
||||
if _under(path, home):
|
||||
argv += ["--ro-bind", str(path), str(path)]
|
||||
argv += [
|
||||
"--bind", str(workdir), str(workdir),
|
||||
"--chdir", str(workdir),
|
||||
"--unshare-all",
|
||||
"--die-with-parent",
|
||||
"--new-session",
|
||||
"--",
|
||||
]
|
||||
return argv + list(inner)
|
||||
|
||||
|
||||
def _nproc_ceiling() -> int:
|
||||
"""A process cap that bounds a fork bomb without tripping on existing load.
|
||||
|
||||
RLIMIT_NPROC is enforced per real UID across the whole system, not per
|
||||
process tree, so it has to sit above whatever the user is already running.
|
||||
"""
|
||||
try:
|
||||
current = len(os.listdir("/proc"))
|
||||
except OSError:
|
||||
current = 512
|
||||
return max(current * 2, 1024)
|
||||
|
||||
|
||||
def _apply_rlimits(timeout: float, with_nproc: bool,
|
||||
as_bytes: Optional[int] = LIMIT_AS_BYTES):
|
||||
def _preexec():
|
||||
os.setsid()
|
||||
cpu = int(timeout) + LIMIT_CPU_SECONDS_SLACK
|
||||
limits = [
|
||||
(resource.RLIMIT_CPU, (cpu, cpu)),
|
||||
(resource.RLIMIT_FSIZE, (LIMIT_FSIZE_BYTES, LIMIT_FSIZE_BYTES)),
|
||||
(resource.RLIMIT_CORE, (0, 0)),
|
||||
]
|
||||
if as_bytes:
|
||||
limits.append((resource.RLIMIT_AS, (as_bytes, as_bytes)))
|
||||
if with_nproc:
|
||||
ceiling = _nproc_ceiling()
|
||||
limits.append((resource.RLIMIT_NPROC, (ceiling, ceiling)))
|
||||
for res, lim in limits:
|
||||
try:
|
||||
resource.setrlimit(res, lim)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
return _preexec
|
||||
|
||||
|
||||
class Sandbox:
|
||||
"""A cached venv plus an isolated runner for untrusted candidate programs."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
packages: Optional[Sequence[str]] = None,
|
||||
cache_root: Optional[Path] = None,
|
||||
base_python: Optional[str] = None,
|
||||
isolation: Optional[str] = None,
|
||||
provision: Optional[Sequence[str]] = None,
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
address_space_limit: Optional[int] = LIMIT_AS_BYTES,
|
||||
):
|
||||
self.name = name
|
||||
self.packages = sorted(set(packages or []))
|
||||
self.provision = list(provision or [])
|
||||
self.env = dict(env or {})
|
||||
# None drops the RLIMIT_AS cap entirely -- see _apply_rlimits
|
||||
self.address_space_limit = address_space_limit
|
||||
self.cache_root = Path(cache_root or DEFAULT_CACHE_ROOT)
|
||||
self.base_python = base_python or sys.executable
|
||||
self.venv_dir = self.cache_root / f"{name}-venv"
|
||||
self._python: Optional[Path] = None
|
||||
self._isolation = isolation
|
||||
self._base_prefix: List[Path] = []
|
||||
|
||||
@property
|
||||
def python(self) -> Path:
|
||||
if self._python is None:
|
||||
raise RuntimeError("Sandbox.ensure() must be called first")
|
||||
return self._python
|
||||
|
||||
@property
|
||||
def isolation(self) -> str:
|
||||
if self._isolation is None:
|
||||
if self._python:
|
||||
py = str(self._python)
|
||||
roots = [self.venv_dir, *(self._base_prefix or [])]
|
||||
else:
|
||||
py = sys.executable
|
||||
roots = self._detect_base_prefix(Path(py))
|
||||
self._isolation = _detect_isolation(py, roots)
|
||||
return self._isolation
|
||||
|
||||
def _stamp_path(self) -> Path:
|
||||
return self.venv_dir / ".llama-eval-stamp.json"
|
||||
|
||||
def _stamp_matches(self) -> bool:
|
||||
try:
|
||||
stamp = json.loads(self._stamp_path().read_text())
|
||||
except Exception:
|
||||
return False
|
||||
return (
|
||||
stamp.get("packages") == self.packages
|
||||
and stamp.get("py") == self._base_version()
|
||||
and stamp.get("provision") == self.provision
|
||||
)
|
||||
|
||||
def _resolved_env(self) -> Dict[str, str]:
|
||||
return {k: v.replace("{venv}", str(self.venv_dir)) for k, v in self.env.items()}
|
||||
|
||||
def _base_version(self) -> str:
|
||||
r = subprocess.run(
|
||||
[self.base_python, "-c",
|
||||
"import sys;print('.'.join(map(str,sys.version_info[:3])))"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
return r.stdout.strip()
|
||||
|
||||
def ensure(self, force: bool = False, quiet: bool = False) -> Path:
|
||||
"""Create/refresh the venv. Idempotent and cheap when already valid."""
|
||||
py = self.venv_dir / "bin" / "python"
|
||||
if force and self.venv_dir.exists():
|
||||
shutil.rmtree(self.venv_dir)
|
||||
|
||||
if py.exists() and self._stamp_matches():
|
||||
self._python = py
|
||||
self._base_prefix = self._detect_base_prefix(py)
|
||||
return py
|
||||
|
||||
if not quiet:
|
||||
pkgs = ", ".join(self.packages) if self.packages else "stdlib only"
|
||||
print(f"[sandbox] provisioning venv {self.venv_dir} ({pkgs})")
|
||||
|
||||
self.cache_root.mkdir(parents=True, exist_ok=True)
|
||||
if self.venv_dir.exists():
|
||||
shutil.rmtree(self.venv_dir)
|
||||
|
||||
uv = shutil.which("uv")
|
||||
if uv:
|
||||
subprocess.run(
|
||||
[uv, "venv", "--python", self.base_python, str(self.venv_dir)],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
elif Path(self.base_python).resolve() == Path(sys.executable).resolve():
|
||||
venv.EnvBuilder(with_pip=bool(self.packages), clear=True).create(self.venv_dir)
|
||||
else:
|
||||
subprocess.run(
|
||||
[self.base_python, "-m", "venv", str(self.venv_dir)],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
|
||||
if self.packages:
|
||||
if uv:
|
||||
cmd = [uv, "pip", "install", "--python", str(py), *self.packages]
|
||||
else:
|
||||
cmd = [str(py), "-m", "pip", "install", "--quiet",
|
||||
"--disable-pip-version-check", *self.packages]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"failed to install {len(self.packages)} package(s) into "
|
||||
f"{self.venv_dir}:\n{(r.stderr or r.stdout)[-3000:]}"
|
||||
)
|
||||
|
||||
for i, snippet in enumerate(self.provision, 1):
|
||||
if not quiet:
|
||||
print(f"[sandbox] provisioning step {i}/{len(self.provision)}")
|
||||
prov_env = dict(os.environ)
|
||||
prov_env["SANDBOX_VENV"] = str(self.venv_dir)
|
||||
prov_env.update(self._resolved_env())
|
||||
r = subprocess.run([str(py), "-c", snippet], env=prov_env,
|
||||
capture_output=True, text=True, timeout=1800)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"provisioning step {i} failed:\n{(r.stderr or r.stdout)[-3000:]}"
|
||||
)
|
||||
|
||||
self._stamp_path().write_text(json.dumps({
|
||||
"packages": self.packages,
|
||||
"py": self._base_version(),
|
||||
"provision": self.provision,
|
||||
}))
|
||||
self._python = py
|
||||
self._base_prefix = self._detect_base_prefix(py)
|
||||
if not quiet:
|
||||
print(f"[sandbox] ready ({self.isolation} isolation)")
|
||||
if self.isolation == "unshare-root":
|
||||
print("Warning: this host's unshare cannot keep the caller's uid, so "
|
||||
"candidate code runs as root inside the namespace. Tests that "
|
||||
"assert a permission denial will not see one. Install "
|
||||
"bubblewrap (bwrap) for correct results.")
|
||||
return py
|
||||
|
||||
@staticmethod
|
||||
def _detect_base_prefix(py: Path) -> List[Path]:
|
||||
"""Every directory that must stay visible for the venv to start.
|
||||
|
||||
A venv is only symlinks into its base interpreter, so the whole install
|
||||
tree has to survive the tmpfs that hides $HOME -- and binding just the
|
||||
resolved tree is not enough. Version-managed interpreters (uv, pyenv)
|
||||
are commonly reached through an unversioned alias symlink
|
||||
(cpython-3.13-... -> cpython-3.13.14-...), and startup resolves through
|
||||
the alias path, so both spellings need to be mounted. Walk the chain
|
||||
and keep every install root it passes through.
|
||||
"""
|
||||
roots: List[Path] = []
|
||||
|
||||
def add(path: Path):
|
||||
if path.exists() and path not in roots:
|
||||
roots.append(path)
|
||||
|
||||
cur, hops = py, 0
|
||||
while hops < 16:
|
||||
hops += 1
|
||||
add(cur.parent.parent)
|
||||
if cur.is_symlink():
|
||||
target = Path(os.readlink(cur))
|
||||
cur = target if target.is_absolute() else (cur.parent / target)
|
||||
else:
|
||||
break
|
||||
|
||||
try:
|
||||
r = subprocess.run([str(py), "-c", "import sys; print(sys.base_prefix)"],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
add(Path(r.stdout.strip()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return [p for p in roots if str(p) not in ("/", "/usr", "/usr/local")]
|
||||
|
||||
def _isolate(self, argv: Sequence[str], workdir: Path,
|
||||
ro_binds: Sequence[Path] = ()) -> List[str]:
|
||||
"""Wrap a command in the strongest isolation tier this host supports."""
|
||||
if self.isolation == "bwrap":
|
||||
return _bwrap_argv(
|
||||
workdir, argv,
|
||||
ro_binds=[self.venv_dir, *(self._base_prefix or []), *ro_binds],
|
||||
)
|
||||
if self.isolation in UNSHARE_ARGV:
|
||||
return [*UNSHARE_ARGV[self.isolation], *argv]
|
||||
return list(argv)
|
||||
|
||||
def _base_env(self, workdir: Path) -> Dict[str, str]:
|
||||
env = {
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": str(workdir),
|
||||
"TMPDIR": str(workdir),
|
||||
"LC_ALL": "C.UTF-8",
|
||||
"PYTHONHASHSEED": "0",
|
||||
"PYTHONDONTWRITEBYTECODE": "1",
|
||||
"OPENBLAS_NUM_THREADS": "1",
|
||||
"OMP_NUM_THREADS": "1",
|
||||
}
|
||||
env.update(self._resolved_env())
|
||||
return env
|
||||
|
||||
def run_argv(self, argv: Sequence[str], workdir: Path,
|
||||
timeout: float = 15.0, env: Optional[Dict[str, str]] = None,
|
||||
ro_binds: Sequence[Path] = ()) -> ExecResult:
|
||||
"""Run an arbitrary command in `workdir`, which stays writable.
|
||||
|
||||
Unlike run(), the caller owns the directory and it survives the call.
|
||||
That is what a multi-step agent needs: its edits have to persist across
|
||||
many tool invocations before anything is graded. Taking an argv rather
|
||||
than a source string is what lets a suite drive a linter or a test
|
||||
runner for a language other than Python.
|
||||
"""
|
||||
run_env = self._base_env(workdir)
|
||||
if env:
|
||||
run_env.update(env)
|
||||
|
||||
started = time.monotonic()
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
self._isolate(argv, workdir, ro_binds),
|
||||
cwd=str(workdir),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
errors="replace",
|
||||
env=run_env,
|
||||
preexec_fn=_apply_rlimits(timeout,
|
||||
with_nproc=self.isolation == "rlimit",
|
||||
as_bytes=self.address_space_limit),
|
||||
)
|
||||
except Exception as e:
|
||||
return ExecResult(False, "error", "", f"{type(e).__name__}: {e}")
|
||||
|
||||
try:
|
||||
out, err = proc.communicate(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
_kill_tree(proc)
|
||||
out, err = proc.communicate()
|
||||
return ExecResult(False, "timeout", out or "", err or "",
|
||||
None, time.monotonic() - started)
|
||||
|
||||
duration = time.monotonic() - started
|
||||
if proc.returncode == 0:
|
||||
return ExecResult(True, "ok", out, err, 0, duration)
|
||||
return ExecResult(False, "failed", out, err, proc.returncode, duration)
|
||||
|
||||
def run(self, code: str, timeout: float = 15.0,
|
||||
env: Optional[Dict[str, str]] = None) -> ExecResult:
|
||||
"""Run `code` to completion in the sandbox. Exit 0 == passed."""
|
||||
workdir = Path(tempfile.mkdtemp(prefix=f"{self.name}-", dir="/tmp"))
|
||||
try:
|
||||
prog = workdir / "candidate.py"
|
||||
prog.write_text(code)
|
||||
return self.run_argv([str(self.python), "-I", "-B", str(prog)],
|
||||
workdir, timeout=timeout, env=env)
|
||||
except Exception as e:
|
||||
return ExecResult(False, "error", "", f"{type(e).__name__}: {e}")
|
||||
finally:
|
||||
shutil.rmtree(workdir, ignore_errors=True)
|
||||
|
||||
|
||||
def _kill_tree(proc: subprocess.Popen):
|
||||
"""Kill the candidate and everything it spawned."""
|
||||
for sig in (signal.SIGKILL,):
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), sig)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
+1241
-34
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,6 @@ from typing import Dict, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import datasets
|
||||
|
||||
# Set cache directory for HuggingFace datasets
|
||||
cache_dir = Path.home() / ".cache" / "huggingface" / "datasets"
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -76,6 +74,8 @@ class AimeDataset:
|
||||
return question.get("problem", question.get("question", ""))
|
||||
|
||||
def _load_dataset(self):
|
||||
import datasets
|
||||
|
||||
if self.dataset_type == "aime":
|
||||
print(f"Loading AIME dataset (split: {self.split})...")
|
||||
cache_path = Path.home() / ".cache" / "huggingface" / "datasets" / "AI-MO___aimo-validation-aime" / "default" / "0.0.0"
|
||||
@@ -164,6 +164,58 @@ class AimeDataset:
|
||||
return str(normalized) if normalized is not None else answer
|
||||
return str(answer)
|
||||
|
||||
class CodeDataset:
|
||||
"""Stand-in for a code model: replays canonical solutions as chat replies.
|
||||
|
||||
Handles both code suites: HumanEval keys off the function stub, ClassEval
|
||||
off the class skeleton.
|
||||
"""
|
||||
|
||||
DEFAULT_SOURCE = {
|
||||
"humaneval": "openai/openai_humaneval",
|
||||
"classeval": "FudanSELab/ClassEval",
|
||||
}
|
||||
|
||||
def __init__(self, dataset_type: str, source: Optional[str] = None):
|
||||
self.dataset_type = dataset_type
|
||||
self.source = source or self.DEFAULT_SOURCE[dataset_type]
|
||||
self.questions: List[Dict] = []
|
||||
self._load_dataset()
|
||||
|
||||
def _load_dataset(self):
|
||||
print(f"Loading {self.dataset_type} dataset (source: {self.source})...")
|
||||
local = Path(self.source)
|
||||
if local.exists():
|
||||
text = local.read_text()
|
||||
if local.suffix == ".json":
|
||||
rows = json.loads(text)
|
||||
else:
|
||||
rows = [json.loads(ln) for ln in text.splitlines() if ln.strip()]
|
||||
else:
|
||||
import datasets
|
||||
rows = [dict(r) for r in datasets.load_dataset(self.source, split="test")]
|
||||
self.questions = rows
|
||||
print(f"{self.dataset_type} dataset loaded: {len(self.questions)} questions")
|
||||
|
||||
def _stub_field(self) -> str:
|
||||
return "skeleton" if self.dataset_type == "classeval" else "prompt"
|
||||
|
||||
def _get_question_text(self, question: Dict) -> str:
|
||||
return question[self._stub_field()]
|
||||
|
||||
def find_question(self, request_text: str) -> Optional[Dict]:
|
||||
for question in self.questions:
|
||||
stub = question[self._stub_field()].rstrip()
|
||||
if stub and stub in request_text:
|
||||
return question
|
||||
return None
|
||||
|
||||
def get_answer(self, question: Dict) -> str:
|
||||
if self.dataset_type == "classeval":
|
||||
return question["solution_code"]
|
||||
return question["prompt"] + question["canonical_solution"]
|
||||
|
||||
|
||||
class Simulator:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -171,12 +223,17 @@ class Simulator:
|
||||
host: str = "localhost",
|
||||
success_rate: float = 0.8,
|
||||
dataset_split: str = "train",
|
||||
dataset_type: str = "aime"
|
||||
dataset_type: str = "aime",
|
||||
dataset_source: Optional[str] = None
|
||||
):
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.success_rate = success_rate
|
||||
self.dataset = AimeDataset(dataset_split, dataset_type)
|
||||
self.dataset_type = dataset_type
|
||||
if dataset_type in ("humaneval", "classeval"):
|
||||
self.dataset = CodeDataset(dataset_type, dataset_source)
|
||||
else:
|
||||
self.dataset = AimeDataset(dataset_split, dataset_type)
|
||||
self.eval_state = EvalState(
|
||||
id=dataset_type,
|
||||
tasks=[dataset_type],
|
||||
@@ -196,6 +253,12 @@ class Simulator:
|
||||
else:
|
||||
response_text = self._generate_wrong_answer(question)
|
||||
|
||||
if self.dataset_type in ("humaneval", "classeval"):
|
||||
response_text = (
|
||||
f"Here is the implementation:\n\n```python\n{response_text}\n```\n\n"
|
||||
"Let me know if you want it optimised further."
|
||||
)
|
||||
|
||||
comp_tokens = random.randint(10000, 60000)
|
||||
tps_gen = random.uniform(90.0, 110.0)
|
||||
t_gen_ms = comp_tokens / tps_gen * 1000
|
||||
@@ -229,6 +292,12 @@ class Simulator:
|
||||
def _generate_wrong_answer(self, question: Dict) -> str:
|
||||
expected_answer = self.dataset.get_answer(question)
|
||||
|
||||
if self.dataset_type == "classeval":
|
||||
return re.sub(r"(\n\s+def [^\n]+:\n)", r"\1 return None\n",
|
||||
question["skeleton"])
|
||||
if self.dataset_type == "humaneval":
|
||||
return question["prompt"] + " return None\n"
|
||||
|
||||
if expected_answer.isdigit():
|
||||
wrong_answer = str(int(expected_answer) + 1)
|
||||
else:
|
||||
@@ -335,7 +404,7 @@ def main():
|
||||
"--dataset",
|
||||
type=str,
|
||||
default="aime",
|
||||
choices=["aime", "aime2025"],
|
||||
choices=["aime", "aime2025", "humaneval", "classeval"],
|
||||
help="Dataset type (default: aime)"
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -344,6 +413,12 @@ def main():
|
||||
default="train",
|
||||
help="AIME dataset split to use (default: train)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dataset-source",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Override the dataset location (local .jsonl path or HuggingFace repo id)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -353,7 +428,8 @@ def main():
|
||||
host=args.host,
|
||||
success_rate=args.success_rate,
|
||||
dataset_split=args.dataset_split,
|
||||
dataset_type=args.dataset
|
||||
dataset_type=args.dataset,
|
||||
dataset_source=args.dataset_source
|
||||
)
|
||||
|
||||
server = HTTPServer((args.host, args.port), RequestHandler)
|
||||
|
||||
+16
-3
@@ -4,15 +4,18 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
print_usage() {
|
||||
echo "Usage: ./build.sh [fp32|fp16] [--help]"
|
||||
echo "Usage: ./build.sh [fp32|fp16] [ssl|nossl] [--help]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " fp32 Build with FP32 precision (default)"
|
||||
echo " fp16 Build with FP16 precision (faster for long-prompt inference)"
|
||||
echo " ssl Build with OpenSSL (default)"
|
||||
echo " nossl Build without OpenSSL"
|
||||
echo " --help Print this help message"
|
||||
}
|
||||
|
||||
PRECISION=fp32
|
||||
SSL=ON
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
@@ -23,6 +26,12 @@ for arg in "$@"; do
|
||||
fp32|fp16)
|
||||
PRECISION="$arg"
|
||||
;;
|
||||
ssl)
|
||||
SSL=ON
|
||||
;;
|
||||
nossl)
|
||||
SSL=OFF
|
||||
;;
|
||||
*)
|
||||
echo "Error: unknown option '$arg'"
|
||||
print_usage
|
||||
@@ -37,10 +46,13 @@ source /opt/intel/oneapi/setvars.sh
|
||||
|
||||
if [ "$PRECISION" = "fp16" ]; then
|
||||
#for FP16
|
||||
cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL_F16=ON -DLLAMA_OPENSSL=ON # faster for long-prompt inference
|
||||
echo "cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL_F16=ON -DLLAMA_OPENSSL=$SSL # faster for long-prompt inference
|
||||
"
|
||||
cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL_F16=ON -DLLAMA_OPENSSL="$SSL" # faster for long-prompt inference
|
||||
else
|
||||
#for FP32
|
||||
cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DLLAMA_OPENSSL=ON
|
||||
echo "cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DLLAMA_OPENSSL=$SSL"
|
||||
cmake .. -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DLLAMA_OPENSSL="$SSL"
|
||||
fi
|
||||
|
||||
#build example/main
|
||||
@@ -50,4 +62,5 @@ fi
|
||||
#cmake --build . --config Release --target llama-bench
|
||||
|
||||
#build all binary
|
||||
echo "cmake --build . --config Release -j$((($(nproc)+1)/2)) -v"
|
||||
cmake --build . --config Release -j$((($(nproc)+1)/2)) -v
|
||||
|
||||
@@ -3,23 +3,62 @@
|
||||
:: Copyright (C) 2024 Intel Corporation
|
||||
:: SPDX-License-Identifier: MIT
|
||||
|
||||
IF /I "%1"=="--help" (
|
||||
echo Usage: win-build-sycl.bat [fp32^|fp16] [--help]
|
||||
echo.
|
||||
echo Options:
|
||||
echo fp32 Build with FP32 precision ^(default^)
|
||||
echo fp16 Build with FP16 precision ^(faster for long-prompt inference^)
|
||||
echo --help Print this help message
|
||||
exit /B 0
|
||||
)
|
||||
SET "PRECISION=fp32"
|
||||
SET "SSL=ON"
|
||||
SET "OPENSSL_OPTION="
|
||||
|
||||
SET PRECISION=%1
|
||||
IF "%PRECISION%"=="" SET PRECISION=fp32
|
||||
IF /I NOT "%PRECISION%"=="fp32" IF /I NOT "%PRECISION%"=="fp16" (
|
||||
echo Error: invalid value '%PRECISION%'. Use 'fp32' or 'fp16'.
|
||||
echo Usage: win-build-sycl.bat [fp32^|fp16] [--help]
|
||||
exit /B 1
|
||||
)
|
||||
:PARSE_ARGS
|
||||
IF "%~1"=="" GOTO ARGS_DONE
|
||||
IF /I "%~1"=="--help" GOTO HELP
|
||||
IF /I "%~1"=="fp32" GOTO ARG_FP32
|
||||
IF /I "%~1"=="fp16" GOTO ARG_FP16
|
||||
IF /I "%~1"=="ssl" GOTO ARG_SSL
|
||||
IF /I "%~1"=="nossl" GOTO ARG_NOSSL
|
||||
GOTO INVALID_ARG
|
||||
|
||||
:ARG_FP32
|
||||
SET "PRECISION=fp32"
|
||||
GOTO NEXT_ARG
|
||||
|
||||
:ARG_FP16
|
||||
SET "PRECISION=fp16"
|
||||
GOTO NEXT_ARG
|
||||
|
||||
:ARG_SSL
|
||||
SET "SSL=ON"
|
||||
SET "OPENSSL_OPTION="
|
||||
GOTO NEXT_ARG
|
||||
|
||||
:ARG_NOSSL
|
||||
SET "SSL=OFF"
|
||||
SET "OPENSSL_OPTION=-DLLAMA_OPENSSL=OFF"
|
||||
GOTO NEXT_ARG
|
||||
|
||||
:NEXT_ARG
|
||||
SHIFT
|
||||
GOTO PARSE_ARGS
|
||||
|
||||
:HELP
|
||||
SET "ARG_ERROR=0"
|
||||
GOTO PRINT_USAGE
|
||||
|
||||
:INVALID_ARG
|
||||
SET "ARG_ERROR=1"
|
||||
echo Error: unknown option '%~1'
|
||||
|
||||
:PRINT_USAGE
|
||||
echo Usage: win-build-sycl.bat [fp32^|fp16] [ssl^|nossl] [--help]
|
||||
echo.
|
||||
echo Options:
|
||||
echo fp32 Build with FP32 precision ^(default^)
|
||||
echo fp16 Build with FP16 precision ^(faster for long-prompt inference^)
|
||||
echo ssl Build with BoringSSL ^(default^)
|
||||
echo nossl Build without BoringSSL
|
||||
echo --help Print this help message
|
||||
IF "%ARG_ERROR%"=="1" exit /B 1
|
||||
exit /B 0
|
||||
|
||||
:ARGS_DONE
|
||||
|
||||
IF not exist build (mkdir build)
|
||||
cd build
|
||||
@@ -31,10 +70,10 @@ if %errorlevel% neq 0 goto ERROR
|
||||
IF /I "%PRECISION%"=="fp16" (
|
||||
:: for FP16
|
||||
:: faster for long-prompt inference
|
||||
cmake -G "MinGW Makefiles" .. -DLLAMA_OPENSSL=OFF -DGGML_SYCL=ON -DCMAKE_CXX_COMPILER=icx -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DGGML_SYCL_F16=ON
|
||||
cmake -G "MinGW Makefiles" .. -DLLAMA_BUILD_BORINGSSL=%SSL% %OPENSSL_OPTION% -DGGML_SYCL=ON -DCMAKE_CXX_COMPILER=icx -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DGGML_SYCL_F16=ON
|
||||
) ELSE (
|
||||
:: for FP32
|
||||
cmake -G "Ninja" .. -DLLAMA_OPENSSL=OFF -DGGML_SYCL=ON -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=icx -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
cmake -G "Ninja" .. -DLLAMA_BUILD_BORINGSSL=%SSL% %OPENSSL_OPTION% -DGGML_SYCL=ON -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=icx -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
|
||||
)
|
||||
if %errorlevel% neq 0 goto ERROR
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ project("ggml" C CXX ASM)
|
||||
|
||||
### GGML Version
|
||||
set(GGML_VERSION_MAJOR 0)
|
||||
set(GGML_VERSION_MINOR 26)
|
||||
set(GGML_VERSION_MINOR 27)
|
||||
set(GGML_VERSION_PATCH 0)
|
||||
set(GGML_VERSION_BASE "${GGML_VERSION_MAJOR}.${GGML_VERSION_MINOR}.${GGML_VERSION_PATCH}")
|
||||
|
||||
|
||||
@@ -2,10 +2,16 @@ function(ggml_get_flags CCID CCVER)
|
||||
set(C_FLAGS "")
|
||||
set(CXX_FLAGS "")
|
||||
|
||||
if (CCID MATCHES "Clang")
|
||||
if (CCID MATCHES "Clang" OR CCID MATCHES "IntelLLVM")
|
||||
set(C_FLAGS -Wunreachable-code-break -Wunreachable-code-return)
|
||||
set(CXX_FLAGS -Wunreachable-code-break -Wunreachable-code-return -Wmissing-prototypes -Wextra-semi)
|
||||
|
||||
if (CCID STREQUAL "IntelLLVM")
|
||||
# for Intel DPC++ and LLVM SYCL, using debug does not disable optimization
|
||||
list(APPEND C_FLAGS -Rno-debug-disables-optimization)
|
||||
list(APPEND CXX_FLAGS -Rno-debug-disables-optimization)
|
||||
endif()
|
||||
|
||||
if (
|
||||
(CCID STREQUAL "Clang" AND CCVER VERSION_GREATER_EQUAL 3.8.0) OR
|
||||
(CCID STREQUAL "AppleClang" AND CCVER VERSION_GREATER_EQUAL 7.3.0)
|
||||
|
||||
@@ -2654,6 +2654,7 @@ extern "C" {
|
||||
// the output packs the attention scores [S_v, H_v, n_tokens, n_seqs] followed by K state
|
||||
// snapshots, most-recent first (slot 0 = final state, slot s = state s tokens back). K == 1
|
||||
// keeps only the final state; when n_tokens < K only slots 0..n_tokens-1 are written.
|
||||
// when n_tokens > K only slot 0 is guaranteed; backends may leave slots 1..K-1 unwritten.
|
||||
GGML_API struct ggml_tensor * ggml_gated_delta_net(
|
||||
struct ggml_context * ctx,
|
||||
struct ggml_tensor * q,
|
||||
|
||||
@@ -2226,6 +2226,7 @@ bool ggml_op_alloc_size_may_expand(enum ggml_op op) {
|
||||
case GGML_OP_CUMSUM:
|
||||
case GGML_OP_ARGSORT:
|
||||
case GGML_OP_TOP_K:
|
||||
case GGML_OP_ARGMAX:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -2455,9 +2455,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Q4 && Q8 per group is not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// only support contiguous for quantized types.
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2472,9 +2473,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Q4 && Q8 per group is not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// only support contiguous for quantized types.
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2641,7 +2643,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Ger is not supported on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
switch (op->src[0]->type) {
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_F32:
|
||||
@@ -2649,6 +2651,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
case GGML_OP_CONV_TRANSPOSE_1D:
|
||||
return true;
|
||||
@@ -2667,7 +2670,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// FA not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// derived from [ggml-cuda.cu]
|
||||
if (op->src[1]->type != GGML_TYPE_F16 || op->src[2]->type != GGML_TYPE_F16) {
|
||||
return false;
|
||||
@@ -2693,6 +2696,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
case GGML_OP_SSM_CONV:
|
||||
return true;
|
||||
|
||||
@@ -310,7 +310,7 @@ void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y[ib].qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_1/2, y[ib].qs);
|
||||
|
||||
const int32x4_t v_xy_ = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy_ = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xy = vec_float(v_xy_);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d));
|
||||
@@ -379,8 +379,8 @@ void ggml_vec_dot_mxfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8x16_t v_y1l = vec_xl(0, y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_0/2, y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0l, v_y0l), v_x0h, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1l, v_y1l), v_x1h, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0l, v_y0l), v_x0h, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1l, v_y1l), v_x1h, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -407,7 +407,7 @@ void ggml_vec_dot_mxfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8x16_t v_yl = vec_xl(0, y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_E8M0_TO_FP32_HALF(x0->e) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -502,8 +502,8 @@ void ggml_vec_dot_q5_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_y1l = vec_xl(0, (const int8_t *)y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_0/2, (const int8_t *)y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -548,7 +548,7 @@ void ggml_vec_dot_q5_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0, (const int8_t *)y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, (const int8_t *)y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -653,8 +653,8 @@ void ggml_vec_dot_q5_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_y1l = vec_xl(0 , y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_1/2, y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -701,7 +701,7 @@ void ggml_vec_dot_q5_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_1/2, y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -751,7 +751,7 @@ void ggml_vec_dot_q8_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y[ib].qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y[ib].qs);
|
||||
|
||||
const int32x4_t v_xy_ = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy_ = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xy = vec_float(v_xy_);
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d));
|
||||
|
||||
@@ -855,10 +855,10 @@ void ggml_vec_dot_q3_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q3bytes[2] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[0], 2), v_3m), (int8x16_t)q3h[2]);
|
||||
q3bytes[3] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[1], 2), v_3m), (int8x16_t)q3h[3]);
|
||||
|
||||
isum0 = ggml_vec_dot(v_z, q3bytes[0], q8bytes[0]);
|
||||
isum1 = ggml_vec_dot(v_z, q3bytes[1], q8bytes[1]);
|
||||
isum2 = ggml_vec_dot(v_z, q3bytes[2], q8bytes[2]);
|
||||
isum3 = ggml_vec_dot(v_z, q3bytes[3], q8bytes[3]);
|
||||
isum0 = ggml_vec_dot_s32(v_z, q3bytes[0], q8bytes[0]);
|
||||
isum1 = ggml_vec_dot_s32(v_z, q3bytes[1], q8bytes[1]);
|
||||
isum2 = ggml_vec_dot_s32(v_z, q3bytes[2], q8bytes[2]);
|
||||
isum3 = ggml_vec_dot_s32(v_z, q3bytes[3], q8bytes[3]);
|
||||
|
||||
isum += (isum0[0] + isum0[1] + isum0[2] + isum0[3]) * scale[0];
|
||||
isum += (isum1[0] + isum1[1] + isum1[2] + isum1[3]) * scale[1];
|
||||
@@ -877,10 +877,10 @@ void ggml_vec_dot_q3_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q3bytes[2] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[0], 6), v_3m), (int8x16_t)q3h[2]);
|
||||
q3bytes[3] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[1], 6), v_3m), (int8x16_t)q3h[3]);
|
||||
|
||||
isum0 = ggml_vec_dot(v_z, q3bytes[0], q8bytes[4]);
|
||||
isum1 = ggml_vec_dot(v_z, q3bytes[1], q8bytes[5]);
|
||||
isum2 = ggml_vec_dot(v_z, q3bytes[2], q8bytes[6]);
|
||||
isum3 = ggml_vec_dot(v_z, q3bytes[3], q8bytes[7]);
|
||||
isum0 = ggml_vec_dot_s32(v_z, q3bytes[0], q8bytes[4]);
|
||||
isum1 = ggml_vec_dot_s32(v_z, q3bytes[1], q8bytes[5]);
|
||||
isum2 = ggml_vec_dot_s32(v_z, q3bytes[2], q8bytes[6]);
|
||||
isum3 = ggml_vec_dot_s32(v_z, q3bytes[3], q8bytes[7]);
|
||||
|
||||
isum += vec_hsum_i32x4(isum0) * scale[0];
|
||||
isum += vec_hsum_i32x4(isum1) * scale[1];
|
||||
@@ -981,7 +981,7 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
v_xl[0] = (int8x16_t)vec_and(v_x[0], v_lm);
|
||||
v_xl[1] = (int8x16_t)vec_and(v_x[1], v_lm);
|
||||
|
||||
const int32x4_t p1 = ggml_vec_dot(ggml_vec_dot(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
const int32x4_t p1 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
sumi1 += vec_hsum_i32x4(p1) * scales[2*j+0];
|
||||
|
||||
v_y[0] = vec_xl(0 , y0);
|
||||
@@ -991,7 +991,7 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
v_xl[0] = (int8x16_t)vec_sr(v_x[0], 4);
|
||||
v_xl[1] = (int8x16_t)vec_sr(v_x[1], 4);
|
||||
|
||||
const int32x4_t p2 = ggml_vec_dot(ggml_vec_dot(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
const int32x4_t p2 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
sumi2 += vec_hsum_i32x4(p2) * scales[2*j+1];
|
||||
}
|
||||
|
||||
@@ -1107,8 +1107,8 @@ void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q5b[2] = (int8x16_t)vec_or(vec_sr(v_xl[0], 4), q5h[2]);
|
||||
q5b[3] = (int8x16_t)vec_or(vec_sr(v_xl[1], 4), q5h[3]);
|
||||
|
||||
int32x4_t sumi0 = ggml_vec_dot(ggml_vec_dot(v_z, q5b[0], v_y[0]), q5b[1], v_y[1]);
|
||||
int32x4_t sumi1 = ggml_vec_dot(ggml_vec_dot(v_z, q5b[2], v_y[2]), q5b[3], v_y[3]);
|
||||
int32x4_t sumi0 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, q5b[0], v_y[0]), q5b[1], v_y[1]);
|
||||
int32x4_t sumi1 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, q5b[2], v_y[2]), q5b[3], v_y[3]);
|
||||
|
||||
sumi += vec_hsum_i32x4(sumi0) * *scales++;
|
||||
sumi += vec_hsum_i32x4(sumi1) * *scales++;
|
||||
@@ -1217,10 +1217,10 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q6b[2] = (int8x16_t)(vec_or(vec_and(v_xl[2], v_lm), q6h[2]));
|
||||
q6b[3] = (int8x16_t)(vec_or(vec_and(v_xl[3], v_lm), q6h[3]));
|
||||
|
||||
int32x4_t summs0 = ggml_vec_dot(v_z, q6b[0], v_y[0]);
|
||||
int32x4_t summs1 = ggml_vec_dot(v_z, q6b[1], v_y[1]);
|
||||
int32x4_t summs2 = ggml_vec_dot(v_z, q6b[2], v_y[2]);
|
||||
int32x4_t summs3 = ggml_vec_dot(v_z, q6b[3], v_y[3]);
|
||||
int32x4_t summs0 = ggml_vec_dot_s32(v_z, q6b[0], v_y[0]);
|
||||
int32x4_t summs1 = ggml_vec_dot_s32(v_z, q6b[1], v_y[1]);
|
||||
int32x4_t summs2 = ggml_vec_dot_s32(v_z, q6b[2], v_y[2]);
|
||||
int32x4_t summs3 = ggml_vec_dot_s32(v_z, q6b[3], v_y[3]);
|
||||
|
||||
isum += vec_hsum_i32x4(summs0) * scale[0] +
|
||||
vec_hsum_i32x4(summs1) * scale[1] +
|
||||
@@ -1251,10 +1251,10 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q6b[2] = (int8x16_t)(vec_or(vec_sr(v_xl[2], 4), q6h[2]));
|
||||
q6b[3] = (int8x16_t)(vec_or(vec_sr(v_xl[3], 4), q6h[3]));
|
||||
|
||||
summs0 = ggml_vec_dot(v_z, q6b[0], v_y[0]);
|
||||
summs1 = ggml_vec_dot(v_z, q6b[1], v_y[1]);
|
||||
summs2 = ggml_vec_dot(v_z, q6b[2], v_y[2]);
|
||||
summs3 = ggml_vec_dot(v_z, q6b[3], v_y[3]);
|
||||
summs0 = ggml_vec_dot_s32(v_z, q6b[0], v_y[0]);
|
||||
summs1 = ggml_vec_dot_s32(v_z, q6b[1], v_y[1]);
|
||||
summs2 = ggml_vec_dot_s32(v_z, q6b[2], v_y[2]);
|
||||
summs3 = ggml_vec_dot_s32(v_z, q6b[3], v_y[3]);
|
||||
|
||||
isum += vec_hsum_i32x4(summs0) * scale[0] +
|
||||
vec_hsum_i32x4(summs1) * scale[1] +
|
||||
@@ -1367,8 +1367,8 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
// q2u2 = vec_mul(q2u2, q2s2);
|
||||
// q2u3 = vec_mul(q2u3, q2s3);
|
||||
|
||||
// const int32x4_t p1 = ggml_vec_dot(ggml_vec_dot(vec_splat_s32(0), q2u0, q8b0), q2u1, q8b1);
|
||||
// const int32x4_t p2 = ggml_vec_dot(ggml_vec_dot(vec_splat_s32(0), q2u2, q8b2), q2u3, q8b3);
|
||||
// const int32x4_t p1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splat_s32(0), q2u0, q8b0), q2u1, q8b1);
|
||||
// const int32x4_t p2 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splat_s32(0), q2u2, q8b2), q2u3, q8b3);
|
||||
|
||||
// sumf1 += (p1[0] + p1[1] + p1[2] + p1[3]) * (0.5f + (aux32[1] >> 28));
|
||||
// sumf2 += (p2[0] + p2[1] + p2[2] + p2[3]) * (0.5f + (aux32[3] >> 28));
|
||||
@@ -1445,7 +1445,7 @@ void ggml_vec_dot_iq4_nl_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const v
|
||||
|
||||
const int8x16_t v_yl = vec_xl(0 , y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y0->qs);
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
|
||||
sumf += GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d) * vec_hsum_i32x4(v_xy);
|
||||
}
|
||||
@@ -1508,8 +1508,8 @@ void ggml_vec_dot_iq4_xs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const v
|
||||
const int8x16_t v_y3 = vec_xl(48, q8);
|
||||
q8 += 64;
|
||||
|
||||
int32x4_t vsumi0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0l, v_y0), v_x0h, v_y1);
|
||||
int32x4_t vsumi1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1l, v_y2), v_x1h, v_y3);
|
||||
int32x4_t vsumi0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0l, v_y0), v_x0h, v_y1);
|
||||
int32x4_t vsumi1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1l, v_y2), v_x1h, v_y3);
|
||||
|
||||
int ls1 = ((x[ibl].scales_l[ib] & 0xF) | ((h << 4) & 0x30)) - 32;
|
||||
int ls2 = ((x[ibl].scales_l[ib] >> 4) | ((h << 2) & 0x30)) - 32;
|
||||
|
||||
@@ -70,23 +70,6 @@ void ggml_quantize_mat_q8_0_4x4(const float * GGML_RESTRICT x, void * GGML_RESTR
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__VXE__) || defined(__VXE2__)
|
||||
static inline int16x8_t vxe_dot_acc(const int8x16_t v_x, const int8x16_t v_y, const int16x8_t v_acc) {
|
||||
return vec_meadd(v_x, v_y, vec_moadd(v_x, v_y, v_acc));
|
||||
}
|
||||
|
||||
static inline int8x16_t vxe_splat_granule(const int8_t * qs) {
|
||||
uint32_t g;
|
||||
memcpy(&g, qs, sizeof(g));
|
||||
return (int8x16_t)vec_splats(g);
|
||||
}
|
||||
|
||||
static inline int32x4_t vxe_fold(const int16x8_t v_sumi) {
|
||||
const int16x8_t v_ones = vec_splats((int16_t)1);
|
||||
return vec_add(vec_mule(v_sumi, v_ones), vec_mulo(v_sumi, v_ones));
|
||||
}
|
||||
#endif
|
||||
|
||||
void ggml_gemv_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, const void * GGML_RESTRICT vy, int nr, int nc) {
|
||||
const int qk = QK8_0;
|
||||
const int nb = n / qk;
|
||||
@@ -131,21 +114,21 @@ void ggml_gemv_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
|
||||
int16x8_t v_sumi = vec_splats((int16_t)0);
|
||||
|
||||
v_sumi = vxe_dot_acc(v_x0l, vxe_splat_granule(y_lo + 0), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x1l, vxe_splat_granule(y_lo + 4), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x2l, vxe_splat_granule(y_lo + 8), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x3l, vxe_splat_granule(y_lo + 12), v_sumi);
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x0l, ggml_vec_xl_s8x4_splat(y_lo + 0));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x1l, ggml_vec_xl_s8x4_splat(y_lo + 4));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x2l, ggml_vec_xl_s8x4_splat(y_lo + 8));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x3l, ggml_vec_xl_s8x4_splat(y_lo + 12));
|
||||
|
||||
v_sumi = vxe_dot_acc(v_x0h, vxe_splat_granule(y_hi + 0), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x1h, vxe_splat_granule(y_hi + 4), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x2h, vxe_splat_granule(y_hi + 8), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x3h, vxe_splat_granule(y_hi + 12), v_sumi);
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x0h, ggml_vec_xl_s8x4_splat(y_hi + 0));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x1h, ggml_vec_xl_s8x4_splat(y_hi + 4));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x2h, ggml_vec_xl_s8x4_splat(y_hi + 8));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x3h, ggml_vec_xl_s8x4_splat(y_hi + 12));
|
||||
|
||||
const float32x4_t v_yd = vec_splats(GGML_CPU_FP16_TO_FP32(a_ptr[l].d));
|
||||
const float32x4_t v_xd = __lzs_f16cx4_load(b_ptr[l].d);
|
||||
const float32x4_t v_xd = ggml_vec_xl_f16x4_finite(b_ptr[l].d);
|
||||
const float32x4_t v_d = vec_mul(v_yd, v_xd);
|
||||
|
||||
v_sumf = vec_madd(vec_float(vxe_fold(v_sumi)), v_d, v_sumf);
|
||||
v_sumf = vec_madd(vec_float(vec_paddl_s16(v_sumi)), v_d, v_sumf);
|
||||
}
|
||||
|
||||
vec_xst(v_sumf, 0, res_ptr + x * ncols_interleaved);
|
||||
@@ -192,24 +175,24 @@ void ggml_gemm_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8_t * y_lo = a_ptr[l].qs + 16 * k;
|
||||
const int8_t * y_hi = y_lo + qk / 2 * 4;
|
||||
|
||||
v_sumi0 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 0), v_sumi0);
|
||||
v_sumi1 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 4), v_sumi1);
|
||||
v_sumi2 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 8), v_sumi2);
|
||||
v_sumi3 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 12), v_sumi3);
|
||||
v_sumi0 = ggml_vec_dot_s16(v_sumi0, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 0));
|
||||
v_sumi1 = ggml_vec_dot_s16(v_sumi1, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 4));
|
||||
v_sumi2 = ggml_vec_dot_s16(v_sumi2, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 8));
|
||||
v_sumi3 = ggml_vec_dot_s16(v_sumi3, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 12));
|
||||
|
||||
v_sumi0 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 0), v_sumi0);
|
||||
v_sumi1 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 4), v_sumi1);
|
||||
v_sumi2 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 8), v_sumi2);
|
||||
v_sumi3 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 12), v_sumi3);
|
||||
v_sumi0 = ggml_vec_dot_s16(v_sumi0, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 0));
|
||||
v_sumi1 = ggml_vec_dot_s16(v_sumi1, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 4));
|
||||
v_sumi2 = ggml_vec_dot_s16(v_sumi2, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 8));
|
||||
v_sumi3 = ggml_vec_dot_s16(v_sumi3, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 12));
|
||||
}
|
||||
|
||||
const float32x4_t v_yd = __lzs_f16cx4_load(a_ptr[l].d);
|
||||
const float32x4_t v_xd = __lzs_f16cx4_load(b_ptr[l].d);
|
||||
const float32x4_t v_xd = ggml_vec_xl_f16x4_finite(b_ptr[l].d);
|
||||
|
||||
v_sumf[0] = vec_madd(vec_float(vxe_fold(v_sumi0)), vec_mul(v_xd, vec_splat(v_yd, 0)), v_sumf[0]);
|
||||
v_sumf[1] = vec_madd(vec_float(vxe_fold(v_sumi1)), vec_mul(v_xd, vec_splat(v_yd, 1)), v_sumf[1]);
|
||||
v_sumf[2] = vec_madd(vec_float(vxe_fold(v_sumi2)), vec_mul(v_xd, vec_splat(v_yd, 2)), v_sumf[2]);
|
||||
v_sumf[3] = vec_madd(vec_float(vxe_fold(v_sumi3)), vec_mul(v_xd, vec_splat(v_yd, 3)), v_sumf[3]);
|
||||
v_sumf[0] = vec_madd(vec_float(vec_paddl_s16(v_sumi0)), vec_mul(v_xd, vec_splat(v_yd, 0)), v_sumf[0]);
|
||||
v_sumf[1] = vec_madd(vec_float(vec_paddl_s16(v_sumi1)), vec_mul(v_xd, vec_splat(v_yd, 1)), v_sumf[1]);
|
||||
v_sumf[2] = vec_madd(vec_float(vec_paddl_s16(v_sumi2)), vec_mul(v_xd, vec_splat(v_yd, 2)), v_sumf[2]);
|
||||
v_sumf[3] = vec_madd(vec_float(vec_paddl_s16(v_sumi3)), vec_mul(v_xd, vec_splat(v_yd, 3)), v_sumf[3]);
|
||||
}
|
||||
|
||||
for (int m = 0; m < 4; m++) {
|
||||
|
||||
@@ -443,6 +443,13 @@ inline static ggml_int8x16x4_t ggml_vec_xl_s8x4(const int8_t * ptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// load 4 bytes and replicate them into all 4 word lanes
|
||||
inline static int8x16_t ggml_vec_xl_s8x4_splat(const int8_t * ptr) {
|
||||
uint32_t tmp;
|
||||
memcpy(&tmp, ptr, sizeof(tmp));
|
||||
return (int8x16_t)vec_splats(tmp);
|
||||
}
|
||||
|
||||
typedef struct ggml_int16x8x2_t {
|
||||
int16x8_t val[2];
|
||||
} ggml_int16x8x2_t;
|
||||
@@ -456,31 +463,17 @@ inline static ggml_int16x8x2_t ggml_vec_xl_s16x2(const int16_t * ptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
! WARNING: Very slow. Use vec_perm if possible. Refer to iq4_xs
|
||||
! or iq4_nl for example implementation.
|
||||
*/
|
||||
inline static int8x16_t ggml_vec_tbl(int8x16_t a, uint8x16_t b) {
|
||||
int8x16_t res;
|
||||
// faster fp16 -> fp32 conversion using vector intrinsics
|
||||
// WARNING: fp16 inf converts into 65536.0f. use this only for finite values.
|
||||
inline static float32x4_t ggml_vec_xl_f16x4_finite(const ggml_fp16_t * x) {
|
||||
uint64_t tmp;
|
||||
memcpy(&tmp, x, sizeof(tmp));
|
||||
|
||||
res[ 0] = a[b[ 0]];
|
||||
res[ 1] = a[b[ 1]];
|
||||
res[ 2] = a[b[ 2]];
|
||||
res[ 3] = a[b[ 3]];
|
||||
res[ 4] = a[b[ 4]];
|
||||
res[ 5] = a[b[ 5]];
|
||||
res[ 6] = a[b[ 6]];
|
||||
res[ 7] = a[b[ 7]];
|
||||
res[ 8] = a[b[ 8]];
|
||||
res[ 9] = a[b[ 9]];
|
||||
res[10] = a[b[10]];
|
||||
res[11] = a[b[11]];
|
||||
res[12] = a[b[12]];
|
||||
res[13] = a[b[13]];
|
||||
res[14] = a[b[14]];
|
||||
res[15] = a[b[15]];
|
||||
|
||||
return res;
|
||||
const uint64x2_t v_x = { tmp, tmp };
|
||||
const uint32x4_t v_xh = vec_unpackh((uint16x8_t)v_x);
|
||||
const uint32x4_t v_bits = vec_sl(vec_and(v_xh, vec_splats((uint32_t)0x7FFF)), 13);
|
||||
const float32x4_t v_abs = vec_mul((float32x4_t)v_bits, vec_splats(0x1.0p+112f));
|
||||
return (float32x4_t)vec_or((uint32x4_t)v_abs, vec_sl(vec_and(v_xh, vec_splats((uint32_t)0x8000)), 16));
|
||||
}
|
||||
|
||||
inline static int16x8_t vec_padd_s16(int16x8_t a, int16x8_t b) {
|
||||
@@ -492,6 +485,11 @@ inline static int16x8_t vec_padd_s16(int16x8_t a, int16x8_t b) {
|
||||
return v_abo + v_abe;
|
||||
}
|
||||
|
||||
inline static int32x4_t vec_paddl_s16(int16x8_t a) {
|
||||
const int16x8_t v_ones = vec_splats((int16_t)1);
|
||||
return vec_mule(a, v_ones) + vec_mulo(a, v_ones);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/ggml-org/llama.cpp/pull/14037
|
||||
*/
|
||||
@@ -505,7 +503,11 @@ inline static int32_t vec_hsum_i32x4(int32x4_t v) {
|
||||
return v_temp[0] + v_temp[1];
|
||||
}
|
||||
|
||||
inline static int32x4_t ggml_vec_dot(int32x4_t acc, int8x16_t a, int8x16_t b) {
|
||||
inline static int16x8_t ggml_vec_dot_s16(int16x8_t acc, int8x16_t a, int8x16_t b) {
|
||||
return vec_meadd(a, b, vec_moadd(a, b, acc));
|
||||
}
|
||||
|
||||
inline static int32x4_t ggml_vec_dot_s32(int32x4_t acc, int8x16_t a, int8x16_t b) {
|
||||
const int16x8_t p = vec_mule(a, b) + vec_mulo(a, b);
|
||||
return acc + (vec_unpackh(p) + vec_unpackl(p));
|
||||
}
|
||||
|
||||
@@ -2360,11 +2360,11 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) {
|
||||
case GGML_OP_MUL_MAT:
|
||||
case GGML_OP_MUL_MAT_ID:
|
||||
case GGML_OP_OUT_PROD:
|
||||
case GGML_OP_SET_ROWS:
|
||||
{
|
||||
n_tasks = n_threads;
|
||||
} break;
|
||||
case GGML_OP_GET_ROWS:
|
||||
case GGML_OP_SET_ROWS:
|
||||
{
|
||||
// FIXME: get_rows can use additional threads, but the cost of launching additional threads
|
||||
// decreases performance with GPU offloading
|
||||
|
||||
+130
-39
@@ -1292,24 +1292,37 @@ static void ggml_compute_forward_sum_f32(
|
||||
}
|
||||
|
||||
assert(ggml_is_scalar(dst));
|
||||
assert(src0->nb[0] == sizeof(float));
|
||||
|
||||
GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nb0, src0, nb)
|
||||
|
||||
ggml_float sum = 0;
|
||||
ggml_float row_sum = 0;
|
||||
ggml_float sum = 0;
|
||||
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
ggml_vec_sum_f32_ggf(ne00,
|
||||
&row_sum,
|
||||
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
|
||||
sum += row_sum;
|
||||
if (nb00 == sizeof(float)) {
|
||||
ggml_float row_sum = 0;
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
ggml_vec_sum_f32_ggf(ne00,
|
||||
&row_sum,
|
||||
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
|
||||
sum += row_sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
const char * row = (const char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
|
||||
for (int64_t i00 = 0; i00 < ne00; i00++) {
|
||||
sum += (ggml_float) *(const float *) (row + i00*nb00);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((float *) dst->data)[0] = sum;
|
||||
}
|
||||
|
||||
@@ -3548,6 +3561,73 @@ static void ggml_compute_forward_swiglu_oai_f32(
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_swiglu_oai_f16(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
char * src0_d = (char *) src0->data;
|
||||
char * src1_d = (char *) (src1 ? src1->data : src0->data);
|
||||
const size_t src0_o = src0->nb[1];
|
||||
const size_t src1_o = src1 ? src1->nb[1] : src0->nb[1];
|
||||
|
||||
GGML_ASSERT(ggml_is_contiguous_1(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous_1(dst));
|
||||
|
||||
if (src1) {
|
||||
GGML_ASSERT(ggml_is_contiguous_1(src1));
|
||||
GGML_ASSERT(src0->type == src1->type);
|
||||
}
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
const int nc = src1 ? src0->ne[0] : src0->ne[0] / 2;
|
||||
const int nr = ggml_nrows(src0);
|
||||
|
||||
GGML_ASSERT(dst->ne[0] == nc);
|
||||
GGML_ASSERT(ggml_nrows(dst) == nr);
|
||||
|
||||
const int32_t swapped = ggml_get_op_params_i32(dst, 1);
|
||||
const float alpha = ggml_get_op_params_f32(dst, 2);
|
||||
const float limit = ggml_get_op_params_f32(dst, 3);
|
||||
|
||||
// rows per thread
|
||||
const int dr = (nr + nth - 1)/nth;
|
||||
|
||||
// row range for this thread
|
||||
const int ir0 = dr*ith;
|
||||
const int ir1 = MIN(ir0 + dr, nr);
|
||||
|
||||
for (int i1 = ir0; i1 < ir1; i1++) {
|
||||
ggml_fp16_t * src0_p = (ggml_fp16_t *) (src0_d + i1*src0_o);
|
||||
ggml_fp16_t * src1_p = (ggml_fp16_t *) (src1_d + i1*src1_o);
|
||||
ggml_fp16_t * dst_p = (ggml_fp16_t *) ((char *) dst->data + i1*(dst->nb[1]));
|
||||
|
||||
if (!src1) {
|
||||
src0_p += swapped ? nc : 0;
|
||||
src1_p += swapped ? 0 : nc;
|
||||
}
|
||||
|
||||
for (int k = 0; k < nc; k++) {
|
||||
const float x = std::min(GGML_CPU_FP16_TO_FP32(src0_p[k]), limit);
|
||||
const float y = std::clamp(GGML_CPU_FP16_TO_FP32(src1_p[k]), -limit, limit);
|
||||
const float out_glu = x / (1.f + expf(alpha * (-x)));
|
||||
dst_p[k] = GGML_CPU_FP32_TO_FP16(out_glu * (y + 1.f));
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (int k = 0; k < nc; k++) {
|
||||
const float x = GGML_CPU_FP16_TO_FP32(dst_p[k]);
|
||||
GGML_UNUSED(x);
|
||||
assert(!isnan(x));
|
||||
assert(!isinf(x));
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
}
|
||||
|
||||
static void ggml_compute_forward_swiglu_oai(
|
||||
const ggml_compute_params * params,
|
||||
ggml_tensor * dst) {
|
||||
@@ -3559,6 +3639,10 @@ static void ggml_compute_forward_swiglu_oai(
|
||||
{
|
||||
ggml_compute_forward_swiglu_oai_f32(params, dst);
|
||||
} break;
|
||||
case GGML_TYPE_F16:
|
||||
{
|
||||
ggml_compute_forward_swiglu_oai_f16(params, dst);
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
GGML_ABORT("fatal error");
|
||||
@@ -8444,40 +8528,47 @@ static void ggml_compute_forward_pad_f32(
|
||||
const int32_t lp3 = ggml_get_op_params_i32(dst, 6);
|
||||
const int32_t rp3 = ggml_get_op_params_i32(dst, 7);
|
||||
|
||||
// TODO: optimize
|
||||
const int64_t nrows = ne1 * ne2 * ne3;
|
||||
const bool src_row_contig = src0->nb[0] == sizeof(float);
|
||||
|
||||
for (int64_t row = ith; row < nrows; row += nth) {
|
||||
const int64_t i1 = row % ne1;
|
||||
const int64_t i2 = (row / ne1) % ne2;
|
||||
const int64_t i3 = row / (ne1 * ne2);
|
||||
|
||||
float * dst_row = dst_ptr + row * ne0;
|
||||
|
||||
if constexpr (circular_t) {
|
||||
// circular padding wraps around on a torus
|
||||
const int64_t src_i1 = ggml_wrap_around(i1 - lp1, ne01);
|
||||
const int64_t src_i2 = ggml_wrap_around(i2 - lp2, ne02);
|
||||
const int64_t src_i3 = ggml_wrap_around(i3 - lp3, ne03);
|
||||
|
||||
for (int64_t i2 = 0; i2 < ne2; ++i2) {
|
||||
for (int64_t i1 = ith; i1 < ne1; i1 += nth) {
|
||||
for (int64_t i0 = 0; i0 < ne0; ++i0) {
|
||||
for (int64_t i3 = 0; i3 < ne3; ++i3) {
|
||||
// circular means wrap around on a torus, so x and y loop around
|
||||
if constexpr (circular_t) {
|
||||
const int64_t dst_idx = i3*(ne0*ne1*ne2) + i2*(ne0*ne1) + i1*ne0 + i0;
|
||||
const int64_t src_i0 = ggml_wrap_around(i0 - lp0, ne00);
|
||||
const int64_t src_i1 = ggml_wrap_around(i1 - lp1, ne01);
|
||||
const int64_t src_i2 = ggml_wrap_around(i2 - lp2, ne02);
|
||||
const int64_t src_i3 = ggml_wrap_around(i3 - lp3, ne03);
|
||||
const int64_t src_i0 = ggml_wrap_around(i0 - lp0, ne00);
|
||||
dst_row[i0] = *(const float *)((const char *) src0->data + src_i3*nb03 + src_i2*nb02 + src_i1*nb01 + src_i0*nb00);
|
||||
}
|
||||
} else {
|
||||
const bool interior = (i1 >= lp1 && i1 < ne1 - rp1)
|
||||
&& (i2 >= lp2 && i2 < ne2 - rp2)
|
||||
&& (i3 >= lp3 && i3 < ne3 - rp3);
|
||||
if (!interior) {
|
||||
memset(dst_row, 0, ne0 * sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t src_idx =
|
||||
src_i3*nb03 +
|
||||
src_i2*nb02 +
|
||||
src_i1*nb01 +
|
||||
src_i0*nb00;
|
||||
const char * src_row = (const char *) src0->data + (i3 - lp3)*nb03 + (i2 - lp2)*nb02 + (i1 - lp1)*nb01;
|
||||
|
||||
const float * src_ptr = (const float *)((char *) src0->data + src_idx);
|
||||
dst_ptr[dst_idx] = *src_ptr;
|
||||
if (src_row_contig && lp0 >= 0 && rp0 >= 0) {
|
||||
memset(dst_row, 0, lp0 * sizeof(float));
|
||||
memcpy(dst_row + lp0, src_row, ne00 * sizeof(float));
|
||||
memset(dst_row + lp0 + ne00, 0, rp0 * sizeof(float));
|
||||
} else {
|
||||
for (int64_t i0 = 0; i0 < ne0; ++i0) {
|
||||
if (i0 >= lp0 && i0 < ne0 - rp0) {
|
||||
dst_row[i0] = *(const float *)(src_row + (i0 - lp0)*nb00);
|
||||
} else {
|
||||
const int64_t dst_idx = i3*(ne0*ne1*ne2) + i2*(ne0*ne1) + i1*ne0 + i0;
|
||||
if ((i0 >= lp0 && i0 < ne0 - rp0) \
|
||||
&& (i1 >= lp1 && i1 < ne1 - rp1) \
|
||||
&& (i2 >= lp2 && i2 < ne2 - rp2) \
|
||||
&& (i3 >= lp3 && i3 < ne3 - rp3)) {
|
||||
const int64_t src_idx = (i3 - lp3)*nb03 + (i2 - lp2)*nb02 + (i1 - lp1)*nb01 + (i0 - lp0)*nb00;
|
||||
const float * src_ptr = (const float *)((char *) src0->data + src_idx);
|
||||
dst_ptr[dst_idx] = *src_ptr;
|
||||
} else {
|
||||
dst_ptr[dst_idx] = 0;
|
||||
}
|
||||
dst_row[i0] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ extern "C" {
|
||||
#define GGML_CPU_COMPUTE_FP32_TO_FP16(x) neon_compute_fp32_to_fp16(x)
|
||||
|
||||
#define GGML_CPU_FP16_TO_FP32(x) GGML_CPU_COMPUTE_FP16_TO_FP32(x)
|
||||
#define GGML_CPU_FP32_TO_FP16(x) GGML_CPU_COMPUTE_FP32_TO_FP16(x)
|
||||
|
||||
static inline float neon_compute_fp16_to_fp32(ggml_fp16_t h) {
|
||||
__fp16 tmp;
|
||||
@@ -144,13 +145,21 @@ extern float ggml_table_f32_ue4m3[1 << 8];
|
||||
// do not produce codegen as checks below don't test for __F16C__
|
||||
// but this check does.
|
||||
#if defined(__F16C__)
|
||||
#define GGML_CPU_FP16_TO_FP32(x) GGML_CPU_COMPUTE_FP16_TO_FP32(x)
|
||||
// force FP16 -> FP32 conversion to use LUT instead
|
||||
// see: https://github.com/ggml-org/llama.cpp/issues/30342
|
||||
//#define GGML_CPU_FP16_TO_FP32(x) GGML_CPU_COMPUTE_FP16_TO_FP32(x)
|
||||
#define GGML_CPU_FP32_TO_FP16(x) GGML_CPU_COMPUTE_FP32_TO_FP16(x)
|
||||
#endif
|
||||
|
||||
// On ARM NEON, it's quicker to directly convert x -> x instead of calling into ggml_lookup_fp16_to_fp32,
|
||||
// so we define GGML_CPU_FP16_TO_FP32 and GGML_CPU_FP32_TO_FP16 elsewhere for NEON.
|
||||
// This is also true for POWER9.
|
||||
// a branch that defines a GGML_CPU_COMPUTE_* macro must also select it, or the fallback takes over silently
|
||||
#if defined(GGML_CPU_COMPUTE_FP16_TO_FP32) && !defined(GGML_CPU_FP16_TO_FP32) && !defined(__F16C__) // see: https://github.com/ggml-org/llama.cpp/issues/30342
|
||||
#error "a branch defined GGML_CPU_COMPUTE_FP16_TO_FP32 without selecting it as GGML_CPU_FP16_TO_FP32"
|
||||
#endif
|
||||
#if defined(GGML_CPU_COMPUTE_FP32_TO_FP16) && !defined(GGML_CPU_FP32_TO_FP16)
|
||||
#error "a branch defined GGML_CPU_COMPUTE_FP32_TO_FP16 without selecting it as GGML_CPU_FP32_TO_FP16"
|
||||
#endif
|
||||
|
||||
// fall back to the lookup table / the portable software conversion for whichever direction no branch selected
|
||||
#if !defined(GGML_CPU_FP16_TO_FP32)
|
||||
inline static float ggml_lookup_fp16_to_fp32(ggml_fp16_t f) {
|
||||
uint16_t s;
|
||||
|
||||
@@ -244,16 +244,6 @@ static void tiled_run_microtile_avx2(const tiled_tile_src0 & src0, const tiled_t
|
||||
const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO;
|
||||
const int bs_off = seff * TILED_MICRO;
|
||||
|
||||
// Horizontal sum helper: converts 8x32-bit int lane inside a YMM register to scalar int32
|
||||
auto hsum256_epi32 = [](const __m256i v) -> int32_t {
|
||||
__m128i low = _mm256_castsi256_si128(v);
|
||||
__m128i high = _mm256_extracti128_si256(v, 1);
|
||||
__m128i sum128 = _mm_add_epi32(low, high);
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_SHUFFLE(2, 3, 0, 1)));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_SHUFFLE(1, 0, 3, 2)));
|
||||
return _mm_cvtsi128_si32(sum128);
|
||||
};
|
||||
|
||||
for (int i = 0; i < TILED_MICRO; i++) {
|
||||
const int ar = i0 + i;
|
||||
const int d_off = seff * TILED_MICRO + ar;
|
||||
@@ -362,15 +352,20 @@ static void tiled_run_microtile_avx2(const tiled_tile_src0 & src0, const tiled_t
|
||||
}
|
||||
}
|
||||
|
||||
// OPTIMIZATION 2: Vectorized Epilogue with FMA3 gating
|
||||
// Reduce accumulators into a 256-bit vector across 8 columns
|
||||
alignas(32) int32_t s1_vals[GROUP];
|
||||
#pragma GCC unroll 8
|
||||
for (int t = 0; t < GROUP; t++) {
|
||||
s1_vals[t] = hsum256_epi32(acc[t]);
|
||||
}
|
||||
__m256i s1_vec = _mm256_load_si256((const __m256i *) s1_vals);
|
||||
// Reduce the eight accumulators to column sums in acc[0]..acc[7] order, with additions modulo 2^32.
|
||||
const __m256i ab = _mm256_hadd_epi32(acc[0], acc[1]);
|
||||
const __m256i cd = _mm256_hadd_epi32(acc[2], acc[3]);
|
||||
const __m256i ef = _mm256_hadd_epi32(acc[4], acc[5]);
|
||||
const __m256i gh = _mm256_hadd_epi32(acc[6], acc[7]);
|
||||
|
||||
// Each 128-bit half now holds four column sums over input lanes 0..3 or 4..7.
|
||||
const __m256i abcd = _mm256_hadd_epi32(ab, cd);
|
||||
const __m256i efgh = _mm256_hadd_epi32(ef, gh);
|
||||
const __m256i lo = _mm256_permute2x128_si256(abcd, efgh, 0x20);
|
||||
const __m256i hi = _mm256_permute2x128_si256(abcd, efgh, 0x31);
|
||||
__m256i s1_vec = _mm256_add_epi32(lo, hi);
|
||||
|
||||
// OPTIMIZATION 2: Vectorized Epilogue with FMA3 gating
|
||||
if constexpr (BIAS != 0 && ACTBIAS) {
|
||||
// act-bias correction: raw = desired + 128*sum_s scales[s]*w_bsum[s]; corr precomputed in repack_src0
|
||||
s1_vec = _mm256_sub_epi32(s1_vec, _mm256_set1_epi32(mins_row[0]));
|
||||
|
||||
@@ -401,6 +401,10 @@ void ggml_vec_silu_f32(const int n, float * y, const float * x) {
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vst1q_f32(y + i, ggml_v_silu(vld1q_f32(x + i)));
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vec_xst(ggml_v_silu(vec_xl(0, x + i)), 0, y + i);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
for (int vl; i < n; i += vl) {
|
||||
vl = __riscv_vsetvl_e32m2(n - i);
|
||||
@@ -438,6 +442,10 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vst1q_f32(y + i, vmulq_f32(ggml_v_silu(vld1q_f32(x + i)), vld1q_f32(g + i)));
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vec_xst(vec_mul(ggml_v_silu(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
for (int vl; i < n; i += vl) {
|
||||
vl = __riscv_vsetvl_e32m2(n - i);
|
||||
@@ -581,6 +589,12 @@ ggml_float ggml_vec_soft_max_f32(const int n, float * y, const float * x, float
|
||||
vst1q_f32(y + i, val);
|
||||
sum += (ggml_float)vaddvq_f32(val);
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
float32x4_t v_val = ggml_v_expf(vec_sub(vec_xl(0, x + i), vec_splats(max)));
|
||||
vec_xst(v_val, 0, y + i);
|
||||
sum += (ggml_float)vec_hsum_f32x4(v_val);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
vfloat64m1_t vsum = __riscv_vfmv_v_f_f64m1(0, 1);
|
||||
for (int avl; i < n; i += avl) {
|
||||
|
||||
+41
-1
@@ -1334,6 +1334,46 @@ inline static __m128 ggml_v_silu(__m128 x) {
|
||||
return _mm_div_ps(x, one_plus_exp_neg_x);
|
||||
}
|
||||
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
|
||||
// adapted from arm limited optimized routine
|
||||
// the maximum error is 1.45358 plus 0.5 ulps
|
||||
// numbers above 88.38 will flush to infinity
|
||||
// numbers beneath -103.97 will flush to zero
|
||||
inline static float32x4_t ggml_v_expf(float32x4_t v_x) {
|
||||
const float32x4_t v_r = vec_splats(0x1.8p23f);
|
||||
const float32x4_t v_z = vec_madd(v_x, vec_splats(0x1.715476p+0f), v_r);
|
||||
const float32x4_t v_n = vec_sub(v_z, v_r);
|
||||
const float32x4_t v_b = vec_nmsub(v_n, vec_splats(0x1.7f7d1cp-20f),
|
||||
vec_nmsub(v_n, vec_splats(0x1.62e4p-1f), v_x));
|
||||
const uint32x4_t v_e = vec_sl((uint32x4_t)v_z, 23);
|
||||
const float32x4_t v_k = (float32x4_t)vec_add(v_e, (uint32x4_t)vec_splats(1.0f));
|
||||
const uint32x4_t v_c = (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(126.0f));
|
||||
const float32x4_t v_u = vec_mul(v_b, v_b);
|
||||
const float32x4_t v_j = vec_madd(
|
||||
vec_madd(vec_madd(vec_splats(0x1.0e4020p-7f), v_b, vec_splats(0x1.573e2ep-5f)), v_u,
|
||||
vec_madd(vec_splats(0x1.555e66p-3f), v_b, vec_splats(0x1.fffdb6p-2f))),
|
||||
v_u, vec_mul(vec_splats(0x1.ffffecp-1f), v_b));
|
||||
if (vec_all_eq(v_c, vec_splats(UINT32_C(0)))) {
|
||||
return vec_madd(v_j, v_k, v_k);
|
||||
}
|
||||
const uint32x4_t v_d = vec_and((uint32x4_t)vec_cmple(v_n, vec_splats(0.0f)), vec_splats(UINT32_C(0x82000000)));
|
||||
const float32x4_t v_s1 = (float32x4_t)vec_add(v_d, vec_splats(UINT32_C(0x7f000000)));
|
||||
const float32x4_t v_s2 = (float32x4_t)vec_sub(v_e, v_d);
|
||||
return vec_sel(vec_sel(vec_madd(v_k, v_j, v_k), vec_mul(vec_madd(v_s2, v_j, v_s2), v_s1), v_c),
|
||||
vec_mul(v_s1, v_s1), (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(192.0f)));
|
||||
}
|
||||
|
||||
// computes silu x/(1+exp(-x)) in single precision vector
|
||||
inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
|
||||
const float32x4_t v_one = vec_splats(1.0f);
|
||||
const float32x4_t v_zero = vec_splats(0.0f);
|
||||
const float32x4_t v_neg_x = vec_sub(v_zero, v_x);
|
||||
const float32x4_t v_exp_neg_x = ggml_v_expf(v_neg_x);
|
||||
const float32x4_t v_one_plus_exp_neg_x = vec_add(v_one, v_exp_neg_x);
|
||||
return vec_div(v_x, v_one_plus_exp_neg_x);
|
||||
}
|
||||
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
|
||||
// adapted from arm limited optimized routine
|
||||
@@ -1386,7 +1426,7 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
|
||||
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
|
||||
}
|
||||
|
||||
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __riscv_v_intrinsic
|
||||
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __VXE__ / __riscv_v_intrinsic
|
||||
|
||||
inline static void ggml_vec_silu_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#define GGML_CUDA_CC_VOLTA 700
|
||||
#define GGML_CUDA_CC_TURING 750
|
||||
#define GGML_CUDA_CC_AMPERE 800
|
||||
#define GGML_CUDA_CC_AMPERE_GA10X 860
|
||||
#define GGML_CUDA_CC_ORIN 870
|
||||
#define GGML_CUDA_CC_ADA_LOVELACE 890
|
||||
#define GGML_CUDA_CC_HOPPER 900
|
||||
@@ -1550,11 +1551,11 @@ struct ggml_backend_cuda_context {
|
||||
// pool
|
||||
std::unique_ptr<ggml_cuda_pool> pools[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS];
|
||||
|
||||
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(int device, int stream_no);
|
||||
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(ggml_backend_cuda_context & ctx, int device, int stream_no);
|
||||
|
||||
ggml_cuda_pool & pool(int device) {
|
||||
if (pools[device][curr_stream_no] == nullptr) {
|
||||
pools[device][curr_stream_no] = new_pool_for_device(device, curr_stream_no);
|
||||
pools[device][curr_stream_no] = new_pool_for_device(*this, device, curr_stream_no);
|
||||
}
|
||||
return *pools[device][curr_stream_no];
|
||||
}
|
||||
|
||||
@@ -145,7 +145,8 @@ static __global__ void dsv4_hc_pre_f32(
|
||||
dst[i0*sd0 + it*sd1] = scale * sum;
|
||||
}
|
||||
|
||||
template <bool has_comb>
|
||||
// gated: post = gate_scale_out*sigmoid(gate_scale_in*post), the fused SCALE -> SIGMOID -> SCALE chain of qwen4exp
|
||||
template <bool has_comb, bool gated>
|
||||
static __global__ void dsv4_hc_post_f32(
|
||||
const float * x,
|
||||
const float * residual,
|
||||
@@ -167,7 +168,9 @@ static __global__ void dsv4_hc_post_f32(
|
||||
int64_t sc2,
|
||||
int64_t sd0,
|
||||
int64_t sd1,
|
||||
int64_t sd2) {
|
||||
int64_t sd2,
|
||||
float gate_scale_in,
|
||||
float gate_scale_out) {
|
||||
ggml_cuda_pdl_lc();
|
||||
const int64_t ir = (int64_t) blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const int64_t nr = n_embd * hc * n_tokens;
|
||||
@@ -182,7 +185,12 @@ static __global__ void dsv4_hc_post_f32(
|
||||
const int64_t idst = (ir / n_embd) % hc;
|
||||
const int64_t it = ir / (n_embd * hc);
|
||||
|
||||
float sum = x[i0*sx0 + it*sx1] * post[idst*sp0 + it*sp1];
|
||||
float p = post[idst*sp0 + it*sp1];
|
||||
if constexpr (gated) {
|
||||
p = gate_scale_out * (1.0f / (1.0f + expf(-(gate_scale_in * p))));
|
||||
}
|
||||
|
||||
float sum = x[i0*sx0 + it*sx1] * p;
|
||||
if constexpr (has_comb) {
|
||||
for (int64_t isrc = 0; isrc < hc; ++isrc) {
|
||||
sum += residual[i0*sr0 + isrc*sr1 + it*sr2] * comb[idst*sc0 + isrc*sc1 + it*sc2];
|
||||
@@ -272,12 +280,19 @@ void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst
|
||||
scale);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
|
||||
const ggml_tensor * gate_scale_in, const ggml_tensor * gate_scale_out) {
|
||||
const ggml_tensor * x = dst->src[0];
|
||||
const ggml_tensor * residual = dst->src[1];
|
||||
const ggml_tensor * post = dst->src[2];
|
||||
const ggml_tensor * comb = dst->src[3];
|
||||
|
||||
// when fused, read the raw post weights from before the SCALE -> SIGMOID -> SCALE chain
|
||||
const bool gated = gate_scale_in != nullptr;
|
||||
const ggml_tensor * post = gated ? gate_scale_in->src[0] : dst->src[2];
|
||||
|
||||
const float gate_in = gated ? ggml_get_op_params_f32(gate_scale_in, 0) : 1.0f;
|
||||
const float gate_out = gated ? ggml_get_op_params_f32(gate_scale_out, 0) : 1.0f;
|
||||
|
||||
GGML_ASSERT(x->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(residual->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(post->type == GGML_TYPE_F32);
|
||||
@@ -303,7 +318,8 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
|
||||
const dim3 grid_dims((nr + block_size - 1) / block_size, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, ctx.stream());
|
||||
|
||||
auto kernel = comb ? dsv4_hc_post_f32<true> : dsv4_hc_post_f32<false>;
|
||||
auto kernel = comb ? (gated ? dsv4_hc_post_f32<true, true> : dsv4_hc_post_f32<true, false>)
|
||||
: (gated ? dsv4_hc_post_f32<false, true> : dsv4_hc_post_f32<false, false>);
|
||||
ggml_cuda_kernel_launch(kernel, launch_params,
|
||||
(const float *) x->data, (const float *) residual->data,
|
||||
(const float *) post->data, comb ? (const float *) comb->data : nullptr, (float *) dst->data,
|
||||
@@ -312,5 +328,6 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
|
||||
nbr0 / sizeof(float), nbr1 / sizeof(float), nbr2 / sizeof(float),
|
||||
nbp0 / sizeof(float), nbp1 / sizeof(float),
|
||||
nbc0 / sizeof(float), nbc1 / sizeof(float), nbc2 / sizeof(float),
|
||||
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float));
|
||||
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float),
|
||||
gate_in, gate_out);
|
||||
}
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
|
||||
void ggml_cuda_op_dsv4_hc_comb(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
// gate_scale_in/gate_scale_out: the SCALE nodes of a fused SCALE -> SIGMOID -> SCALE chain feeding src[2]
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
|
||||
const ggml_tensor * gate_scale_in = nullptr, const ggml_tensor * gate_scale_out = nullptr);
|
||||
|
||||
@@ -955,7 +955,7 @@ static __global__ void flash_attn_tile(
|
||||
if (ncols2 == 1) {
|
||||
// Branch with out-of-bounds checks.
|
||||
int k_VKQ_0 = blockIdx.y*nbatch_fa;
|
||||
while (k_VKQ_0 < k_VKQ_max - nbatch_fa) {
|
||||
while (k_VKQ_0 <= k_VKQ_max - nbatch_fa) {
|
||||
constexpr bool oob_check = false;
|
||||
flash_attn_tile_iter<warp_size, nwarps, ncols1, ncols2, DKQ, DV, nbatch_fa, nbatch_K, use_logit_softcap, oob_check>
|
||||
(Q_tmp, K_h2, V_h2, maskh, ne01, logit_softcap, slope, KQ, KV_tmp,
|
||||
|
||||
@@ -317,7 +317,7 @@ static __global__ void flash_attn_ext_vec(
|
||||
#endif // V_DOT2_F32_F16_AVAILABLE
|
||||
}
|
||||
|
||||
ggml_cuda_syncwarp();
|
||||
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
|
||||
|
||||
#pragma unroll
|
||||
for (int k0 = 0; k0 < WARP_SIZE; k0 += V_cols_per_iter) {
|
||||
@@ -374,6 +374,8 @@ static __global__ void flash_attn_ext_vec(
|
||||
}
|
||||
#endif // V_DOT2_F32_F16_AVAILABLE
|
||||
}
|
||||
|
||||
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
|
||||
}
|
||||
|
||||
if (sinks && blockIdx.y == 0) {
|
||||
|
||||
@@ -417,9 +417,7 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
|
||||
|
||||
#define FATTN_VEC_CASE(D, type_K_case, type_V_case) \
|
||||
if constexpr (GGML_CUDA_FA_##type_K_case##_##type_V_case) { \
|
||||
const bool type_K_okay = type_K == GGML_TYPE_##type_K_case || (type_K == GGML_TYPE_F32 && GGML_TYPE_##type_K_case == GGML_TYPE_F16); \
|
||||
const bool type_V_okay = type_V == GGML_TYPE_##type_V_case || (type_V == GGML_TYPE_F32 && GGML_TYPE_##type_V_case == GGML_TYPE_F16); \
|
||||
if (head_size == (D) && type_K_okay && type_V_okay) { \
|
||||
if (head_size == (D) && type_K == GGML_TYPE_##type_K_case && type_V == GGML_TYPE_##type_V_case) { \
|
||||
return ggml_cuda_flash_attn_ext_vec_case<D, GGML_TYPE_##type_K_case, GGML_TYPE_##type_V_case>; \
|
||||
} \
|
||||
} \
|
||||
@@ -432,7 +430,9 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
|
||||
typedef void (* fattn_vec_case_t)(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
// Vector kernel for the given head size and K/V types, nullptr if its template instance was not compiled:
|
||||
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K, const ggml_type type_V) {
|
||||
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K_in, const ggml_type type_V_in) {
|
||||
const ggml_type type_K = type_K_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_K_in;
|
||||
const ggml_type type_V = type_V_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_V_in;
|
||||
FATTN_VEC_CASES_ALL_D(F16, F16)
|
||||
FATTN_VEC_CASES_ALL_D(Q4_0, F16)
|
||||
FATTN_VEC_CASES_ALL_D(Q4_1, F16)
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
#include "gated-delta-net-mma.cuh"
|
||||
#include "mma.cuh"
|
||||
|
||||
#include <climits>
|
||||
#include <mutex>
|
||||
|
||||
namespace ggml_cuda_gated_delta_net_mma {
|
||||
constexpr int D = 128;
|
||||
using bf16 = nv_bfloat16;
|
||||
|
||||
template <int R, int C> struct matrix {
|
||||
bf16 hi[R * C], lo[R * C];
|
||||
|
||||
static __device__ __forceinline__ int index(int r, int c) {
|
||||
#ifdef GGML_USE_HIP
|
||||
return r * C + c;
|
||||
#else
|
||||
return 2 * ggml_cuda_mma::swizzle<C / 2, nv_bfloat162>(r * (C / 2) + c / 2, r) + c % 2;
|
||||
#endif // GGML_USE_HIP
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void store2(int r, int c, float2 x) {
|
||||
#ifdef GGML_USE_MUSA
|
||||
store(r, c, x.x);
|
||||
store(r, c + 1, x.y);
|
||||
#else
|
||||
const int i = index(r, c);
|
||||
const auto h = __float22bfloat162_rn(x);
|
||||
const float2 rounded = __bfloat1622float2(h);
|
||||
*reinterpret_cast<nv_bfloat162 *>(hi + i) = h;
|
||||
*reinterpret_cast<nv_bfloat162 *>(lo + i) =
|
||||
__float22bfloat162_rn(make_float2(x.x - rounded.x, x.y - rounded.y));
|
||||
#endif // GGML_USE_MUSA
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void store(int r, int c, float x) {
|
||||
const int i = index(r, c);
|
||||
const bf16 h = __float2bfloat16(x);
|
||||
hi[i] = h;
|
||||
lo[i] = __float2bfloat16(x - __bfloat162float(h));
|
||||
}
|
||||
};
|
||||
|
||||
template <int C, int V> struct alignas(128) shared {
|
||||
matrix<C, D> q, k;
|
||||
matrix<C, C> p, inverse;
|
||||
matrix<C, V> delta;
|
||||
|
||||
union {
|
||||
matrix<D, V> state;
|
||||
matrix<C, V> solved;
|
||||
} scratch;
|
||||
|
||||
float lower[C * C], prefix[C], beta[C];
|
||||
|
||||
static __device__ __forceinline__ int lower_index(int r, int c) { return r * C + c; }
|
||||
};
|
||||
|
||||
#if defined(AMPERE_MMA_AVAILABLE) || (defined(AMD_WMMA_AVAILABLE) && defined(RDNA3))
|
||||
using namespace ggml_cuda_mma;
|
||||
#ifdef GGML_USE_HIP
|
||||
using acc = tile<16, 16, float, DATA_LAYOUT_J_MAJOR>;
|
||||
using a_tile = tile<16, 8, nv_bfloat162, DATA_LAYOUT_I_MAJOR_MIRRORED>;
|
||||
using b_tile = a_tile;
|
||||
#else
|
||||
using acc = tile<16, 8, float>;
|
||||
using a_tile = tile<16, 8, nv_bfloat162>;
|
||||
using b_tile = tile<8, 8, nv_bfloat162>;
|
||||
#endif // GGML_USE_HIP
|
||||
|
||||
template <class tile_t, bool TRANS, int R, int C>
|
||||
__device__ __forceinline__ tile_t load(const matrix<R, C> & m, int row, int col, bool low) {
|
||||
tile_t t;
|
||||
const bf16 * data = low ? m.lo : m.hi;
|
||||
#ifdef GGML_USE_HIP
|
||||
#pragma unroll
|
||||
for (int i = 0; i < tile_t::ne; ++i) {
|
||||
const int r = row + tile_t::get_i(i), c = col + 2 * tile_t::get_j(i);
|
||||
const bf16 x = data[TRANS ? m.index(c, r) : m.index(r, c)];
|
||||
const bf16 y = data[TRANS ? m.index(c + 1, r) : m.index(r, c + 1)];
|
||||
t.x[i] = __float22bfloat162_rn(make_float2(__bfloat162float(x), __bfloat162float(y)));
|
||||
}
|
||||
#else
|
||||
const auto * packed = reinterpret_cast<const nv_bfloat162 *>(data);
|
||||
if constexpr (TRANS) {
|
||||
load_ldmatrix_trans_swizzled<C / 2>(t, packed, col * (C / 2) + row / 2);
|
||||
} else {
|
||||
load_ldmatrix_swizzled<C / 2>(t, packed, row * (C / 2) + col / 2);
|
||||
}
|
||||
#endif // GGML_USE_HIP
|
||||
return t;
|
||||
}
|
||||
|
||||
// B is addressed as B-transpose. Preserve both BF16 first-order residual terms.
|
||||
template <int K, bool TA = false, bool TB = false, int AR, int AC, int BR, int BC>
|
||||
__device__ __forceinline__ void product(acc & c, const matrix<AR, AC> & a, const matrix<BR, BC> & b, int row, int col) {
|
||||
#pragma unroll
|
||||
for (int k = 0; k < K; k += 16) {
|
||||
const auto ah = load<a_tile, TA>(a, row, k, false);
|
||||
const auto al = load<a_tile, TA>(a, row, k, true);
|
||||
const auto bh = load<b_tile, TB>(b, col, k, false);
|
||||
const auto bl = load<b_tile, TB>(b, col, k, true);
|
||||
mma(c, al, bh);
|
||||
mma(c, ah, bl);
|
||||
mma(c, ah, bh);
|
||||
}
|
||||
}
|
||||
|
||||
template <int R, int C> __device__ __forceinline__ void store(matrix<R, C> & m, const acc & x, int r, int c) {
|
||||
#pragma unroll
|
||||
#ifdef GGML_USE_HIP
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
m.store(r + acc::get_i(i), c + acc::get_j(i), x.x[i]);
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < acc::ne; i += 2) {
|
||||
m.store2(r + acc::get_i(i), c + acc::get_j(i), make_float2(x.x[i], x.x[i + 1]));
|
||||
}
|
||||
#endif // GGML_USE_HIP
|
||||
}
|
||||
#endif // defined(AMPERE_MMA_AVAILABLE) || (defined(AMD_WMMA_AVAILABLE) && defined(RDNA3))
|
||||
|
||||
// Each block owns V independent value columns and advances by C tokens.
|
||||
// Keep recurrent state in FP32; pack high/residual BF16 operands inside the block.
|
||||
template <int C, int V, int BLOCKS = 1>
|
||||
static __global__ __launch_bounds__(256, BLOCKS) void gdn_single(ggml_cuda_gdn_mma_args a) {
|
||||
#if defined(AMPERE_MMA_AVAILABLE) || (defined(AMD_WMMA_AVAILABLE) && defined(RDNA3))
|
||||
static_assert(ggml_cuda_get_physical_warp_size() == 32, "GDN MMA requires 32-lane warps");
|
||||
constexpr int WARPS = 8;
|
||||
#ifdef GGML_USE_HIP
|
||||
constexpr int N = 16;
|
||||
#else
|
||||
constexpr int N = 8;
|
||||
#endif // GGML_USE_HIP
|
||||
static_assert(C >= 16 && (C & (C - 1)) == 0 && V % N == 0 && D % V == 0, "invalid GDN tile");
|
||||
extern __shared__ __align__(128) unsigned char bytes[];
|
||||
auto & s = *reinterpret_cast<shared<C, V> *>(bytes);
|
||||
const int lane = threadIdx.x, warp = threadIdx.y, tid = warp * 32 + lane;
|
||||
const int slice = blockIdx.x % (D / V);
|
||||
const int h = (blockIdx.x / (D / V)) % a.H;
|
||||
const int seq = blockIdx.x / ((D / V) * a.H);
|
||||
const int v0 = slice * V;
|
||||
const int64_t soff = ((int64_t) seq * a.H + h) * D * D;
|
||||
const int64_t qoff = (seq / a.rq3) * a.sq3 + (h % a.H_k) * a.sq1;
|
||||
const int64_t voff = seq * a.sv3 + h * a.sv1 + v0;
|
||||
const int64_t goff = seq * a.sb3 + h * a.sb1;
|
||||
float * out = a.dst + ((int64_t) seq * a.n_tokens * a.H + h) * D + v0;
|
||||
constexpr int STATE_TILES = (D / 16) * (V / N);
|
||||
constexpr int PER_WARP = STATE_TILES / WARPS;
|
||||
static_assert(STATE_TILES % WARPS == 0, "state must divide across warps");
|
||||
acc state[PER_WARP];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < PER_WARP; ++j) {
|
||||
const int tile_index = warp + j * WARPS, r = (tile_index / (V / N)) * 16, c = (tile_index % (V / N)) * N;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
state[j].x[i] = a.state[soff + (v0 + c + acc::get_j(i)) * D + r + acc::get_i(i)];
|
||||
}
|
||||
}
|
||||
auto read_qk = [&](int64_t t, float4 & q, float4 & k) {
|
||||
t += warp;
|
||||
q = k = make_float4(0, 0, 0, 0);
|
||||
if (t < a.n_tokens) {
|
||||
const int64_t off = qoff + t * a.sq2 + 4 * lane;
|
||||
if ((((uintptr_t) (a.q + off) | (uintptr_t) (a.k + off)) & 15) == 0) {
|
||||
q = *reinterpret_cast<const float4 *>(a.q + off);
|
||||
k = *reinterpret_cast<const float4 *>(a.k + off);
|
||||
} else {
|
||||
q = make_float4(a.q[off], a.q[off + 1], a.q[off + 2], a.q[off + 3]);
|
||||
k = make_float4(a.k[off], a.k[off + 1], a.k[off + 2], a.k[off + 3]);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Carry the first eight input rows across chunks to hide global-load latency.
|
||||
constexpr bool PREFETCH = C == 16 && V == 32;
|
||||
float4 next_q, next_k;
|
||||
if constexpr (PREFETCH) {
|
||||
read_qk(0, next_q, next_k);
|
||||
}
|
||||
for (int64_t t0 = 0; t0 < a.n_tokens; t0 += C) {
|
||||
const int valid = min((int64_t) C, a.n_tokens - t0);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < C / 8; ++j) {
|
||||
const int t = warp + 8 * j, d = 4 * lane;
|
||||
float4 q, k;
|
||||
if (PREFETCH && j == 0) {
|
||||
q = next_q;
|
||||
k = next_k;
|
||||
} else {
|
||||
read_qk(t0 + 8 * j, q, k);
|
||||
}
|
||||
s.q.store2(t, d, make_float2(q.x * a.scale, q.y * a.scale));
|
||||
s.q.store2(t, d + 2, make_float2(q.z * a.scale, q.w * a.scale));
|
||||
s.k.store2(t, d, make_float2(k.x, k.y));
|
||||
s.k.store2(t, d + 2, make_float2(k.z, k.w));
|
||||
}
|
||||
// Load gates in parallel before the FP32 prefix scan.
|
||||
if (warp == 0) {
|
||||
float carry = 0.f;
|
||||
for (int base = 0; base < C; base += 32) {
|
||||
const int t = base + lane;
|
||||
float g = t < valid ? a.g[goff + (t0 + t) * a.sb2] : 0.f;
|
||||
const float beta = t < valid ? a.beta[goff + (t0 + t) * a.sb2] : 0.f;
|
||||
#pragma unroll
|
||||
for (int offset = 1; offset < 32; offset *= 2) {
|
||||
const float previous = __shfl_up_sync(0xffffffff, g, offset, 32);
|
||||
if (lane >= offset) {
|
||||
g += previous;
|
||||
}
|
||||
}
|
||||
g += carry;
|
||||
if (t < C) {
|
||||
s.prefix[t] = g;
|
||||
s.beta[t] = beta;
|
||||
}
|
||||
carry = __shfl_sync(0xffffffff, g, 31, 32);
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int j = 0; j < PER_WARP; ++j) {
|
||||
const int ti = warp + j * WARPS;
|
||||
store(s.scratch.state, state[j], (ti / (V / N)) * 16, (ti % (V / N)) * N);
|
||||
}
|
||||
__syncthreads();
|
||||
if constexpr (PREFETCH) {
|
||||
if (t0 + C < a.n_tokens) {
|
||||
read_qk(t0 + C, next_q, next_k);
|
||||
}
|
||||
}
|
||||
// V32 overlaps Gram work with one output tile per projection warp.
|
||||
constexpr bool SPLIT_PROJECTION = C == 16 && V == 32;
|
||||
acc base_output[((C / 16) * (V / N) + WARPS - 1) / WARPS];
|
||||
// delta = beta * (v - decay * K S), base output = decay * Q S
|
||||
auto project = [&]() {
|
||||
if constexpr (V == 32) {
|
||||
if (warp < 4) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int ti = (V == 32 ? warp - 4 : warp); ti < (C / 16) * (V / N); ti += (V == 32 ? 4 : WARPS)) {
|
||||
const int r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N;
|
||||
acc qs, ks;
|
||||
product<D, false, true>(qs, s.q, s.scratch.state, r, c);
|
||||
product<D, false, true>(ks, s.k, s.scratch.state, r, c);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
const int t = r + acc::get_i(i), v = c + acc::get_j(i);
|
||||
const float decay = expf(s.prefix[t]);
|
||||
const float vv = t < valid ? a.v[voff + (t0 + t) * a.sv2 + v] : 0.f;
|
||||
s.delta.store(t, v, s.beta[t] * (vv - decay * ks.x[i]));
|
||||
base_output[ti / WARPS].x[i] = decay * qs.x[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
// A = beta * strict_tril(K K^T * decay), P = tril(Q K^T * decay)
|
||||
for (int ti = warp; ti < 2 * (C / 16) * (C / N) && (V != 32 || warp < 4); ti += (V == 32 ? 4 : WARPS)) {
|
||||
const bool gram = ti < (C / 16) * (C / N);
|
||||
const int t = ti % ((C / 16) * (C / N)), r = (t / (C / N)) * 16, c = (t % (C / N)) * N;
|
||||
acc x;
|
||||
if (gram) {
|
||||
product<D>(x, s.k, s.k, r, c);
|
||||
} else {
|
||||
product<D>(x, s.q, s.k, r, c);
|
||||
}
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
const int rr = r + acc::get_i(i), cc = c + acc::get_j(i);
|
||||
const float v = rr < valid && cc <= rr ? x.x[i] * expf(s.prefix[rr] - s.prefix[cc]) : 0.f;
|
||||
if (gram) {
|
||||
if (cc < rr) {
|
||||
s.lower[s.lower_index(rr, cc)] = v * s.beta[rr];
|
||||
}
|
||||
} else {
|
||||
s.p.store(rr, cc, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (V == 32) {
|
||||
project();
|
||||
}
|
||||
__syncthreads();
|
||||
// inverse = (I + A)^-1 by forward substitution
|
||||
for (int row = warp; row < C; row += WARPS) {
|
||||
float x[(C + 31) / 32];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < (C + 31) / 32; ++j) {
|
||||
const int col = lane + 32 * j;
|
||||
x[j] = col == row ? 1.f : (col < row ? -s.lower[s.lower_index(row, col)] : 0.f);
|
||||
}
|
||||
for (int pivot = row - 1; pivot > 0; --pivot) {
|
||||
const float xp = __shfl_sync(0xffffffff, x[pivot / 32], pivot % 32, 32);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < (C + 31) / 32; ++j) {
|
||||
const int col = lane + 32 * j;
|
||||
if (col < pivot) {
|
||||
x[j] -= xp * s.lower[s.lower_index(pivot, col)];
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int j = 0; j < (C + 31) / 32; ++j) {
|
||||
if (lane + 32 * j < C) {
|
||||
s.inverse.store(row, lane + 32 * j, x[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (V != 32) {
|
||||
project();
|
||||
}
|
||||
__syncthreads();
|
||||
// solved = inverse * delta
|
||||
for (int ti = warp; ti < (C / 16) * (V / N); ti += WARPS) {
|
||||
const int r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N;
|
||||
acc x;
|
||||
product<C, false, true>(x, s.inverse, s.delta, r, c);
|
||||
store(s.scratch.solved, x, r, c);
|
||||
}
|
||||
__syncthreads();
|
||||
// out = base output + P * solved
|
||||
for (int ti = (SPLIT_PROJECTION ? warp - 4 : warp); ti < (C / 16) * (V / N) && (!SPLIT_PROJECTION || warp >= 4);
|
||||
ti += WARPS) {
|
||||
const int r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N;
|
||||
acc x;
|
||||
product<C, false, true>(x, s.p, s.scratch.solved, r, c);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
const int t = r + acc::get_i(i), v = c + acc::get_j(i);
|
||||
if (t < valid) {
|
||||
out[(t0 + t) * a.H * D + v] = base_output[ti / WARPS].x[i] + x.x[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// S = decay(chunk) * S + K^T * (solved * decay to chunk end)
|
||||
for (int i = tid; i < C * V; i += 256) {
|
||||
const int t = i / V, v = i % V, j = s.scratch.solved.index(t, v);
|
||||
const float x = __bfloat162float(s.scratch.solved.hi[j]) + __bfloat162float(s.scratch.solved.lo[j]);
|
||||
s.delta.store(t, v, x * expf(s.prefix[valid - 1] - s.prefix[t]));
|
||||
}
|
||||
__syncthreads();
|
||||
const float decay = expf(s.prefix[valid - 1]);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < PER_WARP; ++j) {
|
||||
const int ti = warp + j * WARPS, r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
state[j].x[i] *= decay;
|
||||
}
|
||||
product<C, true, true>(state[j], s.k, s.delta, r, c);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
#pragma unroll
|
||||
for (int j = 0; j < PER_WARP; ++j) {
|
||||
const int ti = warp + j * WARPS, r = (ti / (V / N)) * 16, c = (ti % (V / N)) * N;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < acc::ne; ++i) {
|
||||
a.state_out[soff + (v0 + c + acc::get_j(i)) * D + r + acc::get_i(i)] = state[j].x[i];
|
||||
}
|
||||
}
|
||||
#else
|
||||
GGML_UNUSED_VARS(a);
|
||||
NO_DEVICE_CODE;
|
||||
#endif // defined(AMPERE_MMA_AVAILABLE) || (defined(AMD_WMMA_AVAILABLE) && defined(RDNA3))
|
||||
}
|
||||
|
||||
template <int C, int V, int BLOCKS = 1> bool init(int device) {
|
||||
static std::once_flag once[GGML_CUDA_MAX_DEVICES];
|
||||
static bool available[GGML_CUDA_MAX_DEVICES] = {};
|
||||
std::call_once(once[device], [device] {
|
||||
ggml_cuda_set_device(device);
|
||||
if (sizeof(shared<C, V>) > ggml_cuda_info().devices[device].smpbo) {
|
||||
return;
|
||||
}
|
||||
const auto status = cudaFuncSetAttribute((const void *) gdn_single<C, V, BLOCKS>,
|
||||
cudaFuncAttributeMaxDynamicSharedMemorySize, sizeof(shared<C, V>));
|
||||
available[device] = status == cudaSuccess;
|
||||
if (status != cudaSuccess) {
|
||||
(void) cudaGetLastError();
|
||||
}
|
||||
});
|
||||
return available[device];
|
||||
}
|
||||
|
||||
enum class gdn_path { ar, slice32_one_block, slice32_two_blocks, slice64, whole_head };
|
||||
|
||||
static gdn_path select_gdn_mma_path(int device, const ggml_cuda_gdn_mma_args & a) {
|
||||
GGML_ASSERT(device >= 0 && device < GGML_CUDA_MAX_DEVICES);
|
||||
if (!a.eligible || a.H_k != 16 || (a.H != 16 && a.H != 32 && a.H != 48 && a.H != 64) || a.n_tokens < 64 ||
|
||||
a.n_tokens > INT64_MAX - 32 || a.rq3 <= 0 || a.n_seqs <= 0 || a.n_seqs > INT_MAX / (a.H * 4)) {
|
||||
return gdn_path::ar;
|
||||
}
|
||||
const auto & info = ggml_cuda_info().devices[device];
|
||||
#ifdef GGML_USE_HIP
|
||||
if (!GGML_CUDA_CC_IS_RDNA3_5(info.cc) || a.n_tokens < 2048) {
|
||||
return gdn_path::ar;
|
||||
}
|
||||
return init<16, 64>(device) ? gdn_path::slice64 : gdn_path::ar;
|
||||
#elif defined(GGML_USE_MUSA)
|
||||
GGML_UNUSED_VARS(info);
|
||||
return gdn_path::ar;
|
||||
#else
|
||||
// Enable only architectures benchmarked for this schedule. MMA support alone does not imply a speedup.
|
||||
if (!ampere_mma_available(info.cc) ||
|
||||
(info.cc != GGML_CUDA_CC_AMPERE_GA10X && info.cc != GGML_CUDA_CC_ADA_LOVELACE &&
|
||||
info.cc != GGML_CUDA_CC_BLACKWELL && info.cc != GGML_CUDA_CC_DGX_SPARK)) {
|
||||
return gdn_path::ar;
|
||||
}
|
||||
// Value splitting pays off when whole heads leave at least half the SMs idle.
|
||||
if (a.H * a.n_seqs * 2 <= info.nsm) {
|
||||
// A grid that already fits in one wave benefits from a larger register budget.
|
||||
if (a.H * a.n_seqs * 4 <= info.nsm) {
|
||||
return init<16, 32, 1>(device) ? gdn_path::slice32_one_block : gdn_path::ar;
|
||||
}
|
||||
return init<16, 32, 2>(device) ? gdn_path::slice32_two_blocks : gdn_path::ar;
|
||||
}
|
||||
return init<16, 128>(device) ? gdn_path::whole_head : gdn_path::ar;
|
||||
#endif // GGML_USE_HIP
|
||||
}
|
||||
} // namespace
|
||||
using namespace ggml_cuda_gated_delta_net_mma;
|
||||
|
||||
bool ggml_cuda_gdn_mma_launch(int device, const ggml_cuda_gdn_mma_args & a, cudaStream_t stream) {
|
||||
if (!a.state_out) {
|
||||
return false;
|
||||
}
|
||||
const auto path = select_gdn_mma_path(device, a);
|
||||
if (path == gdn_path::ar) {
|
||||
return false;
|
||||
}
|
||||
#ifdef GGML_USE_HIP
|
||||
gdn_single<16, 64><<<a.H * a.n_seqs * 2, dim3(32, 8), sizeof(shared<16, 64>), stream>>>(a);
|
||||
#else
|
||||
if (path == gdn_path::slice32_two_blocks) {
|
||||
gdn_single<16, 32, 2><<<a.H * a.n_seqs * 4, dim3(32, 8), sizeof(shared<16, 32>), stream>>>(a);
|
||||
} else if (path == gdn_path::slice32_one_block) {
|
||||
gdn_single<16, 32, 1><<<a.H * a.n_seqs * 4, dim3(32, 8), sizeof(shared<16, 32>), stream>>>(a);
|
||||
} else {
|
||||
gdn_single<16, 128><<<a.H * a.n_seqs, dim3(32, 8), sizeof(shared<16, 128>), stream>>>(a);
|
||||
}
|
||||
#endif // GGML_USE_HIP
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.cuh"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct ggml_cuda_gdn_mma_args {
|
||||
const float * q;
|
||||
const float * k;
|
||||
const float * v;
|
||||
const float * g;
|
||||
const float * beta;
|
||||
const float * state;
|
||||
float * dst;
|
||||
float * state_out;
|
||||
|
||||
int64_t H;
|
||||
int64_t H_k;
|
||||
int64_t n_tokens;
|
||||
int64_t n_seqs;
|
||||
int64_t rq3;
|
||||
int64_t sq1;
|
||||
int64_t sq2;
|
||||
int64_t sq3;
|
||||
int64_t sv1;
|
||||
int64_t sv2;
|
||||
int64_t sv3;
|
||||
int64_t sb1;
|
||||
int64_t sb2;
|
||||
int64_t sb3;
|
||||
float scale;
|
||||
bool eligible;
|
||||
};
|
||||
|
||||
bool ggml_cuda_gdn_mma_launch(int device, const ggml_cuda_gdn_mma_args & args, cudaStream_t stream);
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "gated_delta_net.cuh"
|
||||
#include "gated-delta-net-mma.cuh"
|
||||
#include "ggml-cuda/common.cuh"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr int gdn_cols_per_warp = 4;
|
||||
|
||||
template <int S_v, bool KDA, bool keep_rs_t, int cols_per_warp = gdn_cols_per_warp>
|
||||
@@ -236,6 +239,44 @@ static void launch_gated_delta_net(
|
||||
}
|
||||
}
|
||||
|
||||
static ggml_cuda_gdn_mma_args ggml_cuda_gdn_mma_args_from_tensor(const ggml_tensor * dst) {
|
||||
const ggml_tensor * q = dst->src[0];
|
||||
const ggml_tensor * k = dst->src[1];
|
||||
const ggml_tensor * v = dst->src[2];
|
||||
const ggml_tensor * g = dst->src[3];
|
||||
const ggml_tensor * beta = dst->src[4];
|
||||
const ggml_tensor * state = dst->src[5];
|
||||
|
||||
const int64_t S_v = v->ne[0];
|
||||
const int64_t H = v->ne[1];
|
||||
return {
|
||||
(const float *) q->data,
|
||||
(const float *) k->data,
|
||||
(const float *) v->data,
|
||||
(const float *) g->data,
|
||||
(const float *) beta->data,
|
||||
(const float *) state->data,
|
||||
(float *) dst->data,
|
||||
nullptr,
|
||||
H,
|
||||
q->ne[1],
|
||||
v->ne[2],
|
||||
v->ne[3],
|
||||
v->ne[3] / q->ne[3],
|
||||
(int64_t) (q->nb[1] / sizeof(float)),
|
||||
(int64_t) (q->nb[2] / sizeof(float)),
|
||||
(int64_t) (q->nb[3] / sizeof(float)),
|
||||
(int64_t) (v->nb[1] / sizeof(float)),
|
||||
(int64_t) (v->nb[2] / sizeof(float)),
|
||||
(int64_t) (v->nb[3] / sizeof(float)),
|
||||
(int64_t) (beta->nb[1] / sizeof(float)),
|
||||
(int64_t) (beta->nb[2] / sizeof(float)),
|
||||
(int64_t) (beta->nb[3] / sizeof(float)),
|
||||
1.0f / sqrtf((float) S_v),
|
||||
g->ne[0] == 1 && ggml_get_op_params_i32(dst, 0) < v->ne[2] && S_v == 128 && q->ne[0] == 128,
|
||||
};
|
||||
}
|
||||
|
||||
static void ggml_cuda_op_gated_delta_net_impl(
|
||||
ggml_backend_cuda_context & ctx, ggml_tensor * dst, const ggml_cuda_gated_delta_net_fused_cache * cache) {
|
||||
ggml_tensor * src_q = dst->src[0];
|
||||
@@ -263,6 +304,8 @@ static void ggml_cuda_op_gated_delta_net_impl(
|
||||
GGML_ASSERT(neq1 == nek1);
|
||||
const int64_t neqk1 = neq1;
|
||||
|
||||
GGML_ASSERT(neq3 > 0 && nev3 >= neq3 && nev3 % neq3 == 0);
|
||||
GGML_ASSERT(nek3 == neq3);
|
||||
const int64_t rq3 = nev3 / neq3;
|
||||
|
||||
const float * q_d = (const float *) src_q->data;
|
||||
@@ -310,6 +353,13 @@ static void ggml_cuda_op_gated_delta_net_impl(
|
||||
state_slot_stride = cache->slot_stride;
|
||||
}
|
||||
|
||||
{
|
||||
ggml_cuda_gdn_mma_args args = ggml_cuda_gdn_mma_args_from_tensor(dst);
|
||||
args.state_out = state_d;
|
||||
if (ggml_cuda_gdn_mma_launch(ctx.device, args, stream)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (kda) {
|
||||
if (keep_rs) {
|
||||
launch_gated_delta_net<true, true>(q_d, k_d, v_d, g_d, b_d, s_d, dst_d, state_d,
|
||||
|
||||
+149
-24
@@ -457,6 +457,7 @@ const ggml_cuda_device_info & ggml_cuda_info() {
|
||||
struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
static const int MAX_BUFFERS = 256;
|
||||
|
||||
ggml_backend_cuda_context & ctx;
|
||||
int device;
|
||||
struct ggml_cuda_buffer {
|
||||
void * ptr = nullptr;
|
||||
@@ -466,7 +467,8 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
ggml_cuda_buffer buffer_pool[MAX_BUFFERS] = {};
|
||||
size_t pool_size = 0;
|
||||
|
||||
explicit ggml_cuda_pool_leg(int device) :
|
||||
ggml_cuda_pool_leg(ggml_backend_cuda_context & ctx, int device) :
|
||||
ctx(ctx),
|
||||
device(device) {
|
||||
}
|
||||
|
||||
@@ -488,6 +490,20 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
}
|
||||
}
|
||||
|
||||
// captured CUDA graphs can still use freed buffers, so they must be captured again
|
||||
void invalidate_cuda_graphs() {
|
||||
#ifdef USE_CUDA_GRAPH
|
||||
int n_captured = 0;
|
||||
for (const auto & [key, graph] : ctx.cuda_graphs) {
|
||||
n_captured += graph->instance != nullptr;
|
||||
}
|
||||
if (n_captured > 0) {
|
||||
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: memory freed, %d captured CUDA graphs must be captured again\n", device, n_captured);
|
||||
}
|
||||
ctx.cuda_graphs.clear();
|
||||
#endif // USE_CUDA_GRAPH
|
||||
}
|
||||
|
||||
void * alloc(size_t size, size_t * actual_size) override {
|
||||
#ifdef DEBUG_CUDA_MALLOC
|
||||
int nnz = 0;
|
||||
@@ -538,6 +554,7 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
device, look_ahead_size/1024.0/1024.0, cached_bytes/1024.0/1024.0);
|
||||
CUDA_CHECK(cudaDeviceSynchronize());
|
||||
clear_pool();
|
||||
invalidate_cuda_graphs();
|
||||
err = ggml_cuda_device_malloc(&ptr, look_ahead_size, device);
|
||||
if (err == cudaSuccess) {
|
||||
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: retry succeeded\n", device);
|
||||
@@ -566,6 +583,7 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
|
||||
ggml_cuda_set_device(device);
|
||||
CUDA_CHECK(cudaFree(ptr));
|
||||
pool_size -= size;
|
||||
invalidate_cuda_graphs();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -720,14 +738,15 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
|
||||
};
|
||||
#endif // defined(GGML_USE_VMM)
|
||||
|
||||
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(int device,
|
||||
[[maybe_unused]] int stream_no) {
|
||||
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(ggml_backend_cuda_context & ctx,
|
||||
int device,
|
||||
[[maybe_unused]] int stream_no) {
|
||||
#if defined(GGML_USE_VMM)
|
||||
if (ggml_cuda_info().devices[device].vmm) {
|
||||
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_vmm(device));
|
||||
}
|
||||
#endif // defined(GGML_USE_VMM)
|
||||
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_leg(device));
|
||||
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_leg(ctx, device));
|
||||
}
|
||||
|
||||
// destroying a cuBLAS handle while a graph is being captured in a different thread can result in a CUDA error
|
||||
@@ -1348,6 +1367,12 @@ static ggml_backend_buffer_t ggml_backend_cuda_host_buffer_type_alloc_buffer(ggm
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static size_t ggml_backend_cuda_host_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
|
||||
GGML_UNUSED(buft);
|
||||
// very large pinned allocations can fail (e.g. on Windows) and fall back to pageable memory, smaller ones succeed
|
||||
return 4ull*1024*1024*1024;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_type_t ggml_backend_cuda_host_buffer_type() {
|
||||
static struct ggml_backend_buffer_type ggml_backend_cuda_buffer_type_host = {
|
||||
/* .iface = */ {
|
||||
@@ -1355,7 +1380,7 @@ ggml_backend_buffer_type_t ggml_backend_cuda_host_buffer_type() {
|
||||
/* .alloc_buffer = */ ggml_backend_cuda_host_buffer_type_alloc_buffer,
|
||||
/* .alloc_buffer_n = */ NULL,
|
||||
/* .get_alignment = */ ggml_backend_cpu_buffer_type()->iface.get_alignment,
|
||||
/* .get_max_size = */ NULL, // defaults to SIZE_MAX
|
||||
/* .get_max_size = */ ggml_backend_cuda_host_buffer_type_get_max_size,
|
||||
/* .get_alloc_size = */ ggml_backend_cpu_buffer_type()->iface.get_alloc_size,
|
||||
/* .get_alloc_size_n = */ NULL,
|
||||
/* .is_host = */ ggml_backend_cpu_buffer_type()->iface.is_host,
|
||||
@@ -2008,6 +2033,8 @@ static bool ggml_cuda_mul_mat_id_needs_sync(const ggml_tensor * dst, const int c
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
const int warp_size = ggml_cuda_info().devices[ggml_cuda_get_device()].warp_size;
|
||||
|
||||
if (src1->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
|
||||
return true;
|
||||
}
|
||||
@@ -2026,7 +2053,7 @@ static bool ggml_cuda_mul_mat_id_needs_sync(const ggml_tensor * dst, const int c
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ggml_cuda_should_use_mmf(src0->type, cc, WARP_SIZE, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
|
||||
if (ggml_cuda_should_use_mmf(src0->type, cc, warp_size, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2044,6 +2071,7 @@ static void ggml_cuda_mul_mat_id(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
GGML_TENSOR_BINARY_OP_LOCALS
|
||||
|
||||
const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
|
||||
const int warp_size = ggml_cuda_info().devices[ggml_cuda_get_device()].warp_size;
|
||||
|
||||
// [TAG_MUL_MAT_ID_CUDA_GRAPHS]
|
||||
if (src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
@@ -2068,7 +2096,7 @@ static void ggml_cuda_mul_mat_id(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
return;
|
||||
}
|
||||
|
||||
if (ggml_cuda_should_use_mmf(src0->type, cc, WARP_SIZE, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
|
||||
if (ggml_cuda_should_use_mmf(src0->type, cc, warp_size, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
|
||||
ggml_cuda_mul_mat_f(ctx, src0, src1, ids, dst);
|
||||
return;
|
||||
}
|
||||
@@ -3023,6 +3051,65 @@ static int ggml_cuda_try_ssm_scan_cache_fusion(
|
||||
return skip;
|
||||
}
|
||||
|
||||
// expert group selection from build_moe_ffn (n_expert_groups > 1)
|
||||
// src0/src1 index into this table, -1 is the (biased) probs, -2 is not checked
|
||||
static const struct {
|
||||
ggml_op op;
|
||||
int src0;
|
||||
int src1;
|
||||
} topk_moe_group_pattern[] = {
|
||||
{ GGML_OP_RESHAPE, -1, -2 }, // 0: [n_exp_per_group, n_expert_groups, n_tokens]
|
||||
{ GGML_OP_RESHAPE, 0, -2 }, // 1: [1, n_exp_per_group, n_expert_groups, n_tokens]
|
||||
{ GGML_OP_ARGSORT, 0, -2 }, // 2
|
||||
{ GGML_OP_VIEW, 2, -2 }, // 3: top-2 per group
|
||||
{ GGML_OP_GET_ROWS, 1, 3 }, // 4
|
||||
{ GGML_OP_RESHAPE, 4, -2 }, // 5
|
||||
{ GGML_OP_SUM_ROWS, 5, -2 }, // 6: group scores
|
||||
{ GGML_OP_RESHAPE, 6, -2 }, // 7
|
||||
{ GGML_OP_ARGSORT, 7, -2 }, // 8
|
||||
{ GGML_OP_VIEW, 8, -2 }, // 9: top n_group_used groups
|
||||
{ GGML_OP_GET_ROWS, 0, 9 }, // 10
|
||||
{ GGML_OP_FILL, 0, -2 }, // 11
|
||||
{ GGML_OP_SET_ROWS, 10, 9 }, // 12: unused groups -> -inf
|
||||
{ GGML_OP_RESHAPE, 12, -2 }, // 13: [n_expert, n_tokens]
|
||||
};
|
||||
|
||||
static bool ggml_cuda_topk_moe_group_fusion(const struct ggml_cgraph * cgraph, int node_idx, const ggml_tensor * selection, ggml_cuda_topk_moe_args & args) {
|
||||
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
GGML_UNUSED_VARS(cgraph, node_idx, selection, args);
|
||||
return false;
|
||||
#else
|
||||
const int n_ops = (int) std::size(topk_moe_group_pattern);
|
||||
if (node_idx + n_ops > cgraph->n_nodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_tensor ** nodes = cgraph->nodes + node_idx;
|
||||
auto src_ok = [&](const ggml_tensor * src, int idx) {
|
||||
return idx == -2 || src == (idx == -1 ? selection : nodes[idx]);
|
||||
};
|
||||
|
||||
for (int i = 0; i < n_ops; i++) {
|
||||
const auto & p = topk_moe_group_pattern[i];
|
||||
if (nodes[i]->op != p.op || !src_ok(nodes[i]->src[0], p.src0) || !src_ok(nodes[i]->src[1], p.src1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const int n_groups = nodes[0]->ne[1];
|
||||
const int n_group_used = nodes[9]->ne[0];
|
||||
|
||||
if (nodes[3]->ne[0] != 2 || nodes[12]->src[2] != nodes[11] || ggml_get_op_params_f32(nodes[11], 0) != -INFINITY ||
|
||||
n_groups > WARP_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.n_groups = n_groups;
|
||||
args.n_group_used = n_group_used;
|
||||
return true;
|
||||
#endif // defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
}
|
||||
|
||||
static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int node_idx, ggml_cuda_topk_moe_args & args) {
|
||||
args.sigmoid = false;
|
||||
args.sqrt_softplus = false;
|
||||
@@ -3030,6 +3117,8 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
args.delayed_softmax = false;
|
||||
args.prob_bias = false;
|
||||
args.norm = false;
|
||||
args.n_groups = 0;
|
||||
args.n_group_used = 0;
|
||||
|
||||
const int n_nodes = cgraph->n_nodes;
|
||||
ggml_tensor ** nodes = cgraph->nodes;
|
||||
@@ -3072,18 +3161,20 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
}
|
||||
|
||||
// src of bias add is the unreshaped probs (-2 instead of -1)
|
||||
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == nodes[node_idx - 2]) {
|
||||
const ggml_tensor * selection = nodes[node_idx - 2];
|
||||
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == selection) {
|
||||
args.prob_bias = true;
|
||||
selection = nodes[node_idx];
|
||||
node_idx++;
|
||||
}
|
||||
// RESHAPE/ADD -> ARGSORT
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT) {
|
||||
return false;
|
||||
|
||||
if (ggml_cuda_topk_moe_group_fusion(cgraph, node_idx, selection, args)) {
|
||||
node_idx += (int) std::size(topk_moe_group_pattern);
|
||||
selection = nodes[node_idx - 1];
|
||||
}
|
||||
|
||||
if (args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
|
||||
return false;
|
||||
} else if (!args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 2]) {
|
||||
// RESHAPE/ADD/group mask -> ARGSORT
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT || nodes[node_idx]->src[0] != selection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3094,6 +3185,11 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// kept groups must hold all used experts
|
||||
if (args.n_groups > 0 && nodes[node_idx]->ne[0] > args.n_group_used * (probs_reshaped->ne[1] / args.n_groups)) {
|
||||
return false;
|
||||
}
|
||||
node_idx++;
|
||||
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_GET_ROWS) {
|
||||
@@ -3476,6 +3572,26 @@ static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph,
|
||||
}
|
||||
}
|
||||
|
||||
// scale_out*sigmoid(scale_in*post) as the hc_post weights (qwen4exp hc_combine)
|
||||
std::initializer_list<enum ggml_op> hc_post_gate_ops = { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST };
|
||||
|
||||
if (is_equal(hc_post_gate_ops, ops) && ggml_can_fuse_subgraph(cgraph, node_idx, ops, { node_idx + 3 })) {
|
||||
const ggml_tensor * scale_in = cgraph->nodes[node_idx];
|
||||
const ggml_tensor * sigmoid = cgraph->nodes[node_idx + 1];
|
||||
const ggml_tensor * scale_out = cgraph->nodes[node_idx + 2];
|
||||
|
||||
// the chain must feed hc_post->src[2]; a bias on either scale is not handled
|
||||
if (ggml_check_edges(cgraph, node_idx, {{1, 0, 0}, {2, 0, 1}, {3, 2, 2}}) &&
|
||||
ggml_get_unary_op(sigmoid) == GGML_UNARY_OP_SIGMOID &&
|
||||
ggml_get_op_params_f32(scale_in, 1) == 0.0f && ggml_get_op_params_f32(scale_out, 1) == 0.0f &&
|
||||
scale_in->src[0]->type == GGML_TYPE_F32 && scale_out->type == GGML_TYPE_F32 &&
|
||||
ggml_are_same_shape(scale_in->src[0], scale_out)) {
|
||||
int out_nodes[] = { node_idx + 3 };
|
||||
return ggml_cuda_check_fusion_memory_ranges(cgraph, node_idx, (int)ops.size(), out_nodes, 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ggml_can_fuse(cgraph, node_idx, ops)) {
|
||||
return false;
|
||||
}
|
||||
@@ -3748,17 +3864,18 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
|
||||
} else {
|
||||
ops.insert(ops.end(), { GGML_OP_SOFT_MAX });
|
||||
}
|
||||
const int i_probs = i + (int) ops.size() - 1; // last node of the gating activation
|
||||
|
||||
ops.push_back(GGML_OP_RESHAPE);
|
||||
if (args.prob_bias) {
|
||||
bias = cgraph->nodes[i_probs + 2]->src[1];
|
||||
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ADD, GGML_OP_ARGSORT, GGML_OP_VIEW,
|
||||
GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i_probs + 4;
|
||||
} else {
|
||||
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i_probs + 3;
|
||||
ops.push_back(GGML_OP_ADD);
|
||||
bias = cgraph->nodes[i + ops.size() - 1]->src[1];
|
||||
}
|
||||
if (args.n_groups > 0) {
|
||||
for (const auto & p : topk_moe_group_pattern) {
|
||||
ops.push_back(p.op);
|
||||
}
|
||||
}
|
||||
ops.insert(ops.end(), { GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i + ops.size() - 2;
|
||||
ids = cgraph->nodes[out_nodes[0]];
|
||||
|
||||
if (args.norm) {
|
||||
@@ -4438,6 +4555,11 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST }, { GGML_UNARY_OP_SIGMOID })) {
|
||||
ggml_cuda_op_dsv4_hc_post(*cuda_ctx, cgraph->nodes[i + 3], node, cgraph->nodes[i + 2]);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE }, { GGML_UNARY_OP_TANH })) {
|
||||
ggml_cuda_op_softcap(*cuda_ctx, cgraph->nodes[i + 2], node);
|
||||
return 2;
|
||||
@@ -5474,6 +5596,9 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
(ggml_get_glu_op(op) == GGML_GLU_OP_SWIGLU_OAI || ggml_get_glu_op(op) == GGML_GLU_OP_SWIGLU_CLAMP)) {
|
||||
return false;
|
||||
}
|
||||
if (op->src[0]->type == GGML_TYPE_F16 && ggml_get_glu_op(op) == GGML_GLU_OP_SWIGLU_OAI) {
|
||||
return false;
|
||||
}
|
||||
return ggml_is_contiguous_1(op->src[0]);
|
||||
default:
|
||||
return false;
|
||||
@@ -5819,7 +5944,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
//return ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous_rows(op->src[1]);
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
case GGML_OP_SUM:
|
||||
return ggml_is_contiguous_rows(op->src[0]);
|
||||
return ggml_is_contiguously_allocated(op->src[0]);
|
||||
case GGML_OP_TOP_K:
|
||||
return op->src[0]->ne[0] <= INT_MAX;
|
||||
case GGML_OP_ARGSORT:
|
||||
|
||||
+23
-11
@@ -949,16 +949,22 @@ namespace ggml_cuda_mma {
|
||||
static __device__ __forceinline__ void load_ldmatrix_swizzled(
|
||||
tile<I, J, T, dl> & t, const T * __restrict__ xs0, const int offset) {
|
||||
#if defined(TURING_MMA_AVAILABLE)
|
||||
static_assert(I == 16, "bad tile width");
|
||||
static_assert(I == 8 || I == 16, "bad tile width");
|
||||
static_assert(J == 8, "bad tile height");
|
||||
const int i = threadIdx.x % t.I;
|
||||
const int j = (threadIdx.x / t.I) * (t.J / 2);
|
||||
const int j = ((threadIdx.x / t.I) * (t.J / 2)) % t.J;
|
||||
int offset_ij = offset + i * stride + j;
|
||||
offset_ij = swizzle<stride, T>(offset_ij, i);
|
||||
int * xi = (int *) t.x;
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x4.b16 {%0, %1, %2, %3}, [%4];"
|
||||
: "=r"(xi[0]), "=r"(xi[1]), "=r"(xi[2]), "=r"(xi[3])
|
||||
: "l"(xs0 + offset_ij));
|
||||
if constexpr (I == 16) {
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x4.b16 {%0, %1, %2, %3}, [%4];"
|
||||
: "=r"(xi[0]), "=r"(xi[1]), "=r"(xi[2]), "=r"(xi[3])
|
||||
: "l"(xs0 + offset_ij));
|
||||
} else {
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x2.b16 {%0, %1}, [%2];"
|
||||
: "=r"(xi[0]), "=r"(xi[1])
|
||||
: "l"(xs0 + offset_ij));
|
||||
}
|
||||
#elif defined(VOLTA_MMA_AVAILABLE)
|
||||
#pragma unroll
|
||||
for (int o = 0; o < t.ne; o += 4) {
|
||||
@@ -1011,16 +1017,22 @@ namespace ggml_cuda_mma {
|
||||
static __device__ __forceinline__ void load_ldmatrix_trans_swizzled(
|
||||
tile<I, 8, T, dl> & t, const T * __restrict__ xs0, const int offset) {
|
||||
#if defined(TURING_MMA_AVAILABLE)
|
||||
static_assert(I == 16, "bad tile width");
|
||||
static_assert(I == 8 || I == 16, "bad tile width");
|
||||
static_assert(dl == DATA_LAYOUT_I_MAJOR, "bad data layout");
|
||||
const int i = threadIdx.x % t.I;
|
||||
const int j = (threadIdx.x / t.I) * (t.J / 2);
|
||||
const int i = threadIdx.x % 16;
|
||||
const int j = I == 16 ? (threadIdx.x / 16) * (t.J / 2) : 0;
|
||||
int offset_ij = offset + i * stride + j;
|
||||
offset_ij = swizzle<stride, T>(offset_ij, i);
|
||||
int * xi = (int *) t.x;
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x4.trans.b16 {%0, %1, %2, %3}, [%4];"
|
||||
: "=r"(xi[0]), "=r"(xi[2]), "=r"(xi[1]), "=r"(xi[3])
|
||||
: "l"(xs0 + offset_ij));
|
||||
if constexpr (I == 16) {
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x4.trans.b16 {%0, %1, %2, %3}, [%4];"
|
||||
: "=r"(xi[0]), "=r"(xi[2]), "=r"(xi[1]), "=r"(xi[3])
|
||||
: "l"(xs0 + offset_ij));
|
||||
} else {
|
||||
asm volatile("ldmatrix.sync.aligned.m8n8.x2.trans.b16 {%0, %1}, [%2];"
|
||||
: "=r"(xi[0]), "=r"(xi[1])
|
||||
: "l"(xs0 + offset_ij));
|
||||
}
|
||||
#elif defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
static_assert(dl == DATA_LAYOUT_I_MAJOR || dl == DATA_LAYOUT_I_MAJOR_MIRRORED, "bad data layout");
|
||||
if constexpr (I == 32) {
|
||||
|
||||
@@ -46,6 +46,25 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
const int q = qxi[j];
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
const uint32_t q_bits = (uint16_t) q;
|
||||
|
||||
// Expand each group of four Q1_0 bits into byte selectors for v_perm_b32.
|
||||
const uint32_t q0_bits = q_bits >> 0;
|
||||
const uint32_t q1_bits = q_bits >> 4;
|
||||
const uint32_t q2_bits = q_bits >> 8;
|
||||
const uint32_t q3_bits = q_bits >> 12;
|
||||
|
||||
const uint32_t q0_indices = (q0_bits & 0x01) | ((q0_bits & 0x02) << 7) | ((q0_bits & 0x04) << 14) | ((q0_bits & 0x08) << 21);
|
||||
const uint32_t q1_indices = (q1_bits & 0x01) | ((q1_bits & 0x02) << 7) | ((q1_bits & 0x04) << 14) | ((q1_bits & 0x08) << 21);
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
|
||||
@@ -59,6 +78,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const int v1 = __byte_perm(s0, s1, 0x7632);
|
||||
const int v2 = __byte_perm(s2, s3, 0x5410);
|
||||
const int v3 = __byte_perm(s2, s3, 0x7632);
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_qs[i*sram_stride + dst_offset + j*4+0] = v0;
|
||||
|
||||
@@ -1441,10 +1441,12 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
const int ntiles_dst = ntx * nty * ntzw;
|
||||
const int tiles_nwaves = (ntiles_dst + nsm - 1) / nsm;
|
||||
const int tiles_efficiency_percent = 100 * ntiles_dst / (nsm*tiles_nwaves);
|
||||
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : nsm, 1, 1);
|
||||
|
||||
GGML_ASSERT(ntiles_dst * blocks_per_ne00_fd.z < (1 << 30)); // Assert that variable kbc will not overflow.
|
||||
|
||||
const int nwork = ntiles_dst * ((args.ncols_x + config.K_vram - 1) / config.K_vram); // Maximum number of CUDA blocks that can do useful work.
|
||||
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : std::min(nsm, nwork), 1, 1);
|
||||
|
||||
const bool fixup_needed = ntiles_dst % block_nums_stream_k.x != 0;
|
||||
|
||||
ggml_cuda_pool & pool = ctx.pool(id);
|
||||
|
||||
@@ -468,10 +468,12 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
|
||||
} else if (table_id == MMVQ_PARAMETERS_GCN) {
|
||||
switch (ncols_dst) {
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
case 3:
|
||||
return type == GGML_TYPE_Q3_K ? 1 : 2;
|
||||
case 4:
|
||||
return 2;
|
||||
return type == GGML_TYPE_Q6_K? 1 : 2;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
@@ -576,11 +578,31 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
|
||||
return 1;
|
||||
}
|
||||
|
||||
static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
|
||||
// Low bit types load few weight bytes per thread, a second row per block shares the q8_1 loads and adds memory parallelism.
|
||||
static constexpr __host__ __device__ bool mmvq_two_rows_ncols1(ggml_type type) {
|
||||
switch (type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_IQ1_S:
|
||||
case GGML_TYPE_IQ1_M:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr __host__ __device__ int calc_rows_per_block(ggml_type type, int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
|
||||
if (table_id == MMVQ_PARAMETERS_GENERIC || table_id == MMVQ_PARAMETERS_GCN || table_id == MMVQ_PARAMETERS_TURING || table_id == MMVQ_PARAMETERS_GB10) {
|
||||
switch (ncols_dst) {
|
||||
case 1:
|
||||
return small_k ? nwarps : 1;
|
||||
if (small_k) {
|
||||
return nwarps;
|
||||
}
|
||||
if (table_id == MMVQ_PARAMETERS_GB10) {
|
||||
return type == GGML_TYPE_IQ2_S || type == GGML_TYPE_IQ3_S ? 2 : 1;
|
||||
}
|
||||
return table_id == MMVQ_PARAMETERS_GENERIC && mmvq_two_rows_ncols1(type) ? 2 : 1;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
@@ -615,7 +637,7 @@ static __global__ void mul_mat_vec_q(
|
||||
constexpr int vdr = get_vdr_mmvq(type);
|
||||
constexpr mmvq_parameter_table_id table_id = get_device_table_id();
|
||||
constexpr int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
|
||||
constexpr int rows_per_cuda_block = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
|
||||
constexpr int rows_per_cuda_block = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
|
||||
constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type);
|
||||
@@ -727,7 +749,8 @@ static __global__ void mul_mat_vec_q(
|
||||
#if __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
|
||||
// start the next iterations' weight loads early
|
||||
if constexpr (mmvq_should_prefetch(type)) {
|
||||
constexpr int pf_dist = 2; // loop iterations, not blocks
|
||||
// loop iterations, not blocks; q3_K and q6_K keep the distance in blocks they had with vdr 1
|
||||
constexpr int pf_dist = type == GGML_TYPE_Q3_K || type == GGML_TYPE_Q6_K ? 1 : 2;
|
||||
const int kbx_pf = kbx + pf_dist*blocks_per_iter;
|
||||
if (kbx_pf < blocks_per_row_x) {
|
||||
#pragma unroll
|
||||
@@ -1013,7 +1036,7 @@ static std::pair<dim3, dim3> calc_launch_params(
|
||||
const int ncols_dst, const int nrows_x, const int nchannels_dst, const int nsamples_or_ntokens,
|
||||
const int warp_size, const mmvq_parameter_table_id table_id, const bool small_k = false, const bool halve_iters = false) {
|
||||
const int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
|
||||
const int rpb = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
|
||||
const int rpb = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
|
||||
const int64_t nblocks = (nrows_x + rpb - 1) / rpb;
|
||||
const dim3 block_nums(nblocks, nchannels_dst, nsamples_or_ntokens);
|
||||
const dim3 block_dims(warp_size, nwarps, 1);
|
||||
|
||||
+171
-104
@@ -81,7 +81,7 @@ static __global__ void group_norm_f32(const float * x, float * dst, const int gr
|
||||
}
|
||||
}
|
||||
|
||||
template <int block_size, bool do_multiply = false, bool do_add = false, bool do_scale = false>
|
||||
template <int block_size, int vpt, bool do_vec, bool do_multiply = false, bool do_add = false, bool do_scale = false>
|
||||
static __global__ void rms_norm_f32(const float * x,
|
||||
float * dst,
|
||||
const int ncols,
|
||||
@@ -142,31 +142,82 @@ static __global__ void rms_norm_f32(const float * x,
|
||||
addc = add + add_sample * add_stride_sample + add_channel * add_stride_channel + add_row * add_stride_row;
|
||||
}
|
||||
|
||||
float tmp = 0.0f; // partial sum for thread in warp
|
||||
if constexpr (do_vec) {
|
||||
const int ncols4 = ncols / 4;
|
||||
const float4 * x4 = (const float4 *) xc;
|
||||
const float4 * mul4 = (const float4 *) mulc;
|
||||
const float4 * add4 = (const float4 *) addc;
|
||||
float4 * dst4 = (float4 *) dstc;
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
const float xi = xc[col];
|
||||
tmp += xi * xi;
|
||||
}
|
||||
float4 xs[vpt];
|
||||
float tmp = 0.0f;
|
||||
|
||||
// sum up partial sums
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < vpt; k++) {
|
||||
const int i = tid + k*block_size;
|
||||
if (i < ncols4) {
|
||||
xs[k] = x4[i];
|
||||
tmp += xs[k].x*xs[k].x + xs[k].y*xs[k].y + xs[k].z*xs[k].z + xs[k].w*xs[k].w;
|
||||
}
|
||||
}
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
const float scale = rsqrtf(tmp / ncols + eps);
|
||||
|
||||
const float mean = tmp / ncols;
|
||||
const float scale = rsqrtf(mean + eps);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < vpt; k++) {
|
||||
const int i = tid + k*block_size;
|
||||
if (i < ncols4) {
|
||||
float4 r = make_float4(scale*xs[k].x, scale*xs[k].y, scale*xs[k].z, scale*xs[k].w);
|
||||
if constexpr (do_multiply) {
|
||||
const float4 m = mul4[i];
|
||||
r.x *= m.x;
|
||||
r.y *= m.y;
|
||||
r.z *= m.z;
|
||||
r.w *= m.w;
|
||||
}
|
||||
if constexpr (do_add) {
|
||||
const float4 a = add4[i];
|
||||
r.x += a.x;
|
||||
r.y += a.y;
|
||||
r.z += a.z;
|
||||
r.w += a.w;
|
||||
}
|
||||
if constexpr (do_scale) {
|
||||
r.x *= scale_out;
|
||||
r.y *= scale_out;
|
||||
r.z *= scale_out;
|
||||
r.w *= scale_out;
|
||||
}
|
||||
dst4[i] = r;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
float tmp = 0.0f; // partial sum for thread in warp
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
if constexpr (do_multiply && do_add) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
const int add_col = fastmodulo(col, add_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col] + addc[add_col];
|
||||
} else if constexpr (do_multiply) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col];
|
||||
} else if constexpr (do_scale) {
|
||||
dstc[col] = scale_out * (scale * xc[col]);
|
||||
} else {
|
||||
dstc[col] = scale * xc[col];
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
const float xi = xc[col];
|
||||
tmp += xi * xi;
|
||||
}
|
||||
|
||||
// sum up partial sums
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
|
||||
const float mean = tmp / ncols;
|
||||
const float scale = rsqrtf(mean + eps);
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
if constexpr (do_multiply && do_add) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
const int add_col = fastmodulo(col, add_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col] + addc[add_col];
|
||||
} else if constexpr (do_multiply) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col];
|
||||
} else if constexpr (do_scale) {
|
||||
dstc[col] = scale_out * (scale * xc[col]);
|
||||
} else {
|
||||
dstc[col] = scale * xc[col];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,34 +383,89 @@ static void group_norm_f32_cuda(
|
||||
}
|
||||
}
|
||||
|
||||
#define RMS_NORM_LAUNCH(BLOCK_SIZE, VPT, VEC) \
|
||||
do { \
|
||||
const dim3 block_dims(BLOCK_SIZE, 1, 1); \
|
||||
const ggml_cuda_kernel_launch_params launch_params{ blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float) : 0, stream }; \
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<BLOCK_SIZE, VPT, VEC, do_multiply, do_add, do_scale>, launch_params, \
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, \
|
||||
mul, mul_stride_row, mul_stride_channel, mul_stride_sample, \
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, \
|
||||
add, add_stride_row, add_stride_channel, add_stride_sample, \
|
||||
add_ncols_packed, add_nrows_packed, add_nchannels_packed, add_nsamples_packed, scale_out); \
|
||||
} while (0)
|
||||
|
||||
template <bool do_multiply, bool do_add, bool do_scale>
|
||||
static void rms_norm_f32_dispatch(
|
||||
const dim3 blocks_num,
|
||||
const ggml_tensor * x_t, float * dst, const int ncols, const int nchannels, const int nsamples,
|
||||
const int64_t stride_row, const int64_t stride_channel, const int64_t stride_sample, const float eps,
|
||||
const ggml_tensor * mul_t, const int64_t mul_stride_row, const int64_t mul_stride_channel, const int64_t mul_stride_sample,
|
||||
const uint3 mul_ncols_packed, const uint3 mul_nrows_packed, const uint3 mul_nchannels_packed, const uint3 mul_nsamples_packed,
|
||||
const ggml_tensor * add_t, const int64_t add_stride_row, const int64_t add_stride_channel, const int64_t add_stride_sample,
|
||||
const uint3 add_ncols_packed, const uint3 add_nrows_packed, const uint3 add_nchannels_packed, const uint3 add_nsamples_packed,
|
||||
cudaStream_t stream, const float scale_out = 1.0f) {
|
||||
static_assert(!do_add || do_multiply, "fusing add is not supported without multiplying");
|
||||
static_assert(!do_scale || !do_multiply, "fusing scale is not supported with multiplying");
|
||||
|
||||
const float * x = (const float *) x_t->data;
|
||||
const float * mul = mul_t ? (const float *) mul_t->data : nullptr;
|
||||
const float * add = add_t ? (const float *) add_t->data : nullptr;
|
||||
|
||||
// dst is written at row * ncols, i.e. always at a multiple of ncols, so only its base needs checking here
|
||||
bool vec = ncols % 4 == 0 && ncols > 0 && ((uintptr_t) dst & 15) == 0;
|
||||
vec = vec && ggml_cuda_is_aligned(x_t, 16);
|
||||
if constexpr (do_multiply) {
|
||||
vec = vec && mul_ncols_packed.z == (uint32_t) ncols
|
||||
&& ggml_cuda_is_aligned(mul_t, 16);
|
||||
}
|
||||
if constexpr (do_add) {
|
||||
vec = vec && add_ncols_packed.z == (uint32_t) ncols
|
||||
&& ggml_cuda_is_aligned(add_t, 16);
|
||||
}
|
||||
|
||||
const int n4 = ncols / 4;
|
||||
if (vec) {
|
||||
if (n4 <= 32) {
|
||||
RMS_NORM_LAUNCH(32, 1, true);
|
||||
} else if (n4 <= 128) {
|
||||
RMS_NORM_LAUNCH(128, 1, true);
|
||||
} else if (n4 <= 256) {
|
||||
RMS_NORM_LAUNCH(256, 1, true);
|
||||
} else if (n4 <= 512) {
|
||||
RMS_NORM_LAUNCH(512, 1, true);
|
||||
} else if (n4 <= 1024) {
|
||||
RMS_NORM_LAUNCH(1024, 1, true);
|
||||
} else if (n4 <= 4096) {
|
||||
RMS_NORM_LAUNCH(1024, 4, true);
|
||||
} else {
|
||||
RMS_NORM_LAUNCH(1024, 1, false);
|
||||
}
|
||||
} else if (ncols < 1024) {
|
||||
RMS_NORM_LAUNCH(256, 1, false);
|
||||
} else {
|
||||
RMS_NORM_LAUNCH(1024, 1, false);
|
||||
}
|
||||
}
|
||||
#undef RMS_NORM_LAUNCH
|
||||
|
||||
template <bool do_scale = false>
|
||||
static void rms_norm_f32_cuda(
|
||||
const float * x, float * dst, const int ncols, const int nrows, const int nchannels, const int nsamples,
|
||||
const ggml_tensor * x_t, float * dst, const int ncols, const int nrows, const int nchannels, const int nsamples,
|
||||
const int64_t stride_row, const int64_t stride_channel, const int64_t stride_sample, const float eps, cudaStream_t stream,
|
||||
const float scale_out = 1.0f) {
|
||||
const dim3 blocks_num(nrows, MIN(nchannels, UINT16_MAX), MIN(nsamples, UINT16_MAX));
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = {blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, false, false, do_scale>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
rms_norm_f32_dispatch</*do_multiply*/false, /*do_add*/false, do_scale>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0),
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), scale_out);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, false, false, do_scale>, launch_params, x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0),
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), scale_out);
|
||||
}
|
||||
stream, scale_out);
|
||||
}
|
||||
|
||||
static void rms_norm_mul_f32_cuda(const float * x,
|
||||
const float * mul,
|
||||
const float * add,
|
||||
float * dst,
|
||||
static void rms_norm_mul_f32_cuda(const ggml_tensor * x_t,
|
||||
const ggml_tensor * mul_t,
|
||||
const ggml_tensor * add_t,
|
||||
float * dst,
|
||||
const int ncols,
|
||||
const int nrows,
|
||||
const int nchannels,
|
||||
@@ -384,59 +490,33 @@ static void rms_norm_mul_f32_cuda(const float * x,
|
||||
const float eps,
|
||||
cudaStream_t stream) {
|
||||
const dim3 blocks_num(nrows, MIN(nchannels, UINT16_MAX), MIN(nsamples, UINT16_MAX));
|
||||
if (mul == nullptr) {
|
||||
rms_norm_f32_cuda(x, dst, ncols, nrows, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, stream);
|
||||
if (mul_t == nullptr) {
|
||||
rms_norm_f32_cuda(x_t, dst, ncols, nrows, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, stream);
|
||||
return;
|
||||
}
|
||||
if (add == nullptr) {
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), 1.0f);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), 1.0f);
|
||||
}
|
||||
} else {
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
const uint3 zero_packed = make_uint3(0, 0, 0);
|
||||
|
||||
if (add_t == nullptr) {
|
||||
rms_norm_f32_dispatch</*do_multiply*/true, /*do_add*/false, /*do_scale*/false>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
mul_t, mul_stride_row, mul_stride_channel, mul_stride_sample,
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
nullptr, 0, 0, 0, zero_packed, zero_packed, zero_packed, zero_packed, stream);
|
||||
} else {
|
||||
const uint3 add_ncols_packed = init_fastdiv_values(add_ncols);
|
||||
const uint3 add_nrows_packed = init_fastdiv_values(add_nrows);
|
||||
const uint3 add_nchannels_packed = init_fastdiv_values(add_nchannels);
|
||||
const uint3 add_nsamples_packed = init_fastdiv_values(add_nsamples);
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims,block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, true, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, add,
|
||||
add_stride_row, add_stride_channel, add_stride_sample, add_ncols_packed, add_nrows_packed,
|
||||
add_nchannels_packed, add_nsamples_packed, 1.0f);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, true, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, add,
|
||||
add_stride_row, add_stride_channel, add_stride_sample, add_ncols_packed, add_nrows_packed,
|
||||
add_nchannels_packed, add_nsamples_packed, 1.0f);
|
||||
}
|
||||
rms_norm_f32_dispatch</*do_multiply*/true, /*do_add*/true, /*do_scale*/false>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
mul_t, mul_stride_row, mul_stride_channel, mul_stride_sample,
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
add_t, add_stride_row, add_stride_channel, add_stride_sample,
|
||||
add_ncols_packed, add_nrows_packed, add_nchannels_packed, add_nsamples_packed, stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +590,6 @@ void ggml_cuda_op_group_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst)
|
||||
|
||||
void ggml_cuda_op_rms_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const float * src0_d = (const float *) src0->data;
|
||||
float * dst_d = (float *) dst->data;
|
||||
cudaStream_t stream = ctx.stream();
|
||||
|
||||
@@ -529,12 +608,11 @@ void ggml_cuda_op_rms_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const int64_t s02 = nb02 / ts0;
|
||||
const int64_t s03 = nb03 / ts0;
|
||||
|
||||
rms_norm_f32_cuda(src0_d, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream);
|
||||
rms_norm_f32_cuda(src0, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_rms_norm_scale_fused(ggml_backend_cuda_context & ctx, ggml_tensor * dst, ggml_tensor * scale_tensor) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const float * src0_d = (const float *) src0->data;
|
||||
float * dst_d = (float *) scale_tensor->data;
|
||||
cudaStream_t stream = ctx.stream();
|
||||
|
||||
@@ -556,7 +634,7 @@ void ggml_cuda_op_rms_norm_scale_fused(ggml_backend_cuda_context & ctx, ggml_ten
|
||||
const int64_t s02 = nb02 / ts0;
|
||||
const int64_t s03 = nb03 / ts0;
|
||||
|
||||
rms_norm_f32_cuda<true>(src0_d, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream, scale);
|
||||
rms_norm_f32_cuda<true>(src0, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream, scale);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor * dst, ggml_tensor * mul_tensor) {
|
||||
@@ -565,15 +643,11 @@ void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
|
||||
memcpy(&eps, dst->op_params, sizeof(float));
|
||||
|
||||
const float * src0_d = (const float *) rms_norm_src->data;
|
||||
const float * mul_d = nullptr;
|
||||
const ggml_tensor * mul_src = nullptr;
|
||||
|
||||
if (mul_tensor->src[0] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[1]->data;
|
||||
mul_src = mul_tensor->src[1];
|
||||
} else if(mul_tensor->src[1] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[0]->data;
|
||||
mul_src = mul_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
@@ -609,7 +683,7 @@ void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
const int mul_nchannels = mul_src->ne[2];
|
||||
const int mul_nsamples = mul_src->ne[3];
|
||||
|
||||
rms_norm_mul_f32_cuda(src0_d, mul_d, nullptr, dst_d,
|
||||
rms_norm_mul_f32_cuda(rms_norm_src, mul_src, nullptr, dst_d,
|
||||
ne00, ne01, ne02, ne03,
|
||||
/*s00*/ s01, s02, s03,
|
||||
/*mul_s00*/ mul_s01, mul_s02, mul_s03,
|
||||
@@ -628,28 +702,21 @@ void ggml_cuda_op_rms_norm_fused_add(ggml_backend_cuda_context & ctx,
|
||||
|
||||
memcpy(&eps, dst->op_params, sizeof(float));
|
||||
|
||||
const float * src0_d = (const float *) rms_norm_src->data;
|
||||
const float * mul_d = nullptr;
|
||||
const ggml_tensor * mul_src = nullptr;
|
||||
|
||||
if (mul_tensor->src[0] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[1]->data;
|
||||
mul_src = mul_tensor->src[1];
|
||||
} else if (mul_tensor->src[1] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[0]->data;
|
||||
mul_src = mul_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
}
|
||||
|
||||
const float * add_d = nullptr;
|
||||
const ggml_tensor * add_src = nullptr;
|
||||
|
||||
if (add_tensor->src[0] == mul_tensor) {
|
||||
add_d = (float *) add_tensor->src[1]->data;
|
||||
add_src = add_tensor->src[1];
|
||||
} else if (add_tensor->src[1] == mul_tensor) {
|
||||
add_d = (float *) add_tensor->src[0]->data;
|
||||
add_src = add_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
@@ -697,7 +764,7 @@ void ggml_cuda_op_rms_norm_fused_add(ggml_backend_cuda_context & ctx,
|
||||
const int add_nchannels = add_src->ne[2];
|
||||
const int add_nsamples = add_src->ne[3];
|
||||
|
||||
rms_norm_mul_f32_cuda(src0_d, mul_d,add_d,dst_d,
|
||||
rms_norm_mul_f32_cuda(rms_norm_src, mul_src,add_src,dst_d,
|
||||
ne00,ne01, ne02, ne03,
|
||||
/*s00*/ s01, s02, s03,
|
||||
/*mul_s00*/ mul_s01, mul_s02, mul_s03,
|
||||
|
||||
@@ -151,9 +151,10 @@ static void ssm_conv_f32_cuda(const float * src0, const float * src1, const floa
|
||||
case 3: launch_kernel(std::integral_constant<int, 3 >{}); break;
|
||||
case 4: launch_kernel(std::integral_constant<int, 4 >{}); break;
|
||||
case 5: launch_kernel(std::integral_constant<int, 5 >{}); break;
|
||||
case 7: launch_kernel(std::integral_constant<int, 7 >{}); break;
|
||||
case 9: launch_kernel(std::integral_constant<int, 9 >{}); break;
|
||||
case 15: launch_kernel(std::integral_constant<int, 15>{}); break;
|
||||
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 9, 15 right now.");
|
||||
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 7, 9, 15 right now.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ struct topk_moe_config {
|
||||
bool use_sqrt_softplus;
|
||||
bool with_norm;
|
||||
bool delayed_softmax;
|
||||
int n_groups;
|
||||
int n_group_used;
|
||||
};
|
||||
|
||||
// Warp-local softmax used for both the pre-top-k logits and the post-top-k delayed path.
|
||||
@@ -78,6 +80,53 @@ __device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], co
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
// Set -inf for experts outside the top n_group_used groups. Group score is the sum of its top-2 experts (DeepSeek V3).
|
||||
template <int n_experts, int experts_per_thread>
|
||||
__device__ void group_mask_warp_inplace(float (&vals)[experts_per_thread], const int n_groups, const int n_group_used, const int lane) {
|
||||
const int n_per_group = n_experts / n_groups;
|
||||
|
||||
float score = -INFINITY; // lane g holds the score of group g
|
||||
for (int g = 0; g < n_groups; g++) {
|
||||
float top1 = -INFINITY;
|
||||
float top2 = -INFINITY;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
if ((i * WARP_SIZE + lane) / n_per_group == g) {
|
||||
top2 = fmaxf(top2, fminf(top1, vals[i]));
|
||||
top1 = fmaxf(top1, vals[i]);
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
|
||||
const float o1 = __shfl_xor_sync(0xFFFFFFFF, top1, offset, WARP_SIZE);
|
||||
const float o2 = __shfl_xor_sync(0xFFFFFFFF, top2, offset, WARP_SIZE);
|
||||
top2 = fmaxf(fminf(top1, o1), fmaxf(top2, o2));
|
||||
top1 = fmaxf(top1, o1);
|
||||
}
|
||||
if (lane == g) {
|
||||
score = top1 + top2;
|
||||
}
|
||||
}
|
||||
|
||||
// rank of each group, lower index wins ties
|
||||
int rank = 0;
|
||||
for (int g = 0; g < n_groups; g++) {
|
||||
const float other = __shfl_sync(0xFFFFFFFF, score, g, WARP_SIZE);
|
||||
rank += other > score || (other == score && g < lane);
|
||||
}
|
||||
const auto keep = __ballot_sync(0xFFFFFFFFULL, lane < n_groups && rank < n_group_used);
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
const int g = (i * WARP_SIZE + lane) / n_per_group;
|
||||
if (g >= n_groups || !((keep >> g) & 1)) {
|
||||
vals[i] = -INFINITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
|
||||
/*
|
||||
This kernel does the following:
|
||||
1. optionally softmax over the logits per token [n_experts, n_tokens]
|
||||
@@ -171,6 +220,17 @@ __global__ void topk_moe_cuda(const float * logits,
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
// without bias, masking wt is safe: the matcher ensures the kept groups hold all used experts
|
||||
if (config.n_groups > 0) {
|
||||
if constexpr (has_bias) {
|
||||
group_mask_warp_inplace<n_experts>(selection_wt, config.n_groups, config.n_group_used, threadIdx.x);
|
||||
} else {
|
||||
group_mask_warp_inplace<n_experts>(wt, config.n_groups, config.n_group_used, threadIdx.x);
|
||||
}
|
||||
}
|
||||
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
|
||||
//at this point, each thread holds either a portion of the softmax distribution
|
||||
//or the raw logits. We do the argmax reduce over n_expert_used, each time marking
|
||||
//the expert weight as -inf to exclude from the next iteration
|
||||
@@ -297,6 +357,10 @@ static void launch_topk_moe_cuda(ggml_backend_cuda_context & ctx,
|
||||
cudaStream_t stream = ctx.stream();
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, stream);
|
||||
|
||||
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
GGML_ASSERT(config.n_groups == 0 && "expert groups fusion is not supported on this HIP version");
|
||||
#endif
|
||||
|
||||
switch (n_expert) {
|
||||
case 1:
|
||||
ggml_cuda_kernel_launch(topk_moe_cuda<1, has_bias>, launch_params,
|
||||
@@ -389,6 +453,8 @@ void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
|
||||
config.use_sqrt_softplus = args.sqrt_softplus;
|
||||
config.with_norm = with_norm;
|
||||
config.delayed_softmax = args.delayed_softmax;
|
||||
config.n_groups = args.n_groups;
|
||||
config.n_group_used = args.n_group_used;
|
||||
|
||||
if (bias) {
|
||||
launch_topk_moe_cuda<true>(ctx, logits_d, weights_d, ids_d, bias_d, n_rows, n_experts, n_expert_used, clamp_val,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user