mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-24 21:47:33 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74bca39500 | ||
|
|
d327bf1f07 | ||
|
|
78a8ae14be | ||
|
|
f3269ef96a | ||
|
|
3f03b17724 | ||
|
|
9a0fde9967 | ||
|
|
e168e041ec | ||
|
|
ea1d7d66f3 | ||
|
|
71255fefc0 | ||
|
|
73adf89e38 | ||
|
|
df9beac715 | ||
|
|
4d32b29aaa | ||
|
|
3032209758 | ||
|
|
701fd25b3a | ||
|
|
3642238cc1 | ||
|
|
6b9ba4f2e8 | ||
|
|
8e5a58df87 | ||
|
|
1ddffe9f70 | ||
|
|
7894e8b924 | ||
|
|
edc353842c | ||
|
|
9ae73e68f5 | ||
|
|
0ae0e19766 | ||
|
|
21ec307146 | ||
|
|
9145ddb955 | ||
|
|
1a24df1639 |
@@ -105,7 +105,7 @@ jobs:
|
||||
cd build
|
||||
# Metal Paravirtual devices are difficult to support -> disable
|
||||
# ref: https://github.com/ggml-org/llama.cpp/pull/19802#issuecomment-4013704023
|
||||
ctest -L main -E "test-llama-archs|test-llama-context" --verbose --timeout 900
|
||||
ctest -L main -E "test-llama-archs|test-llama-context|test-speculative-rejection-sampling" --verbose --timeout 900
|
||||
|
||||
macos-latest-x64:
|
||||
runs-on: macos-15-intel
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
- name: Test
|
||||
id: ggml-ci
|
||||
run: |
|
||||
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
cpu-arm64-high-perf-graviton4:
|
||||
runs-on: ah-ubuntu_24_04-c8g_8x
|
||||
|
||||
@@ -60,4 +60,4 @@ jobs:
|
||||
- name: Test
|
||||
id: ggml-ci
|
||||
run: |
|
||||
GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_METAL=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 GGML_VK_DISABLE_COOPMAT2=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
# - name: ccache-buckets-save
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
# - name: ccache-buckets-save
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
vulkaninfo --summary
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
GG_BUILD_VULKAN=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
gpu-vulkan-intel-linux:
|
||||
runs-on: [self-hosted, Linux, Intel]
|
||||
|
||||
@@ -131,4 +131,4 @@ jobs:
|
||||
id: ggml-ci
|
||||
run: |
|
||||
GG_BUILD_WEBGPU=1 GG_BUILD_WEBGPU_DAWN_PREFIX="$GITHUB_WORKSPACE/dawn" \
|
||||
bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
|
||||
bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
|
||||
|
||||
@@ -185,18 +185,28 @@ fi
|
||||
|
||||
## helpers
|
||||
|
||||
# download a file if it does not exist or if it is outdated
|
||||
# download a file unless already cached in $MNT
|
||||
function gg_wget {
|
||||
local out=$1
|
||||
local url=$2
|
||||
|
||||
local cwd=`pwd`
|
||||
local name=`basename $url`
|
||||
local tmp=.gg-wget.$$.tmp
|
||||
|
||||
mkdir -p $out
|
||||
cd $out
|
||||
|
||||
# should not re-download if file is the same
|
||||
wget -nv -c -N $url
|
||||
# cache first: HF artifacts are static, HF sends no Last-Modified for -N
|
||||
if [ ! -s $name ]; then
|
||||
# publish with mv: concurrent runners never read partial files
|
||||
rm -rf $tmp && mkdir $tmp
|
||||
(cd $tmp && wget -nv $url)
|
||||
if [ -s $tmp/$name ]; then
|
||||
mv -f $tmp/$name $name
|
||||
fi
|
||||
rm -rf $tmp
|
||||
fi
|
||||
|
||||
cd $cwd
|
||||
}
|
||||
@@ -312,8 +322,8 @@ function gg_run_test_scripts {
|
||||
|
||||
set -e
|
||||
|
||||
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
|
||||
|
||||
set +e
|
||||
}
|
||||
@@ -374,10 +384,14 @@ function gg_run_qwen3_0_6b {
|
||||
|
||||
|
||||
gg_wget models-mnt/wikitext/ https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip
|
||||
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d models-mnt/wikitext/
|
||||
|
||||
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
|
||||
mkdir -p $OUT/scratch/qwen3-0.6B
|
||||
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d $OUT/scratch/wikitext/
|
||||
|
||||
path_models="../models-mnt/qwen3/0.6B"
|
||||
path_wiki="../models-mnt/wikitext/wikitext-2-raw"
|
||||
path_local="$OUT/scratch/qwen3-0.6B"
|
||||
path_wiki="$OUT/scratch/wikitext/wikitext-2-raw"
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -386,76 +400,15 @@ function gg_run_qwen3_0_6b {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf --outtype f16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-bf16.gguf --outtype bf16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf --outtype f16
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-bf16.gguf --outtype bf16
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_bf16="${path_models}/ggml-model-bf16.gguf"
|
||||
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
|
||||
model_q4_0="${path_models}/ggml-model-q4_0.gguf"
|
||||
model_q4_1="${path_models}/ggml-model-q4_1.gguf"
|
||||
model_q5_0="${path_models}/ggml-model-q5_0.gguf"
|
||||
model_q5_1="${path_models}/ggml-model-q5_1.gguf"
|
||||
model_q2_k="${path_models}/ggml-model-q2_k.gguf"
|
||||
model_q3_k="${path_models}/ggml-model-q3_k.gguf"
|
||||
model_q4_k="${path_models}/ggml-model-q4_k.gguf"
|
||||
model_q5_k="${path_models}/ggml-model-q5_k.gguf"
|
||||
model_q6_k="${path_models}/ggml-model-q6_k.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
model_bf16="${path_local}/ggml-model-bf16.gguf"
|
||||
model_q4_0="${path_local}/ggml-model-q4_0.gguf"
|
||||
|
||||
wiki_test="${path_wiki}/wiki.test.raw"
|
||||
|
||||
./bin/llama-quantize ${model_bf16} ${model_q8_0} q8_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_0} q4_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_1} q4_1 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_0} q5_0 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_1} q5_1 $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q2_k} q2_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q3_k} q3_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q4_k} q4_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q5_k} q5_k $(nproc)
|
||||
./bin/llama-quantize ${model_bf16} ${model_q6_k} q6_k $(nproc)
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q8_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q2_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q3_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q4_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q5_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_q6_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
|
||||
|
||||
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
fi
|
||||
(time ./bin/llama-perplexity --model ${model_q8_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
|
||||
(time ./bin/llama-perplexity --model ${model_q2_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q3_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q4_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q5_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
|
||||
(time ./bin/llama-perplexity --model ${model_q6_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
|
||||
|
||||
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
|
||||
|
||||
# TODO: these tests are extremely slow on WebGPU - re-enable when fixed
|
||||
# https://github.com/ggml-org/llama.cpp/actions/runs/39639985732
|
||||
if [ -z ${GG_BUILD_WEBGPU} ]; then
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
fi
|
||||
|
||||
function check_ppl {
|
||||
qnt="$1"
|
||||
ppl=$(echo "$2" | grep -oE "[0-9]+\.[0-9]+" | tail -n 1)
|
||||
@@ -469,20 +422,56 @@ function gg_run_qwen3_0_6b {
|
||||
return 0
|
||||
}
|
||||
|
||||
for qnt in q8_0 q4_0 q4_1 q5_0 q5_1 q2_k q3_k q4_k q5_k q6_k; do
|
||||
model="${path_local}/ggml-model-${qnt}.gguf"
|
||||
|
||||
./bin/llama-quantize ${model_bf16} ${model} ${qnt} $(nproc)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
|
||||
(time ./bin/llama-perplexity --model ${model} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
|
||||
|
||||
# note: ppl > 20.0 for q2_k with this model
|
||||
if [ "${qnt}" != "q2_k" ]; then
|
||||
check_ppl "${qnt}" "$(cat $OUT/${ci}-tg-${qnt}.log | grep "^\[1\]")"
|
||||
fi
|
||||
|
||||
if [ "${qnt}" = "q4_0" ]; then
|
||||
# q4_0 is kept for other tests
|
||||
q4_0="${path_models}/ggml-model-q4_0.gguf"
|
||||
ln ${model} "$q4_0" 2>/dev/null || { cp ${model} $q4_0.tmp.$$ && mv -f $q4_0.tmp.$$ $q4_0; }
|
||||
else
|
||||
rm -f ${model}
|
||||
fi
|
||||
done
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
|
||||
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
|
||||
fi
|
||||
|
||||
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
|
||||
|
||||
# TODO: these tests are extremely slow on WebGPU - re-enable when fixed
|
||||
# https://github.com/ggml-org/llama.cpp/actions/runs/39639985732
|
||||
if [ -z ${GG_BUILD_WEBGPU} ]; then
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
|
||||
fi
|
||||
|
||||
check_ppl "f16" "$(cat $OUT/${ci}-tg-f16.log | grep "^\[1\]")"
|
||||
if [ -z ${GG_BUILD_NO_BF16} ]; then
|
||||
check_ppl "bf16" "$(cat $OUT/${ci}-tg-bf16.log | grep "^\[1\]")"
|
||||
fi
|
||||
check_ppl "q8_0" "$(cat $OUT/${ci}-tg-q8_0.log | grep "^\[1\]")"
|
||||
check_ppl "q4_0" "$(cat $OUT/${ci}-tg-q4_0.log | grep "^\[1\]")"
|
||||
check_ppl "q4_1" "$(cat $OUT/${ci}-tg-q4_1.log | grep "^\[1\]")"
|
||||
check_ppl "q5_0" "$(cat $OUT/${ci}-tg-q5_0.log | grep "^\[1\]")"
|
||||
check_ppl "q5_1" "$(cat $OUT/${ci}-tg-q5_1.log | grep "^\[1\]")"
|
||||
#check_ppl "q2_k" "$(cat $OUT/${ci}-tg-q2_k.log | grep "^\[1\]")" # note: ppl > 20.0 for this quant and model
|
||||
check_ppl "q3_k" "$(cat $OUT/${ci}-tg-q3_k.log | grep "^\[1\]")"
|
||||
check_ppl "q4_k" "$(cat $OUT/${ci}-tg-q4_k.log | grep "^\[1\]")"
|
||||
check_ppl "q5_k" "$(cat $OUT/${ci}-tg-q5_k.log | grep "^\[1\]")"
|
||||
check_ppl "q6_k" "$(cat $OUT/${ci}-tg-q6_k.log | grep "^\[1\]")"
|
||||
|
||||
# scratch is rebuilt on every run; q4_0 is already published to $MNT
|
||||
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
|
||||
|
||||
set +e
|
||||
}
|
||||
@@ -505,6 +494,8 @@ function gg_run_embd_bge_small {
|
||||
gg_wget models-mnt/bge-small/1_Pooling https://huggingface.co/BAAI/bge-small-en-v1.5/raw/main/1_Pooling/config.json
|
||||
|
||||
path_models="../models-mnt/bge-small"
|
||||
path_local="$OUT/scratch/bge-small"
|
||||
rm -rf $path_local && mkdir -p $path_local
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -513,10 +504,10 @@ function gg_run_embd_bge_small {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
model_q8_0="${path_local}/ggml-model-q8_0.gguf"
|
||||
|
||||
./bin/llama-quantize ${model_f16} ${model_q8_0} q8_0
|
||||
|
||||
@@ -525,6 +516,8 @@ function gg_run_embd_bge_small {
|
||||
(time ./bin/llama-embedding --model ${model_f16} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
|
||||
(time ./bin/llama-embedding --model ${model_q8_0} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
|
||||
|
||||
rm -rf $path_local
|
||||
|
||||
set +e
|
||||
}
|
||||
|
||||
@@ -541,6 +534,8 @@ function gg_run_rerank_tiny {
|
||||
gg_wget models-mnt/rerank-tiny/ https://huggingface.co/jinaai/jina-reranker-v1-tiny-en/raw/main/vocab.json
|
||||
|
||||
path_models="../models-mnt/rerank-tiny"
|
||||
path_local="$OUT/scratch/rerank-tiny"
|
||||
rm -rf $path_local && mkdir -p $path_local
|
||||
|
||||
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
|
||||
|
||||
@@ -549,9 +544,9 @@ function gg_run_rerank_tiny {
|
||||
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
|
||||
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
|
||||
|
||||
model_f16="${path_models}/ggml-model-f16.gguf"
|
||||
model_f16="${path_local}/ggml-model-f16.gguf"
|
||||
|
||||
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
|
||||
|
||||
@@ -581,6 +576,8 @@ function gg_run_rerank_tiny {
|
||||
check_score "rerank score 1" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 1")" "0.00" "0.05" | tee -a $OUT/${ci}-rk-f16.log
|
||||
check_score "rerank score 2" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 2")" "0.10" "0.30" | tee -a $OUT/${ci}-rk-f16.log
|
||||
|
||||
rm -rf $path_local
|
||||
|
||||
set +e
|
||||
}
|
||||
|
||||
@@ -687,11 +684,11 @@ if [ -z ${GG_BUILD_LOW_PERF} ]; then
|
||||
ln -sfn ${mnt_models} ${SRC}/models-mnt
|
||||
|
||||
# Create a fresh python3 venv and enter it
|
||||
if ! python3 -m venv "$MNT/venv"; then
|
||||
echo "Error: Failed to create Python virtual environment at $MNT/venv."
|
||||
if ! python3 -m venv "$OUT/venv"; then
|
||||
echo "Error: Failed to create Python virtual environment at $OUT/venv."
|
||||
exit 1
|
||||
fi
|
||||
source "$MNT/venv/bin/activate"
|
||||
source "$OUT/venv/bin/activate"
|
||||
|
||||
pip install -r ${SRC}/requirements.txt --disable-pip-version-check
|
||||
pip install --editable gguf-py --disable-pip-version-check
|
||||
|
||||
@@ -78,6 +78,8 @@ add_library(${TARGET}
|
||||
download.h
|
||||
fit.cpp
|
||||
fit.h
|
||||
file-lock.cpp
|
||||
file-lock.h
|
||||
hf-cache.cpp
|
||||
hf-cache.h
|
||||
http.h
|
||||
@@ -184,3 +186,8 @@ if (LLAMA_LLGUIDANCE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PUBLIC llama Threads::Threads)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "AIX")
|
||||
# AIX's flock() function comes from libbsd.a
|
||||
target_link_libraries(${TARGET} PRIVATE -lbsd)
|
||||
endif()
|
||||
|
||||
+29
-21
@@ -470,6 +470,14 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
auto opts = handler.opts; // copy
|
||||
opts.callback = callback;
|
||||
|
||||
// another process may hold the cache lock during finalization:
|
||||
// let the download callback cancel that wait too
|
||||
auto finalize = [callback](const hf_cache::hf_file & f) {
|
||||
return hf_cache::finalize_file(f, [callback]() {
|
||||
return !(callback && callback->is_cancelled());
|
||||
});
|
||||
};
|
||||
|
||||
// handle plain "url" if needed
|
||||
auto handle_url = [&](common_params_model & model) {
|
||||
if (!model.url.empty()) {
|
||||
@@ -518,7 +526,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
}
|
||||
|
||||
// handle hf_plan tasks
|
||||
auto add_tasks = [&opts, &tasks](const hf_cache::hf_files & model_files,
|
||||
auto add_tasks = [&opts, &tasks, &finalize](const hf_cache::hf_files & model_files,
|
||||
const hf_cache::hf_file & primary,
|
||||
common_params_model & model) {
|
||||
for (size_t i = 0; i < model_files.size(); ++i) {
|
||||
@@ -527,9 +535,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(model_file, opts, [&, is_primary]() {
|
||||
if (is_primary) {
|
||||
// the primary file is the first split (00001-of), use it as model path
|
||||
model.path = hf_cache::finalize_file(model_file);
|
||||
model.path = finalize(model_file);
|
||||
} else {
|
||||
hf_cache::finalize_file(model_file);
|
||||
finalize(model_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -581,9 +589,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.mtp, opts, [&]() {
|
||||
// only use the discovered MTP head when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.mtp);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.mtp);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.mtp);
|
||||
finalize(plan_spec.mtp);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -591,9 +599,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.dflash, opts, [&]() {
|
||||
// only use the discovered DFlash sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.dflash);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.dflash);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.dflash);
|
||||
finalize(plan_spec.dflash);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -601,9 +609,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.eagle3, opts, [&]() {
|
||||
// only use the discovered Eagle3 sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.eagle3);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.eagle3);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.eagle3);
|
||||
finalize(plan_spec.eagle3);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -611,9 +619,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan_spec.dspark, opts, [&]() {
|
||||
// only use the discovered DSpark sidecar when no draft path is set yet
|
||||
if (params.speculative.draft.mparams.path.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.dspark);
|
||||
params.speculative.draft.mparams.path = finalize(plan_spec.dspark);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan_spec.dspark);
|
||||
finalize(plan_spec.dspark);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -634,16 +642,16 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
}
|
||||
if (!plan.mmproj.local_path.empty()) {
|
||||
tasks.emplace_back(plan.mmproj, opts, [&]() {
|
||||
params.mmproj.path = hf_cache::finalize_file(plan.mmproj);
|
||||
params.mmproj.path = finalize(plan.mmproj);
|
||||
});
|
||||
}
|
||||
if (!plan.mtp.local_path.empty() && !had_spec_url) {
|
||||
tasks.emplace_back(plan.mtp, opts, [&]() {
|
||||
// only fall back to the discovered MTP head when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.mtp);
|
||||
params.speculative.draft.mparams.path = finalize(plan.mtp);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.mtp);
|
||||
finalize(plan.mtp);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -651,9 +659,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.dflash, opts, [&]() {
|
||||
// only fall back to the discovered DFlash sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.dflash);
|
||||
params.speculative.draft.mparams.path = finalize(plan.dflash);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.dflash);
|
||||
finalize(plan.dflash);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -661,9 +669,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.eagle3, opts, [&]() {
|
||||
// only fall back to the discovered Eagle3 sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.eagle3);
|
||||
params.speculative.draft.mparams.path = finalize(plan.eagle3);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.eagle3);
|
||||
finalize(plan.eagle3);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -671,9 +679,9 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.dspark, opts, [&]() {
|
||||
// only fall back to the discovered DSpark sidecar when no draft was explicitly provided
|
||||
if (params.speculative.draft.mparams.empty()) {
|
||||
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.dspark);
|
||||
params.speculative.draft.mparams.path = finalize(plan.dspark);
|
||||
} else {
|
||||
hf_cache::finalize_file(plan.dspark);
|
||||
finalize(plan.dspark);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -681,7 +689,7 @@ void common_models_handler_apply(common_models_handler & handler, common_params
|
||||
tasks.emplace_back(plan.preset, opts, [&]() {
|
||||
// if HF repo is a preset repo, we simply run server in router mode with the preset.ini file
|
||||
params.models_preset_hf = params.model.hf_repo; // only for showing a warning
|
||||
params.models_preset = hf_cache::finalize_file(plan.preset);
|
||||
params.models_preset = finalize(plan.preset);
|
||||
// clear the model so the server starts in router mode
|
||||
params.model.path.clear();
|
||||
params.model.hf_repo.clear();
|
||||
|
||||
+89
-6
@@ -428,6 +428,41 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
|
||||
if (message.contains("tool_call_id")) {
|
||||
msg.tool_call_id = message.at("tool_call_id");
|
||||
}
|
||||
{
|
||||
json extra = json::object();
|
||||
for (const auto & [k, v] : message.items()) {
|
||||
if (k != "role" && k != "content" && k != "tool_calls" && k != "reasoning_content" &&
|
||||
k != "name" && k != "tool_call_id") {
|
||||
extra[k] = v;
|
||||
}
|
||||
}
|
||||
if (has_tool_calls) {
|
||||
// per tool call: keys besides type/function/id, and function keys besides name/arguments
|
||||
json tc_extra = json::array();
|
||||
bool any = false;
|
||||
for (const auto & tool_call : message.at("tool_calls")) {
|
||||
json e = json::object();
|
||||
for (const auto & [k, v] : tool_call.items()) {
|
||||
if (k != "type" && k != "function" && k != "id") {
|
||||
e[k] = v;
|
||||
}
|
||||
}
|
||||
for (const auto & [k, v] : tool_call.at("function").items()) {
|
||||
if (k != "name" && k != "arguments") {
|
||||
e["function"][k] = v;
|
||||
}
|
||||
}
|
||||
any = any || !e.empty();
|
||||
tc_extra.push_back(e);
|
||||
}
|
||||
if (any) {
|
||||
extra["__tool_call_extras"] = tc_extra;
|
||||
}
|
||||
}
|
||||
if (!extra.empty()) {
|
||||
msg.extra_json = extra.dump();
|
||||
}
|
||||
}
|
||||
|
||||
msgs.push_back(msg);
|
||||
}
|
||||
@@ -516,6 +551,50 @@ static json render_message_to_json(const std::vector<common_chat_msg> & msgs, co
|
||||
return messages_inp_normalizer(c).normalize(messages);
|
||||
}
|
||||
|
||||
// DeepSeek V4.1 reads non-OpenAI message keys and joins content blocks with "\n\n" around images,
|
||||
// so keep media markers as separate text parts and restore the extra keys
|
||||
static json render_message_to_json_deepseek_v4_1(const std::vector<common_chat_msg> & msgs, const jinja::caps & c) {
|
||||
json messages = json::array();
|
||||
for (const auto & msg : msgs) {
|
||||
json jmsg;
|
||||
if (msg.contains_media() && msg.content.empty()) {
|
||||
common_chat_msg typed = msg;
|
||||
for (auto & part : typed.content_parts) {
|
||||
part.type = "text";
|
||||
}
|
||||
jmsg = typed.to_json_oaicompat(/* concat_typed_text= */ false);
|
||||
} else {
|
||||
jmsg = msg.to_json_oaicompat(/* concat_typed_text= */ false);
|
||||
}
|
||||
if (!msg.extra_json.empty()) {
|
||||
const json extra = json::parse(msg.extra_json);
|
||||
for (const auto & [k, v] : extra.items()) {
|
||||
if (k == "__tool_call_extras") {
|
||||
if (!jmsg.contains("tool_calls")) {
|
||||
continue;
|
||||
}
|
||||
auto & tcs = jmsg.at("tool_calls");
|
||||
for (size_t i = 0; i < tcs.size() && i < v.size(); i++) {
|
||||
for (const auto & [tk, tv] : v[i].items()) {
|
||||
if (tk == "function") {
|
||||
for (const auto & [fk, fv] : tv.items()) {
|
||||
tcs[i]["function"][fk] = fv;
|
||||
}
|
||||
} else {
|
||||
tcs[i][tk] = tv;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!jmsg.contains(k)) {
|
||||
jmsg[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
messages.push_back(std::move(jmsg));
|
||||
}
|
||||
return messages_inp_normalizer(c).normalize(messages);
|
||||
}
|
||||
|
||||
// DEPRECATED: only used in tests
|
||||
json common_chat_msgs_to_json_oaicompat(const std::vector<common_chat_msg> & msgs, bool concat_typed_text) {
|
||||
jinja::caps c;
|
||||
@@ -1208,11 +1287,13 @@ static common_chat_params_init_fn common_chat_template_detect_params_init(const
|
||||
// DeepSeek V3.2/V4 format detection: template defines dsml_token and uses it for tool calls.
|
||||
// The template source contains the token as a variable assignment, not as a literal in markup.
|
||||
// V3.2 names the tool call block "function_calls", V4 names it "tool_calls".
|
||||
if (src.find("dsml_token") != std::string::npos &&
|
||||
src.find("DSML") != std::string::npos &&
|
||||
(src.find("function_calls") != std::string::npos ||
|
||||
src.find("tool_calls") != std::string::npos)) {
|
||||
LOG_DBG("Using specialized template: DeepSeek V3.2/V4\n");
|
||||
// V4.1 tag names have a leading space and its template does not name the dsml_token variable.
|
||||
if ((src.find("dsml_token") != std::string::npos &&
|
||||
src.find("DSML") != std::string::npos &&
|
||||
(src.find("function_calls") != std::string::npos ||
|
||||
src.find("tool_calls") != std::string::npos)) ||
|
||||
src.find("|DSML| calls>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: DeepSeek V3.2/V4/V4.1\n");
|
||||
return common_chat_params_init_deepseek_v3_2;
|
||||
}
|
||||
|
||||
@@ -1313,7 +1394,9 @@ static common_chat_params common_chat_templates_apply_jinja(const struct common_
|
||||
workaround::trim_all_content(trimmed_messages);
|
||||
messages_to_render = &trimmed_messages;
|
||||
}
|
||||
params.messages = render_message_to_json(*messages_to_render, tmpl.original_caps());
|
||||
params.messages = src.find("|DSML| calls>") != std::string::npos ?
|
||||
render_message_to_json_deepseek_v4_1(*messages_to_render, tmpl.original_caps()) :
|
||||
render_message_to_json(*messages_to_render, tmpl.original_caps());
|
||||
params.tool_choice = inputs.tool_choice;
|
||||
params.reasoning_format = inputs.reasoning_format;
|
||||
params.enable_thinking = inputs.enable_thinking;
|
||||
|
||||
@@ -94,6 +94,8 @@ struct common_chat_msg {
|
||||
std::string reasoning_content;
|
||||
std::string tool_name;
|
||||
std::string tool_call_id;
|
||||
// non-OpenAI message keys as a JSON object string, only for templates that read them (DeepSeek V4.1)
|
||||
std::string extra_json;
|
||||
|
||||
common_json to_json_oaicompat(bool concat_typed_text = false) const;
|
||||
|
||||
|
||||
+6
-1
@@ -26,6 +26,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -917,9 +918,13 @@ std::string fs_path_to_utf8(const std::filesystem::path & path) {
|
||||
}
|
||||
|
||||
void fs_write_atomic(const std::filesystem::path & path, const std::string & data) {
|
||||
// a random suffix gives every writer its own temporary file, across processes and containers
|
||||
std::random_device rd;
|
||||
const uint64_t id = ((uint64_t) rd() << 32) | rd();
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::path path_tmp = path;
|
||||
path_tmp += ".tmp";
|
||||
path_tmp += "." + std::to_string(id) + ".tmp";
|
||||
|
||||
if (path.has_parent_path()) {
|
||||
std::filesystem::create_directories(path.parent_path(), ec);
|
||||
|
||||
+77
-28
@@ -4,6 +4,7 @@
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
#include "download.h"
|
||||
#include "file-lock.h"
|
||||
#include "hf-cache.h"
|
||||
#include "json.h"
|
||||
|
||||
@@ -77,6 +78,18 @@ std::pair<std::string, std::string> common_download_split_repo_tag(const std::st
|
||||
return {hf_repo, tag};
|
||||
}
|
||||
|
||||
// file name from url, without the query string
|
||||
static std::string url_get_filename(const std::string & url) {
|
||||
std::string filename = url;
|
||||
if (auto pos = filename.rfind('/'); pos != std::string::npos) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
if (auto pos = filename.find('?'); pos != std::string::npos) {
|
||||
filename = filename.substr(0, pos);
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
class ProgressBar : public common_download_callback {
|
||||
static inline std::mutex mutex;
|
||||
static inline std::map<const ProgressBar *, int> lines;
|
||||
@@ -100,14 +113,8 @@ public:
|
||||
ProgressBar() = default;
|
||||
|
||||
void on_start(const common_download_progress & p) override {
|
||||
filename = p.url;
|
||||
filename = url_get_filename(p.url);
|
||||
|
||||
if (auto pos = filename.rfind('/'); pos != std::string::npos) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
if (auto pos = filename.find('?'); pos != std::string::npos) {
|
||||
filename = filename.substr(0, pos);
|
||||
}
|
||||
for (size_t i = 0; i < filename.size(); ++i) {
|
||||
if ((filename[i] & 0xC0) != 0x80) {
|
||||
if (len++ == 39) {
|
||||
@@ -257,15 +264,62 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
static int common_download_file_single_online(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts,
|
||||
bool skip_etag) {
|
||||
bool skip_etag,
|
||||
const std::string & lock_path,
|
||||
const std::string & alt_path) {
|
||||
static const int max_attempts = 3;
|
||||
static const int retry_delay_seconds = 2;
|
||||
|
||||
const bool file_exists = std::filesystem::exists(std::filesystem::u8path(path));
|
||||
const std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
const std::string path_lock = lock_path.empty() ? path + ".lock" : lock_path;
|
||||
|
||||
common_download_progress p;
|
||||
p.url = url;
|
||||
|
||||
if (opts.callback) {
|
||||
opts.callback->on_start(p);
|
||||
}
|
||||
|
||||
// the completion callback may itself take the same lock (the HF cache
|
||||
// finalizes blobs under it), so every exit goes through finish()
|
||||
common_file_lock lock(path_lock);
|
||||
auto finish = [&](int status) {
|
||||
lock.close();
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, is_http_status_ok(status));
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
// alt_path is the HF snapshot: the blob may have been moved there when symlinks are unavailable
|
||||
auto cache_hit = [&] {
|
||||
return std::filesystem::exists(std::filesystem::u8path(path)) ||
|
||||
(!alt_path.empty() && std::filesystem::exists(std::filesystem::u8path(alt_path)));
|
||||
};
|
||||
|
||||
if (skip_etag && cache_hit()) {
|
||||
LOG_DBG("%s: using cached file: %s\n", __func__, path.c_str());
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
|
||||
// take the lock before touching the cache, so two processes cannot race on the same file
|
||||
bool waited = false;
|
||||
if (!lock.acquire([&] {
|
||||
if (!waited) {
|
||||
LOG_INF("%s: file '%s' is being downloaded by another process, waiting...\n", __func__, url_get_filename(url).c_str());
|
||||
waited = true;
|
||||
}
|
||||
return !(opts.callback && opts.callback->is_cancelled());
|
||||
})) {
|
||||
return finish(-1);
|
||||
}
|
||||
|
||||
// under the lock: another process may have finished while we waited
|
||||
bool file_exists = cache_hit();
|
||||
|
||||
if (file_exists && skip_etag) {
|
||||
LOG_DBG("%s: using cached file: %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
|
||||
auto [cli, parts] = common_http_client(url);
|
||||
@@ -294,9 +348,9 @@ static int common_download_file_single_online(const std::string & url,
|
||||
LOG_TRC("%s: HEAD failed, status: %d\n", __func__, head ? head->status : -1);
|
||||
if (file_exists) {
|
||||
LOG_TRC("%s: using cached file (HEAD failed): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
return head ? head->status : -1;
|
||||
return finish(head ? head->status : -1);
|
||||
}
|
||||
|
||||
std::string etag;
|
||||
@@ -304,8 +358,6 @@ static int common_download_file_single_online(const std::string & url,
|
||||
etag = head->get_header_value("ETag");
|
||||
}
|
||||
|
||||
common_download_progress p;
|
||||
p.url = url;
|
||||
if (head->has_header("Content-Length")) {
|
||||
try {
|
||||
p.total = std::stoull(head->get_header_value("Content-Length"));
|
||||
@@ -322,16 +374,16 @@ static int common_download_file_single_online(const std::string & url,
|
||||
if (file_exists) {
|
||||
if (etag.empty()) {
|
||||
LOG_DBG("%s: using cached file (no server etag): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
if (!last_etag.empty() && last_etag == etag) {
|
||||
LOG_DBG("%s: using cached file (same etag): %s\n", __func__, path.c_str());
|
||||
return 304; // 304 Not Modified - fake cached response
|
||||
return finish(304); // 304 Not Modified - fake cached response
|
||||
}
|
||||
// pass this point, the file exists but is different from the server version, so we need to redownload it
|
||||
if (remove(path.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path.c_str());
|
||||
return -1;
|
||||
return finish(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +393,6 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
const std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
int delay = retry_delay_seconds;
|
||||
|
||||
if (!skip_etag && !etag.empty()) {
|
||||
@@ -349,7 +400,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
LOG_WRN("%s: partial file does not match the server etag, discarding it: %s\n", __func__, path_temporary.c_str());
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
|
||||
return -1;
|
||||
return finish(-1);
|
||||
}
|
||||
}
|
||||
write_etag(path_temporary, etag);
|
||||
@@ -359,7 +410,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
// report the resume offset, so a resumed file does not start at 0
|
||||
p.downloaded = supports_ranges && std::filesystem::exists(path_temporary) ?
|
||||
std::filesystem::file_size(path_temporary) : 0;
|
||||
opts.callback->on_start(p);
|
||||
opts.callback->on_update(p);
|
||||
}
|
||||
|
||||
for (int i = 0; i < max_attempts; ++i) {
|
||||
@@ -403,16 +454,12 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, success);
|
||||
}
|
||||
// a stopped download keeps path_temporary, so the next attempt resumes from it
|
||||
if (!success) {
|
||||
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
|
||||
return -1; // max attempts reached
|
||||
}
|
||||
|
||||
return head->status;
|
||||
return finish(success ? head->status : -1);
|
||||
}
|
||||
|
||||
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url,
|
||||
@@ -452,14 +499,16 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string
|
||||
int common_download_file_single(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts,
|
||||
bool skip_etag) {
|
||||
bool skip_etag,
|
||||
const std::string & lock_path,
|
||||
const std::string & alt_path) {
|
||||
if (!opts.offline) {
|
||||
ProgressBar tty_cb;
|
||||
common_download_opts online_opts = opts;
|
||||
if (!online_opts.callback) {
|
||||
online_opts.callback = &tty_cb;
|
||||
}
|
||||
return common_download_file_single_online(url, path, online_opts, skip_etag);
|
||||
return common_download_file_single_online(url, path, online_opts, skip_etag, lock_path, alt_path);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(std::filesystem::u8path(path))) {
|
||||
@@ -821,7 +870,7 @@ void common_download_run_tasks(const std::vector<common_download_task> & tasks)
|
||||
for (const auto & task : tasks) {
|
||||
futures.push_back(std::async(std::launch::async,
|
||||
[&task]() {
|
||||
return common_download_file_single(task.url, task.local_path, task.opts, task.is_hf);
|
||||
return common_download_file_single(task.url, task.local_path, task.opts, task.is_hf, task.lock_path, task.alt_path);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
+9
-2
@@ -69,12 +69,15 @@ struct common_download_task {
|
||||
std::string local_path;
|
||||
std::function<void()> on_done;
|
||||
bool is_hf = false;
|
||||
std::string lock_path;
|
||||
std::string alt_path; // HF snapshot path, also treated as cached (the blob may be moved there)
|
||||
|
||||
common_download_task() = default;
|
||||
common_download_task(hf_cache::hf_file f,
|
||||
const common_download_opts & opts,
|
||||
std::function<void()> on_done = nullptr)
|
||||
: opts(opts), url(f.url), local_path(f.local_path), on_done(on_done), is_hf(true) {}
|
||||
: opts(opts), url(f.url), local_path(f.local_path), on_done(on_done), is_hf(true),
|
||||
lock_path(hf_cache::get_lock_path(f.repo_id, f.oid)), alt_path(f.final_path) {}
|
||||
};
|
||||
|
||||
void common_download_run_tasks(const std::vector<common_download_task> & tasks);
|
||||
@@ -92,10 +95,14 @@ std::string common_download_resolve_path(const std::string & hf_repo_with_tag, c
|
||||
// download single file from url to local path
|
||||
// returns status code or -1 on error
|
||||
// skip_etag: if true, don't read/write .etag files (for HF cache where filename is the hash)
|
||||
// lock_path: lock file used to serialize downloads of the same file; when empty, "<path>.lock" is used
|
||||
// alt_path: additional path treated as present in cache (see cache_hit in download.cpp)
|
||||
int common_download_file_single(const std::string & url,
|
||||
const std::string & path,
|
||||
const common_download_opts & opts = {},
|
||||
bool skip_etag = false);
|
||||
bool skip_etag = false,
|
||||
const std::string & lock_path = "",
|
||||
const std::string & alt_path = "");
|
||||
|
||||
// resolve and download model from Docker registry
|
||||
// return local path to downloaded model file
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "file-lock.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
common_file_lock::common_file_lock(const std::string & lock_path) {
|
||||
this->lock_path = lock_path;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// the \\?\ prefix (for paths longer than 255 chars) is only accepted on absolute paths
|
||||
// with backslash separators: normalize first, then check the length of the absolute form
|
||||
static std::wstring as_extended_path(const std::string & path) {
|
||||
// convert from the narrow encoding std::filesystem uses on this system
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, path.c_str(), (int) path.size(), nullptr, 0);
|
||||
if (wlen <= 0) {
|
||||
return std::wstring(path.begin(), path.end()); // best effort for exotic encodings
|
||||
}
|
||||
std::wstring wide(wlen, L'\0');
|
||||
MultiByteToWideChar(CP_ACP, 0, path.c_str(), (int) path.size(), wide.data(), wlen);
|
||||
|
||||
if (wide.rfind(L"\\\\?\\", 0) == 0) {
|
||||
return wide;
|
||||
}
|
||||
|
||||
// GetFullPathNameW resolves relative paths and normalizes separators
|
||||
DWORD size = GetFullPathNameW(wide.c_str(), 0, nullptr, nullptr);
|
||||
if (size == 0) {
|
||||
return wide;
|
||||
}
|
||||
std::wstring full(size, L'\0');
|
||||
DWORD written = GetFullPathNameW(wide.c_str(), size, full.data(), nullptr);
|
||||
if (written == 0 || written >= size) {
|
||||
return wide;
|
||||
}
|
||||
full.resize(written);
|
||||
|
||||
if (full.size() <= 255) {
|
||||
return wide; // short enough: open the path exactly as given
|
||||
}
|
||||
if (full.rfind(L"\\\\", 0) == 0) {
|
||||
return L"\\\\?\\UNC\\" + full.substr(2); // \\server\share -> \\?\UNC\server\share
|
||||
}
|
||||
return L"\\\\?\\" + full;
|
||||
}
|
||||
#endif
|
||||
|
||||
common_file_lock::status common_file_lock::try_lock() {
|
||||
if (lock_path.empty()) {
|
||||
return status::acquired; // no lock path, nothing to lock
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (fd < 0) {
|
||||
// opened with a wide path: extended paths cannot go through the narrow CRT
|
||||
std::wstring lock_path_w = as_extended_path(lock_path);
|
||||
fd = _wopen(lock_path_w.c_str(), _O_RDWR | _O_CREAT | _O_BINARY | _O_NOINHERIT, _S_IREAD | _S_IWRITE);
|
||||
if (fd < 0) {
|
||||
LOG_ERR("%s: cannot open lock file: %s\n", __func__, lock_path.c_str());
|
||||
return status::error;
|
||||
}
|
||||
}
|
||||
// lock the first byte, the file itself stays empty
|
||||
OVERLAPPED ov = {};
|
||||
locked = LockFileEx((HANDLE) _get_osfhandle(fd),
|
||||
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov);
|
||||
if (locked) {
|
||||
return status::acquired;
|
||||
}
|
||||
if (GetLastError() == ERROR_LOCK_VIOLATION) {
|
||||
return status::busy;
|
||||
}
|
||||
LOG_ERR("%s: file locking failed (%d)\n", __func__, (int) GetLastError());
|
||||
return status::error;
|
||||
#else
|
||||
if (fd < 0) {
|
||||
int open_flags = O_RDWR | O_CREAT | O_CLOEXEC;
|
||||
#ifdef O_NOFOLLOW
|
||||
open_flags |= O_NOFOLLOW;
|
||||
#endif
|
||||
// group-writable, so users sharing a cache can wait on the lock
|
||||
fd = open(lock_path.c_str(), open_flags, 0664);
|
||||
if (fd < 0) {
|
||||
LOG_ERR("%s: cannot open lock file: %s\n", __func__, lock_path.c_str());
|
||||
return status::error;
|
||||
}
|
||||
// open() filters the mode through the umask
|
||||
(void) fchmod(fd, 0664);
|
||||
}
|
||||
|
||||
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
|
||||
locked = true;
|
||||
return status::acquired;
|
||||
}
|
||||
// EACCES is reported as contention on some filesystems
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EACCES) {
|
||||
return status::busy;
|
||||
}
|
||||
// flock is not implemented (some NFS/FUSE mounts): report it instead of racing
|
||||
LOG_ERR("%s: file locking failed (%s)\n", __func__, strerror(errno));
|
||||
return status::error;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool common_file_lock::acquire(const std::function<bool()> & keep_waiting) {
|
||||
// the lock file is opened by try_lock, its parent must exist by then
|
||||
if (!lock_path.empty()) {
|
||||
std::error_code ec;
|
||||
auto lock_dir = std::filesystem::path(lock_path).parent_path();
|
||||
if (!lock_dir.empty()) {
|
||||
std::filesystem::create_directories(lock_dir, ec);
|
||||
if (ec) {
|
||||
LOG_ERR("%s: unable to create lock directory '%s': %s\n", __func__, lock_dir.string().c_str(), ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
switch (try_lock()) {
|
||||
case status::acquired:
|
||||
return true;
|
||||
case status::busy:
|
||||
if (keep_waiting && !keep_waiting()) {
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
case status::error:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the lock file is never deleted: another process could then create a fresh
|
||||
// file at the same path and lock that
|
||||
void common_file_lock::close() {
|
||||
#if defined(_WIN32)
|
||||
if (fd >= 0) {
|
||||
if (locked) {
|
||||
OVERLAPPED ov = {};
|
||||
UnlockFileEx((HANDLE) _get_osfhandle(fd), 0, 1, 0, &ov);
|
||||
}
|
||||
_close(fd);
|
||||
}
|
||||
#else
|
||||
if (fd >= 0) {
|
||||
if (locked) {
|
||||
flock(fd, LOCK_UN);
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
fd = -1;
|
||||
locked = false;
|
||||
}
|
||||
|
||||
common_file_lock::~common_file_lock() {
|
||||
close();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
// Cross-process exclusive lock (flock on POSIX, LockFileEx on Windows).
|
||||
// Lazy: acquire() opens the lock file, so a read-only cache works if the lock is never acquired.
|
||||
class common_file_lock {
|
||||
public:
|
||||
common_file_lock(const std::string & lock_path);
|
||||
~common_file_lock();
|
||||
|
||||
// take the lock, waiting while another process holds it;
|
||||
// keep_waiting is polled before each retry, false cancels the wait.
|
||||
// Also returns false when locking is unavailable.
|
||||
bool acquire(const std::function<bool()> & keep_waiting = {});
|
||||
|
||||
void close();
|
||||
|
||||
common_file_lock(const common_file_lock &) = delete;
|
||||
common_file_lock & operator=(const common_file_lock &) = delete;
|
||||
|
||||
private:
|
||||
enum class status {
|
||||
acquired,
|
||||
busy, // another process holds the lock
|
||||
error, // file locking is unavailable: no exclusion is possible
|
||||
};
|
||||
|
||||
status try_lock();
|
||||
|
||||
std::string lock_path;
|
||||
|
||||
int fd = -1;
|
||||
bool locked = false;
|
||||
};
|
||||
+28
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "build-info.h"
|
||||
#include "common.h"
|
||||
#include "file-lock.h"
|
||||
#include "log.h"
|
||||
#include "http.h"
|
||||
#include "json.h"
|
||||
@@ -161,6 +162,14 @@ static bool is_valid_oid(const std::string & oid) {
|
||||
return is_hex_string(oid, 40) || is_hex_string(oid, 64);
|
||||
}
|
||||
|
||||
std::string get_lock_path(const std::string & repo_id, const std::string & oid) {
|
||||
if (!is_valid_repo_id(repo_id) || !is_valid_oid(oid)) {
|
||||
return {};
|
||||
}
|
||||
fs::path lock_path = fs::path(get_cache_path()) / ".locks" / repo_to_folder_name(repo_id) / (oid + ".lock");
|
||||
return lock_path.string();
|
||||
}
|
||||
|
||||
static bool is_valid_subpath(const fs::path & path, const fs::path & subpath) {
|
||||
if (subpath.is_absolute()) {
|
||||
return false; // never do a / b with b absolute
|
||||
@@ -437,7 +446,7 @@ hf_files get_cached_files(const std::string & repo_id) {
|
||||
return files;
|
||||
}
|
||||
|
||||
std::string finalize_file(const hf_file & file) {
|
||||
std::string finalize_file(const hf_file & file, const std::function<bool()> & keep_waiting) {
|
||||
static std::atomic<bool> symlinks_disabled{false};
|
||||
|
||||
std::error_code ec;
|
||||
@@ -452,6 +461,24 @@ std::string finalize_file(const hf_file & file) {
|
||||
return file.final_path;
|
||||
}
|
||||
|
||||
// the lock file is shared with huggingface_hub: another process may be
|
||||
// finalizing the same blob
|
||||
common_file_lock lock(get_lock_path(file.repo_id, file.oid));
|
||||
if (!lock.acquire(keep_waiting)) {
|
||||
// without the lock, another process could move the blob while we check it
|
||||
throw std::runtime_error("failed to lock cache for finalization: " + file.final_path);
|
||||
}
|
||||
|
||||
// acquire() only polls keep_waiting while waiting for a busy lock
|
||||
if (keep_waiting && !keep_waiting()) {
|
||||
throw std::runtime_error("download cancelled before finalization: " + file.final_path);
|
||||
}
|
||||
|
||||
// re-check under the lock: the winner may have created the link already
|
||||
if (fs::exists(final_path, ec)) {
|
||||
return file.final_path;
|
||||
}
|
||||
|
||||
fs::create_directories(final_path.parent_path(), ec);
|
||||
|
||||
if (!symlinks_disabled) {
|
||||
|
||||
+9
-2
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,8 +27,14 @@ hf_files get_repo_files(
|
||||
|
||||
hf_files get_cached_files(const std::string & repo_id = {});
|
||||
|
||||
// Create snapshot path (link or move/copy) and return it
|
||||
std::string finalize_file(const hf_file & file);
|
||||
// Lock file path for a blob, shared with the hf CLI: <cache>/.locks/<repo folder>/<oid>.lock
|
||||
// Pure path computation: creates nothing. Returns an empty string on invalid repo or oid.
|
||||
std::string get_lock_path(const std::string & repo_id, const std::string & oid);
|
||||
|
||||
// Create snapshot path (link or move/copy) and return it.
|
||||
// Throws when the lock cannot be taken or keep_waiting returns false.
|
||||
// A completed snapshot (final_path present) is returned without the lock, so a read-only cache works.
|
||||
std::string finalize_file(const hf_file & file, const std::function<bool()> & keep_waiting = {});
|
||||
|
||||
// Remove the entire cached directory for a repo, returns true if removed
|
||||
bool remove_cached_repo(const std::string & repo_id);
|
||||
|
||||
+42
-63
@@ -299,74 +299,53 @@ static value & get_attribute(const value & val, const value & attr, value & defa
|
||||
return default_val;
|
||||
}
|
||||
|
||||
template<bool is_reject>
|
||||
static value selectattr(const func_args & args) {
|
||||
args.ensure_count(2, 4);
|
||||
args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
|
||||
template<bool is_reject, bool lookup_attr>
|
||||
static value select_or_reject(const func_args & args) {
|
||||
// example: array | select("odd") -> test_is_odd(item)
|
||||
// example: array | selectattr("status", "equalto", "active") -> test_is_equalto(item.status, "active")
|
||||
constexpr size_t test_pos = lookup_attr ? 2 : 1;
|
||||
args.ensure_count(test_pos);
|
||||
args.ensure_val<value_array>(args.get_pos(0));
|
||||
value attr;
|
||||
if constexpr (lookup_attr) {
|
||||
attr = args.get_pos(1);
|
||||
args.ensure_val<value_string>(attr);
|
||||
}
|
||||
|
||||
auto & builtins = global_builtins();
|
||||
auto test_it = builtins.end();
|
||||
if (args.count() > test_pos) {
|
||||
args.ensure_val<value_string>(args.get_pos(test_pos));
|
||||
std::string test_name = args.get_pos(test_pos)->as_string().str();
|
||||
test_it = builtins.find("test_is_" + test_name);
|
||||
if (test_it == builtins.end()) {
|
||||
throw raised_exception(args.func_name + ": unknown test '" + test_name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
auto arr = args.get_pos(0)->as_array();
|
||||
auto attribute = args.get_pos(1);
|
||||
auto out = mk_val<value_array>();
|
||||
value val_default = mk_val<value_undefined>();
|
||||
|
||||
if (args.count() == 2) {
|
||||
// example: array | selectattr("active")
|
||||
for (const auto & item : arr) {
|
||||
value attr_val = get_attribute(item, attribute, val_default);
|
||||
bool is_selected = attr_val->as_bool();
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
for (const auto & item : arr) {
|
||||
value val = item;
|
||||
if constexpr (lookup_attr) {
|
||||
val = get_attribute(item, attr, val_default);
|
||||
}
|
||||
return out;
|
||||
|
||||
} else if (args.count() == 3) {
|
||||
// example: array | selectattr("equalto", "text")
|
||||
// translated to: test_is_equalto(item, "text")
|
||||
std::string test_name = args.get_pos(1)->as_string().str();
|
||||
value test_val = args.get_pos(2);
|
||||
auto & builtins = global_builtins();
|
||||
auto it = builtins.find("test_is_" + test_name);
|
||||
if (it == builtins.end()) {
|
||||
throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
||||
}
|
||||
auto test_fn = it->second;
|
||||
for (const auto & item : arr) {
|
||||
bool is_selected;
|
||||
if (test_it == builtins.end()) {
|
||||
is_selected = val->as_bool();
|
||||
} else {
|
||||
func_args test_args(args.ctx);
|
||||
test_args.push_back(item); // current object
|
||||
test_args.push_back(test_val); // extra argument
|
||||
value test_result = test_fn(test_args);
|
||||
bool is_selected = test_result->as_bool();
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
test_args.push_back(val);
|
||||
for (size_t i = test_pos + 1; i < args.count(); i++) {
|
||||
test_args.push_back(args.get_pos(i));
|
||||
}
|
||||
is_selected = test_it->second(test_args)->as_bool();
|
||||
}
|
||||
return out;
|
||||
|
||||
} else if (args.count() == 4) {
|
||||
// example: array | selectattr("status", "equalto", "active")
|
||||
// translated to: test_is_equalto(item.status, "active")
|
||||
std::string test_name = args.get_pos(2)->as_string().str();
|
||||
auto extra_arg = args.get_pos(3);
|
||||
auto & builtins = global_builtins();
|
||||
auto it = builtins.find("test_is_" + test_name);
|
||||
if (it == builtins.end()) {
|
||||
throw raised_exception("selectattr: unknown test '" + test_name + "'");
|
||||
}
|
||||
auto test_fn = it->second;
|
||||
for (const auto & item : arr) {
|
||||
value attr_val = get_attribute(item, attribute, val_default);
|
||||
func_args test_args(args.ctx);
|
||||
test_args.push_back(attr_val); // attribute value
|
||||
test_args.push_back(extra_arg); // extra argument
|
||||
value test_result = test_fn(test_args);
|
||||
bool is_selected = test_result->as_bool();
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
}
|
||||
return out;
|
||||
} else {
|
||||
throw raised_exception("selectattr: invalid number of arguments");
|
||||
if constexpr (is_reject) is_selected = !is_selected;
|
||||
if (is_selected) out->push_back(item);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1095,10 +1074,10 @@ const func_builtins & value_array_t::get_builtins() const {
|
||||
auto arr = slice(val->as_array(), start, stop, step);
|
||||
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
|
||||
}},
|
||||
{"selectattr", selectattr<false>},
|
||||
{"select", selectattr<false>},
|
||||
{"rejectattr", selectattr<true>},
|
||||
{"reject", selectattr<true>},
|
||||
{"selectattr", select_or_reject<false, true>},
|
||||
{"select", select_or_reject<false, false>},
|
||||
{"rejectattr", select_or_reject<true, true>},
|
||||
{"reject", select_or_reject<true, false>},
|
||||
{"join", [](const func_args & args) -> value {
|
||||
args.ensure_count(1, 3);
|
||||
if (!is_val<value_array>(args.get_pos(0))) {
|
||||
|
||||
+53
-10
@@ -1,4 +1,5 @@
|
||||
#include "parsers.h"
|
||||
#include "log.h"
|
||||
|
||||
// The DeepSeek V4 reference implementation renders consecutive tool results into a single
|
||||
// user block, ordered by the tool call order of the preceding assistant message (matched
|
||||
@@ -75,13 +76,30 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
// non-thinking generation prompt ends with a bare </think> instead of an empty
|
||||
// <think></think> pair.
|
||||
const bool is_v4 = tmpl.source().find("function_calls") == std::string::npos;
|
||||
// V4.1: DSML tag names carry a leading space, e.g. <|DSML| calls>
|
||||
const bool is_v41 = is_v4 && tmpl.source().find("|DSML| calls>") != std::string::npos;
|
||||
|
||||
std::optional<json> adjusted_messages;
|
||||
if (is_v4) {
|
||||
adjusted_messages = deepseek_v4_sort_tool_results(inputs.messages);
|
||||
}
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
// V4.1 can declare tools on a system message instead of top-level "tools": parse calls to those too.
|
||||
json tools = inputs.tools;
|
||||
if (is_v41 && !(tools.is_array() && !tools.empty()) && inputs.messages.is_array()) {
|
||||
for (const auto & msg : inputs.messages) {
|
||||
if (msg.value("role", "") == "system" && msg.contains("tools") && msg.at("tools").is_array()) {
|
||||
if (!tools.is_array()) {
|
||||
tools = json::array();
|
||||
}
|
||||
for (const auto & tool : msg.at("tools")) {
|
||||
tools.push_back(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto has_tools = tools.is_array() && !tools.empty();
|
||||
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
|
||||
@@ -90,17 +108,39 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
if (is_v4 && has_response_format) {
|
||||
additional_context = json{ { "response_format", inputs.json_schema } };
|
||||
}
|
||||
// V4.1 raises on anything but low/high/max or 1-100: map minimal -> low, xhigh -> max, medium -> default
|
||||
if (is_v41 && inputs.extra_context.is_object() && inputs.extra_context.contains("reasoning_effort") &&
|
||||
inputs.extra_context.at("reasoning_effort").is_string()) {
|
||||
const std::string effort = inputs.extra_context.at("reasoning_effort").get<std::string>();
|
||||
json mapped;
|
||||
if (effort == "minimal") {
|
||||
mapped = "low";
|
||||
} else if (effort == "xhigh") {
|
||||
mapped = "max";
|
||||
} else if (effort == "medium") {
|
||||
mapped = nullptr;
|
||||
}
|
||||
if (effort == "minimal" || effort == "xhigh" || effort == "medium") {
|
||||
LOG_DBG("%s: DeepSeek V4.1 reasoning_effort '%s' -> %s\n", __func__, effort.c_str(),
|
||||
mapped.is_null() ? "unset (template default)" : mapped.get<std::string>().c_str());
|
||||
if (!additional_context.has_value()) {
|
||||
additional_context = json::object();
|
||||
}
|
||||
(*additional_context)["reasoning_effort"] = mapped;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string DSML = "|DSML|";
|
||||
const std::string THINK_START = "<think>";
|
||||
const std::string THINK_END = "</think>";
|
||||
const std::string TC_BLOCK = is_v4 ? "tool_calls" : "function_calls";
|
||||
const std::string FC_START = "<" + DSML + TC_BLOCK + ">";
|
||||
const std::string FC_END = "</" + DSML + TC_BLOCK + ">";
|
||||
const std::string INVOKE_START = "<" + DSML + "invoke";
|
||||
const std::string INVOKE_END = "</" + DSML + "invoke>";
|
||||
const std::string PARAM_START = "<" + DSML + "parameter";
|
||||
const std::string PARAM_END = "</" + DSML + "parameter>";
|
||||
const std::string TAG_SP = is_v41 ? " " : "";
|
||||
const std::string TC_BLOCK = is_v41 ? "calls" : is_v4 ? "tool_calls" : "function_calls";
|
||||
const std::string FC_START = "<" + DSML + TAG_SP + TC_BLOCK + ">";
|
||||
const std::string FC_END = "</" + DSML + TAG_SP + TC_BLOCK + ">";
|
||||
const std::string INVOKE_START = "<" + DSML + TAG_SP + "invoke";
|
||||
const std::string INVOKE_END = "</" + DSML + TAG_SP + "invoke>";
|
||||
const std::string PARAM_START = "<" + DSML + TAG_SP + "parameter";
|
||||
const std::string PARAM_END = "</" + DSML + TAG_SP + "parameter>";
|
||||
const std::string GEN_PROMPT = "<|Assistant|>";
|
||||
const std::string TC_SEPARATOR = "\n\n";
|
||||
|
||||
@@ -152,7 +192,7 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
// build tool call section first since we might need it in reasoning
|
||||
auto tool_choice = p.choice();
|
||||
if (has_tool_calls) {
|
||||
foreach_function(inputs.tools, [&](size_t tool_index, const json & tool) {
|
||||
foreach_function(tools, [&](size_t tool_index, const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
std::string name = function.at("name");
|
||||
|
||||
@@ -223,7 +263,10 @@ common_chat_params common_chat_params_init_deepseek_v3_2(const common_chat_templ
|
||||
}
|
||||
|
||||
if (extract_reasoning && inputs.enable_thinking) {
|
||||
reasoning = p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
|
||||
// V4.1 renders a continuation without reasoning as "<|Assistant|></think>" even in thinking mode,
|
||||
// so consume the bare THINK_END too or it leaks into the content
|
||||
reasoning = is_v41 ? p.optional((THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END) | p.literal(THINK_END))
|
||||
: p.optional(THINK_START + p.reasoning(p.until(THINK_END)) + THINK_END);
|
||||
reasoning_with_tc = THINK_START +
|
||||
p.reasoning(p.until_one_of({ TC_SEPARATOR + FC_START, FC_START, THINK_END })) +
|
||||
p.space() + obligatory_tool_calls;
|
||||
|
||||
@@ -70,6 +70,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"LingDSparkModel": "qwen",
|
||||
"DeepseekV4ForCausalLM": "deepseek",
|
||||
"DeepseekV4DSparkModel": "deepseek",
|
||||
"DeepseekV41ForCausalLM": "deepseek41",
|
||||
"DistilBertForMaskedLM": "bert",
|
||||
"DistilBertForSequenceClassification": "bert",
|
||||
"DistilBertModel": "bert",
|
||||
@@ -286,6 +287,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"SeedOssForCausalLM": "olmo",
|
||||
"SmallThinkerForCausalLM": "smallthinker",
|
||||
"SmolLM3ForCausalLM": "llama",
|
||||
"SopranoTextModel": "soprano",
|
||||
"Spark2_5ForCausalLM": "spark2_5",
|
||||
"SolarOpenForCausalLM": "glm",
|
||||
"StableLMEpochForCausalLM": "stablelm",
|
||||
@@ -325,6 +327,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"VllmD3Model": "vllm_d3",
|
||||
"DeepseekOCR2ForCausalLM": "deepseek",
|
||||
"DeepseekOCRForCausalLM": "deepseek",
|
||||
"DeepseekV41ForCausalLM": "deepseek41",
|
||||
"DeepseekV4ForCausalLM": "deepseek",
|
||||
"Dots3NoteForCausalLM": "dots3",
|
||||
"Dots3NoteForConditionalGeneration": "dots3",
|
||||
@@ -380,6 +383,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
|
||||
"Qwen3ASRForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3OmniMoeForConditionalGeneration": "qwen3vl",
|
||||
"PocketTTSModel": "pockettts",
|
||||
"SopranoModel": "soprano",
|
||||
"Qwen3TTSForConditionalGeneration": "qwen3tts",
|
||||
"Qwen3VLForConditionalGeneration": "qwen3vl",
|
||||
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING, cast
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import LazyTorchTensor, ModelBase, TextModel, gguf
|
||||
from .deepseek import DeepseekV4FlashVisionModel, DeepseekV4Model
|
||||
|
||||
|
||||
# Engram hash constants from inference/engram.py, stored in the GGUF: the token map needs the HF tokenizers
|
||||
# normalizers and the multipliers numpy's PCG64 stream, neither of which the runtime has.
|
||||
|
||||
def _is_prime(n: int) -> bool:
|
||||
if n < 2 or n % 2 == 0:
|
||||
return n == 2
|
||||
return all(n % d for d in range(3, math.isqrt(n) + 1, 2))
|
||||
|
||||
|
||||
def engram_primes(n_layers: int, max_ngram_size: int, n_heads: int, vocab_size: int) -> list[list[list[int]]]:
|
||||
"""EngramLayout.from_args: [layer][ngram order 2..max][head] bucket moduli, never reused across layers."""
|
||||
seen: set[int] = set()
|
||||
out: list[list[list[int]]] = []
|
||||
for _ in range(n_layers):
|
||||
per_ngram = []
|
||||
for _ in range(max_ngram_size - 1):
|
||||
sizes, cur = [], vocab_size - 1
|
||||
for _ in range(n_heads):
|
||||
cand = cur + 1
|
||||
while not _is_prime(cand) or cand in seen:
|
||||
cand += 1
|
||||
cur = cand
|
||||
seen.add(cur)
|
||||
sizes.append(cur)
|
||||
per_ngram.append(sizes)
|
||||
out.append(per_ngram)
|
||||
return out
|
||||
|
||||
|
||||
def engram_multipliers(layer_ids: list[int], max_ngram_size: int, compressed_vocab_size: int) -> list[list[int]]:
|
||||
"""compute_hash_multipliers: odd, bounded so that token_id * multiplier cannot overflow int64."""
|
||||
bound = max(1, (np.iinfo(np.int64).max // compressed_vocab_size) // 2)
|
||||
rows = []
|
||||
for layer_id in layer_ids:
|
||||
rng = np.random.default_rng(10007 * layer_id)
|
||||
values = rng.integers(low=0, high=bound, size=(max_ngram_size,), dtype=np.int64)
|
||||
rows.append([int(v) * 2 + 1 for v in values])
|
||||
return rows
|
||||
|
||||
|
||||
def engram_compressed_token_map(tokenizer_dir: Path) -> tuple[list[int], int]:
|
||||
"""build_compressed_token_map: token id -> compressed id (tokens that normalize alike collapse)."""
|
||||
from tokenizers import Regex, normalizers
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(tokenizer_dir)
|
||||
sentinel = "\ue000"
|
||||
normalizer = normalizers.Sequence([
|
||||
normalizers.NFKC(),
|
||||
normalizers.NFD(),
|
||||
normalizers.StripAccents(),
|
||||
normalizers.Lowercase(),
|
||||
normalizers.Replace(Regex(r"[ \t\r\n]+"), " "),
|
||||
normalizers.Replace(Regex(r"^ $"), sentinel),
|
||||
normalizers.Strip(),
|
||||
normalizers.Replace(sentinel, " "),
|
||||
])
|
||||
backend = tokenizer.backend_tokenizer
|
||||
key_to_new: dict[str, int] = {}
|
||||
lookup = [0] * len(tokenizer)
|
||||
for token_id in range(len(tokenizer)):
|
||||
text = backend.decode([token_id], skip_special_tokens=False)
|
||||
if "\ufffd" in text:
|
||||
key = backend.id_to_token(token_id)
|
||||
else:
|
||||
normalized = normalizer.normalize_str(text)
|
||||
key = normalized if normalized else text
|
||||
lookup[token_id] = key_to_new.setdefault(key, len(key_to_new))
|
||||
return lookup, len(key_to_new)
|
||||
|
||||
|
||||
class _StreamedBytes:
|
||||
"""Raw tensor bytes produced chunk by chunk at write time (an Engram table is ~200 GB in BF16)."""
|
||||
def __init__(self, shape: tuple[int, ...], chunks: Callable[[], Iterable[np.ndarray]]):
|
||||
self.shape = shape
|
||||
self.dtype = np.dtype(np.uint8)
|
||||
self.nbytes = int(np.prod(shape))
|
||||
self._chunks = chunks
|
||||
|
||||
def tofile(self, fout) -> None:
|
||||
written = 0
|
||||
for chunk in self._chunks():
|
||||
chunk = np.ascontiguousarray(chunk, dtype=np.uint8)
|
||||
chunk.tofile(fout)
|
||||
written += chunk.nbytes
|
||||
if written != self.nbytes:
|
||||
raise ValueError(f"streamed {written} bytes, expected {self.nbytes}")
|
||||
|
||||
def byteswap(self, inplace: bool = False):
|
||||
raise NotImplementedError("streamed tensors are written little-endian only")
|
||||
|
||||
|
||||
def _e8m0_check(scale_u8: np.ndarray, name: str) -> None:
|
||||
# e4m3 (|x| in [2^-9, 448]) * 2^(e - 127) is exact in BF16 for e in [3, 246]
|
||||
if scale_u8.size and (int(scale_u8.min()) < 3 or int(scale_u8.max()) > 246):
|
||||
raise ValueError(f"{name}: E8M0 scale exponents outside [3, 246], the dequantized values would not be exact")
|
||||
|
||||
|
||||
@ModelBase.register("DeepseekV41ForCausalLM")
|
||||
class DeepseekV41Model(DeepseekV4Model):
|
||||
"""DeepSeek-V4.1 text model, lossless: FP8 (e4m3, 32x32 ue8m0 blocks) linears dequantized to BF16 (F32 with
|
||||
--outtype f32), routed experts copied bit-exact as MXFP4, Engram tables (e4m3 rows, one e8m0 scale per 32) as the
|
||||
exact BF16 lookup (F32 with --outtype f32), router and Engram gate F32. DSpark (mtp.*) is not converted here
|
||||
(--dspark, DeepseekV41DSparkModel); the vision tower goes to an mmproj (--mmproj, DeepseekV41VisionModel).
|
||||
The Engram tables of all layers are written as one per_layer_token_embd (rows concatenated in layer order) with
|
||||
the PLE keys of qwen4exp: absolute head offsets and bucket counts, so the runtime can read it lazily."""
|
||||
model_arch = gguf.MODEL_ARCH.DEEPSEEK41
|
||||
supports_mtp_export = False
|
||||
|
||||
_ENGRAM_EMBED_RE = re.compile(r"layers\.(\d+)\.engram\.embed\.(weight|scale)$")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.hparams.setdefault("num_hash_layers", 0) # no hash-routed layers in V4.1
|
||||
self._f32 = self.ftype == gguf.LlamaFileType.ALL_F32
|
||||
qc = self.hparams.get("quantization_config") or {}
|
||||
self._fp8_block = tuple(qc.get("weight_block_size") or (32, 32))
|
||||
if qc.get("scale_fmt", "ue8m0") != "ue8m0":
|
||||
raise ValueError(f"unsupported FP8 scale format {qc.get('scale_fmt')!r}")
|
||||
if self.hparams.get("gate_temp", 1.0) != 1.0:
|
||||
raise ValueError("gate_temp != 1 is not supported (the router logits would need dividing by it)")
|
||||
|
||||
# V4.1 does not use the DeepSeek-V4 template; a template shipped with the model is still added by set_vocab
|
||||
self.gguf_writer.remove_key(gguf.Keys.Tokenizer.CHAT_TEMPLATE)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
# num_hidden_layers lives in text_config, which is merged only after indexing; MTP stages are skipped anyway
|
||||
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
if item[0].startswith("vision."):
|
||||
return None
|
||||
return super().filter_tensors(item)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hp = self.hparams
|
||||
w = self.gguf_writer
|
||||
w.add_attention_kv_source_layers(hp["kv_source_layer_ids"])
|
||||
w.add_indexer_source_layers(hp["index_source_layer_ids"])
|
||||
if hp.get("candidate_source_layer_id", -1) >= 0:
|
||||
w.add_indexer_candidate_source_layer(hp["candidate_source_layer_id"])
|
||||
w.add_indexer_candidate_top_k_blocks(hp["candidate_topk_blocks"])
|
||||
w.add_indexer_candidate_block_size(hp["candidate_block_size"])
|
||||
|
||||
# GGUF cannot hold empty arrays: the Engram keys are omitted when the model has no Engram layers
|
||||
layer_ids = list(hp.get("engram_layer_ids") or [])
|
||||
if layer_ids:
|
||||
max_ngram, n_heads = hp["engram_max_ngram_size"], hp["engram_n_heads"]
|
||||
primes = engram_primes(len(layer_ids), max_ngram, n_heads, hp["engram_vocab_size"])
|
||||
for li, layer_primes in enumerate(primes):
|
||||
if sum(p for per in layer_primes for p in per) != hp["engram_num_embeddings"][li]:
|
||||
raise ValueError(f"engram layer {layer_ids[li]}: prime sum != num_embeddings")
|
||||
token_map, cvocab = engram_compressed_token_map(self.dir_model)
|
||||
if cvocab != hp["engram_compressed_vocab_size"] or len(token_map) != hp["vocab_size"]:
|
||||
raise ValueError(f"compressed token map ({len(token_map)} -> {cvocab}) does not match the config")
|
||||
# absolute row offsets into the concatenated table
|
||||
sizes = [p for layer in primes for per in layer for p in per]
|
||||
offsets = [sum(sizes[:i]) for i in range(len(sizes))]
|
||||
w.add_key_value(gguf.Keys.PerLayerEmbedding.LAYERS.format(arch=w.arch), layer_ids,
|
||||
gguf.GGUFValueType.ARRAY, gguf.GGUFValueType.UINT32)
|
||||
w.add_ple_ngram_size(max_ngram)
|
||||
w.add_ple_heads_per_ngram(n_heads)
|
||||
w.add_embedding_length_per_layer_input(hp["engram_head_dim"])
|
||||
w.add_ple_layer_multipliers([m for row in engram_multipliers(layer_ids, max_ngram, cvocab) for m in row])
|
||||
w.add_ple_head_offsets(offsets)
|
||||
w.add_ple_head_vocab_sizes(sizes)
|
||||
w.add_engram_pad_token_id(hp["engram_pad_token_id"])
|
||||
w.add_engram_token_map(token_map)
|
||||
|
||||
def dequant_model(self):
|
||||
bo, bi = self._fp8_block
|
||||
|
||||
def dequant(weight: Tensor, scale: Tensor) -> Tensor:
|
||||
out_f, in_f = weight.shape
|
||||
s = self._e8m0_to_float(scale).repeat_interleave(bo, 0)[:out_f].repeat_interleave(bi, 1)[:, :in_f]
|
||||
return weight.float() * s
|
||||
|
||||
for name in [n for n in self.model_tensors if n.endswith(".scale") and not self._ENGRAM_EMBED_RE.match(n)]:
|
||||
weight_name = name.removesuffix(".scale") + ".weight"
|
||||
if weight_name not in self.model_tensors or self.model_tensors[weight_name]().dtype not in self._float8_dtypes():
|
||||
continue
|
||||
w, s = self.model_tensors[weight_name], self.model_tensors.pop(name)
|
||||
out_f, in_f = w().shape
|
||||
if tuple(s().shape) != (-(-out_f // bo), -(-in_f // bi)):
|
||||
raise ValueError(f"{weight_name}: scale shape {tuple(s().shape)} does not match {(bo, bi)} blocks")
|
||||
_e8m0_check(LazyTorchTensor.to_eager(s()).view(torch.uint8).numpy(), name)
|
||||
self.model_tensors[weight_name] = lambda w=w, s=s: dequant(w(), s())
|
||||
self._dsv4_fp8_dequantized.add(weight_name)
|
||||
|
||||
def _tensor_part(self, name: str) -> Path:
|
||||
index = self.dir_model / "model.safetensors.index.json"
|
||||
if not index.is_file():
|
||||
return self.dir_model / "model.safetensors"
|
||||
with open(index, "r", encoding="utf-8") as f:
|
||||
return self.dir_model / json.load(f)["weight_map"][name]
|
||||
|
||||
def _write_mxfp4_experts(self, bid: int, proj: str, key: gguf.MODEL_TENSOR, n_expert: int | None = None) -> None:
|
||||
n_expert = self.hparams["n_routed_experts"] if n_expert is None else n_expert
|
||||
names = [f"layers.{bid}.ffn.experts.{e}.{proj}" for e in range(n_expert)]
|
||||
pairs = [(self.model_tensors.pop(n + ".weight"), self.model_tensors.pop(n + ".scale")) for n in names]
|
||||
rows, packed_cols = pairs[0][0]().shape
|
||||
|
||||
def chunks() -> Iterable[np.ndarray]:
|
||||
for w, s in pairs:
|
||||
yield self.repack_mxfp4_blocks(LazyTorchTensor.to_eager(w()), LazyTorchTensor.to_eager(s()))
|
||||
data = cast(np.ndarray, _StreamedBytes((len(pairs), rows, packed_cols * 2 // 32 * 17), chunks))
|
||||
self.gguf_writer.add_tensor(self.format_tensor_name(key, bid), data, raw_dtype=gguf.GGMLQuantizationType.MXFP4)
|
||||
|
||||
def _write_engram_tables(self, layer_ids: list[int]) -> None:
|
||||
# official lookup e4m3 * 2^(e8m0 - 127), exact in BF16
|
||||
parts, dim = [], None
|
||||
for bid in layer_ids:
|
||||
wn, sn = f"layers.{bid}.engram.embed.weight", f"layers.{bid}.engram.embed.scale"
|
||||
rows, d = self.model_tensors.pop(wn)().shape
|
||||
if d % 32 != 0 or tuple(self.model_tensors.pop(sn)().shape) != (rows, d // 32) or d != (dim or d):
|
||||
raise ValueError(f"{wn}: expected one e8m0 scale per 32 elements and the same width in every layer")
|
||||
if rows != self.hparams["engram_num_embeddings"][len(parts)]:
|
||||
raise ValueError(f"{wn}: {rows} rows, config says {self.hparams['engram_num_embeddings'][len(parts)]}")
|
||||
dim = d
|
||||
parts.append((wn, sn, rows, self._tensor_part(wn), self._tensor_part(sn)))
|
||||
assert dim is not None
|
||||
name = self.format_tensor_name(gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD)
|
||||
n_rows = sum(p[2] for p in parts)
|
||||
|
||||
def chunks() -> Iterable[np.ndarray]:
|
||||
from safetensors import safe_open
|
||||
for wn, sn, rows, wpart, spart in parts:
|
||||
with safe_open(wpart, framework="pt") as fw, safe_open(spart, framework="pt") as fs:
|
||||
ws, ss = fw.get_slice(wn), fs.get_slice(sn)
|
||||
for r0 in range(0, rows, 1 << 18):
|
||||
q = ws[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 32)
|
||||
e = ss[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 1)
|
||||
_e8m0_check(e.numpy(), sn)
|
||||
v = (q.view(torch.float8_e4m3fn).float() * torch.exp2(e.float() - 127)).reshape(-1, dim)
|
||||
yield (v if self._f32 else v.bfloat16()).view(torch.uint8).numpy()
|
||||
qt = gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
|
||||
data = cast(np.ndarray, _StreamedBytes((n_rows, dim * (4 if self._f32 else 2)), chunks))
|
||||
self.gguf_writer.add_tensor(name, data, raw_dtype=qt)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
if not self._dsv4_mxfp4_generated:
|
||||
for bid in range(self.block_count):
|
||||
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP)
|
||||
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP)
|
||||
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP)
|
||||
if self.hparams.get("engram_layer_ids"):
|
||||
self._write_engram_tables(list(self.hparams["engram_layer_ids"]))
|
||||
self._dsv4_mxfp4_generated = True
|
||||
return ()
|
||||
|
||||
_LAYER_MAP: dict[str, tuple[gguf.MODEL_TENSOR, str]] = {
|
||||
"attn.indexer.wk.weight": (gguf.MODEL_TENSOR.INDEXER_ATTN_K, ".weight"),
|
||||
"attn.indexer.k_norm.weight": (gguf.MODEL_TENSOR.INDEXER_K_NORM, ".weight"),
|
||||
"engram.wkv.weight": (gguf.MODEL_TENSOR.ENGRAM_KV, ".weight"),
|
||||
}
|
||||
|
||||
def _map_dsv4_tensor_name(self, name: str, bid: int | None) -> tuple[gguf.MODEL_TENSOR, str]:
|
||||
m = re.match(r"layers\.\d+\.(.+)$", name)
|
||||
if m is not None and m.group(1) in self._LAYER_MAP:
|
||||
return self._LAYER_MAP[m.group(1)]
|
||||
return super()._map_dsv4_tensor_name(name, bid)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(".engram.k_weight"):
|
||||
return [] # folded into engram_gate
|
||||
if name.endswith(".engram.q_weight"):
|
||||
# Engram gate weight = q_weight * k_weight (bf16 * bf16 is exact in f32)
|
||||
k = self.model_tensors[name.removesuffix("q_weight") + "k_weight"]
|
||||
return [(self.format_tensor_name(gguf.MODEL_TENSOR.ENGRAM_GATE, bid), data_torch.float() * k().float())]
|
||||
return super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
if new_name.endswith((".engram_gate.weight", ".ffn_gate_inp.weight")) or name in self._dsv4_f32_tensors:
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
if n_dims >= 2 and (name in self._dsv4_fp8_dequantized or name in self._dsv4_bf16_tensors):
|
||||
return gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
|
||||
return False
|
||||
|
||||
|
||||
class DeepseekV41DSparkModel(DeepseekV41Model):
|
||||
"""DeepSeek-V4.1 DSpark speculative draft (mtp.0..2 of the checkpoint) as a DFLASH GGUF (`--dspark`).
|
||||
|
||||
Layout = the V4 DSpark draft (conversion/deepseek.py DeepseekV4DSparkModel) with V4.1's differences flagged by
|
||||
`dflash.hyper_connection.single_pass`: stages blk.0..2 (MXFP4 experts bit-exact, dense BF16), mtp.0.main_proj /
|
||||
main_norm -> fc / enc.output_norm, mtp.2.norm -> output_norm, markov_head.embed / head -> markov_w1 / markov_w2,
|
||||
confidence_head.proj -> conf_proj (F32), no hc_head. Token embeddings and the lm head are the target's (shared at
|
||||
runtime through ctx_other). target_layers are the V4.1 ids as-is: the draft reads the attention input of those
|
||||
layers, which is what llama_set_embeddings_layer_inp extracts.
|
||||
"""
|
||||
model_arch = gguf.MODEL_ARCH.DFLASH
|
||||
mtp_only = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hp = self.hparams
|
||||
self._n_main = hp["num_hidden_layers"]
|
||||
self._n_stage = hp["num_nextn_predict_layers"]
|
||||
if self._n_stage <= 0 or "dspark_block_size" not in hp:
|
||||
raise ValueError("DeepSeek-V4.1 DSpark export needs num_nextn_predict_layers > 0 and the dspark_* config")
|
||||
# stages keep main-model block ids (mtp.M -> layers.n_main+M); format_tensor_name() maps them back
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self._n_stage)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
|
||||
hp = {**self.hparams, **self.hparams.get("text_config", {})} # text_config is merged only after indexing
|
||||
type(self)._dsv4_main_layers = hp["num_hidden_layers"]
|
||||
type(self)._dsv4_nextn_layers = hp.get("num_nextn_predict_layers", 0)
|
||||
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
m = re.match(r"mtp\.(\d+)\.(.+)$", name)
|
||||
if m is None:
|
||||
return None # shared with the target at runtime
|
||||
stage, rest = int(m.group(1)), m.group(2)
|
||||
assert cls._dsv4_main_layers is not None
|
||||
if stage >= cls._dsv4_nextn_layers:
|
||||
raise ValueError(f"Unexpected DSpark stage {stage}")
|
||||
if rest.startswith(("markov_head.", "confidence_head.")):
|
||||
return f"dspark.{rest}", gen
|
||||
return f"layers.{cls._dsv4_main_layers + stage}.{rest}", gen
|
||||
|
||||
def format_tensor_name(self, key: gguf.MODEL_TENSOR, bid: int | None = None, suffix: str = ".weight") -> str:
|
||||
if bid is not None and bid >= self._n_main:
|
||||
bid -= self._n_main
|
||||
return super().format_tensor_name(key, bid, suffix)
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
self.gguf_writer.add_mask_token_id(self.hparams["dspark_noise_token_id"])
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
hp = self.hparams
|
||||
n = self._n_stage
|
||||
w = self.gguf_writer
|
||||
w.add_block_count(n)
|
||||
w.add_context_length(hp["max_position_embeddings"])
|
||||
w.add_embedding_length(hp["hidden_size"])
|
||||
w.add_feed_forward_length(hp["moe_intermediate_size"])
|
||||
w.add_head_count(hp["num_attention_heads"])
|
||||
w.add_head_count_kv(1)
|
||||
w.add_key_length(hp["head_dim"])
|
||||
w.add_value_length(hp["head_dim"])
|
||||
w.add_rope_dimension_count(hp["qk_rope_head_dim"])
|
||||
# compress-ratio-0 layers: plain rope at rope_theta, no YaRN
|
||||
w.add_rope_freq_base(float(hp["rope_theta"]))
|
||||
w.add_layer_norm_rms_eps(hp["rms_norm_eps"])
|
||||
w.add_q_lora_rank(hp["q_lora_rank"])
|
||||
w.add_sliding_window(hp["sliding_window"])
|
||||
w.add_expert_count(hp["dspark_n_routed_experts"])
|
||||
w.add_expert_used_count(hp["dspark_num_experts_per_tok"])
|
||||
w.add_expert_feed_forward_length(hp["moe_intermediate_size"])
|
||||
w.add_expert_shared_count(hp["n_shared_experts"])
|
||||
w.add_expert_weights_scale(hp["routed_scaling_factor"])
|
||||
w.add_expert_weights_norm(hp["norm_topk_prob"])
|
||||
w.add_expert_gating_func(gguf.ExpertGatingFuncType.SQRTSOFTPLUS)
|
||||
w.add_swiglu_clamp_exp([hp["swiglu_limit"]] * n)
|
||||
w.add_swiglu_clamp_shexp([hp["swiglu_limit"]] * n)
|
||||
w.add_attention_output_group_count(hp["o_groups"])
|
||||
w.add_attention_output_lora_rank(hp["o_lora_rank"])
|
||||
w.add_attention_compress_ratios([0] * n)
|
||||
w.add_hyper_connection_count(hp["hc_mult"])
|
||||
w.add_hyper_connection_sinkhorn_iterations(hp["hc_sinkhorn_iters"])
|
||||
w.add_hyper_connection_epsilon(hp["hc_eps"])
|
||||
w.add_hyper_connection_single_pass(True)
|
||||
w.add_block_size(hp["dspark_block_size"])
|
||||
w.add_target_layers(hp["dspark_target_layer_ids"])
|
||||
w.add_sample_from_anchor(True)
|
||||
w.add_has_confidence_head(True)
|
||||
w.add_file_type(self.ftype)
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
# no Engram tables in the draft
|
||||
if not self._dsv4_mxfp4_generated:
|
||||
n_expert = self.hparams["dspark_n_routed_experts"]
|
||||
for bid in range(self._n_main, self._n_main + self._n_stage):
|
||||
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP, n_expert)
|
||||
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP, n_expert)
|
||||
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP, n_expert)
|
||||
self._dsv4_mxfp4_generated = True
|
||||
return ()
|
||||
|
||||
_DSPARK_ROOT: dict[str, gguf.MODEL_TENSOR] = {
|
||||
"main_proj.weight": gguf.MODEL_TENSOR.FC,
|
||||
"main_norm.weight": gguf.MODEL_TENSOR.ENC_OUTPUT_NORM,
|
||||
"norm.weight": gguf.MODEL_TENSOR.OUTPUT_NORM,
|
||||
"dspark.markov_head.embed.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W1,
|
||||
"dspark.markov_head.head.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W2,
|
||||
"dspark.confidence_head.proj.weight": gguf.MODEL_TENSOR.DSPARK_CONF_PROJ,
|
||||
}
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.endswith(".ffn.gate.bias_vl"):
|
||||
return []
|
||||
if name in self._DSPARK_ROOT:
|
||||
return [(self.format_tensor_name(self._DSPARK_ROOT[name], None), data_torch)]
|
||||
m = re.match(r"layers\.\d+\.(main_proj\.weight|main_norm\.weight|norm\.weight)$", name)
|
||||
if m is not None:
|
||||
return [(self.format_tensor_name(self._DSPARK_ROOT[m.group(1)], None), data_torch)]
|
||||
return super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
if new_name == "conf_proj.weight":
|
||||
return gguf.GGMLQuantizationType.F32
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
|
||||
@ModelBase.register("DeepseekV41ForCausalLM")
|
||||
@ModelBase.example("deepseek-ai/DeepSeek-V4.1-Flash-1017")
|
||||
class DeepseekV41VisionModel(DeepseekV4FlashVisionModel):
|
||||
"""DeepSeek-V4.1 vision tower (projector deepseek41v): the V4-Flash-Vision ViT + 3x3 aligner, with the V4.1 token
|
||||
block ([START] ([IMAGE]*w [NEWLINE])*h [END], no pads) and resize plan. ref: inference/vision.py,
|
||||
inference/image_processor.py of deepseek-ai/DeepSeek-V4.1-Flash-1017. The checkpoint config is HF-style
|
||||
(vision_config); image_start / image_end / image_newline are written like V4's sentinels, there is no image_pad."""
|
||||
|
||||
def get_vision_config(self) -> dict[str, Any] | None:
|
||||
vc = self.global_config.get("vision_config")
|
||||
if not vc or vc.get("num_hidden_layers", 0) == 0:
|
||||
raise ValueError("DeepseekV41VisionModel requires vision_config.num_hidden_layers > 0")
|
||||
return {
|
||||
"num_hidden_layers": vc["num_hidden_layers"],
|
||||
"hidden_size": vc["hidden_size"],
|
||||
"num_attention_heads": vc["num_attention_heads"],
|
||||
"intermediate_size": vc["intermediate_size"],
|
||||
"patch_size": vc["patch_size"],
|
||||
# dynamic resolution; only used for compat / warmup
|
||||
"image_size": vc["patch_size"] * vc["downsample_ratio"] * 16,
|
||||
"rope_theta": vc.get("rope_theta", 10000.0),
|
||||
"downsample_ratio": vc["downsample_ratio"],
|
||||
"min_pixels": vc["min_pixels"],
|
||||
"max_image_tokens": vc["max_image_tokens"],
|
||||
"max_wh_ratio": vc.get("max_wh_ratio"),
|
||||
}
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
assert self.hparams_vision is not None
|
||||
super(DeepseekV4FlashVisionModel, self).set_gguf_parameters()
|
||||
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.DEEPSEEK41V)
|
||||
# pytorch default eps (vision.py), not the LLM rms_norm_eps (1e-20)
|
||||
self.gguf_writer.add_vision_attention_layernorm_eps(1e-6)
|
||||
self.gguf_writer.add_vision_use_silu(True)
|
||||
self.gguf_writer.add_vision_projector_scale_factor(self.hparams_vision["downsample_ratio"])
|
||||
self.gguf_writer.add_vision_min_pixels(self.hparams_vision["min_pixels"])
|
||||
# hardcoded in clip.cpp (PROJECTOR_TYPE_DEEPSEEK41V); add GGUF keys if a later model differs
|
||||
assert self.hparams_vision["max_image_tokens"] == 1024, self.hparams_vision["max_image_tokens"]
|
||||
assert self.hparams_vision["max_wh_ratio"] is None, self.hparams_vision["max_wh_ratio"]
|
||||
assert self.hparams_vision["rope_theta"] == 10000, self.hparams_vision["rope_theta"]
|
||||
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
from .base import ModelBase, MmprojModel, gguf, logger
|
||||
from .qwen import Qwen3Model
|
||||
|
||||
|
||||
def _is_soprano_checkpoint(dir_model: Path) -> bool:
|
||||
if not (dir_model / "decoder.pth").is_file() or not (dir_model / "config.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "config.json", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
if config.get("model_type") != "qwen3" or config.get("bos_token_id") != 3 or config.get("eos_token_id") != 3:
|
||||
return False
|
||||
if not (dir_model / "tokenizer.json").is_file():
|
||||
return False
|
||||
with open(dir_model / "tokenizer.json", encoding="utf-8") as f:
|
||||
added = {t["id"]: t["content"] for t in json.load(f).get("added_tokens", [])}
|
||||
return [added.get(i) for i in range(4)] == ["[UNK]", "[TEXT]", "[START]", "[STOP]"]
|
||||
|
||||
|
||||
@ModelBase.register_hparams_loader(_is_soprano_checkpoint)
|
||||
def _load_soprano_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
logger.info("gguf: detected Soprano checkpoint")
|
||||
hparams = ModelBase.load_hparams(dir_model, False, guess=False)
|
||||
# the text model stays Qwen3, the mmproj holds the decoder
|
||||
hparams["text_config"] = {"architectures": ["SopranoTextModel"]}
|
||||
hparams["architectures"] = ["SopranoModel"]
|
||||
return hparams
|
||||
|
||||
|
||||
@ModelBase.register("SopranoTextModel")
|
||||
class SopranoTextModel(Qwen3Model):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN3
|
||||
|
||||
def get_vocab_base_pre(self, tokenizer) -> str:
|
||||
return "soprano"
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
self.gguf_writer.add_unk_token_id(0)
|
||||
|
||||
|
||||
@ModelBase.register("SopranoModel")
|
||||
@ModelBase.example("ekwek/Soprano-80M", "ekwek/Soprano-1.1-80M")
|
||||
class SopranoModel(MmprojModel):
|
||||
has_vision_encoder = False
|
||||
has_audio_encoder = False
|
||||
|
||||
def _load_decoder(self) -> dict[str, torch.Tensor]:
|
||||
state = torch.load(self.dir_model / "decoder.pth", map_location="cpu", mmap=True, weights_only=True)
|
||||
return state.get("state_dict", state)
|
||||
|
||||
def get_audio_config(self):
|
||||
state = self._load_decoder()
|
||||
n_embd, n_inp, _ = state["decoder.embed.weight"].shape
|
||||
n_ff = state["decoder.convnext.0.pwconv1.weight"].shape[0]
|
||||
n_layer = sum(1 for name in state if name.endswith(".pwconv1.weight"))
|
||||
if n_inp != self.n_embd_text:
|
||||
raise ValueError(f"Soprano decoder input dim {n_inp} does not match the backbone dim {self.n_embd_text}")
|
||||
return {"num_hidden_layers": n_layer, "hidden_size": n_embd, "intermediate_size": n_ff}
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
self.gguf_writer.add_file_type(self.ftype)
|
||||
self.gguf_writer.add_clip_has_gen_audio_encoder(True)
|
||||
self.gguf_writer.add_clip_gen_audio_projector_type(gguf.VisionProjectorType.SOPRANO)
|
||||
self.gguf_writer.add_gen_audio_projection_dim(self.n_embd_text)
|
||||
self.gguf_writer.add_gen_audio_embedding_length(self.hparams["hidden_size"])
|
||||
self.gguf_writer.add_gen_audio_feed_forward_length(self.hparams["intermediate_size"])
|
||||
self.gguf_writer.add_gen_audio_block_count(self.hparams["num_hidden_layers"])
|
||||
self.gguf_writer.add_gen_audio_head_count(1)
|
||||
self.gguf_writer.add_gen_audio_attention_layernorm_eps(1e-6)
|
||||
|
||||
def get_tensors(self):
|
||||
yield from self._load_decoder().items()
|
||||
|
||||
def tensor_force_quant(self, name, new_name, bid, n_dims):
|
||||
if "dwconv.weight" in new_name or (name == "decoder.embed.weight" and n_dims == 3):
|
||||
return gguf.GGMLQuantizationType.F16
|
||||
return super().tensor_force_quant(name, new_name, bid, n_dims)
|
||||
|
||||
def modify_tensors(self, data_torch, name, bid):
|
||||
if name == "head.istft.window":
|
||||
# the ISTFT in mtmd builds its own window
|
||||
if not torch.equal(data_torch.float(), torch.hann_window(data_torch.shape[0])):
|
||||
raise ValueError("Soprano requires a periodic Hann window")
|
||||
return
|
||||
if name == "decoder.embed.weight" and data_torch.shape[-1] == 1:
|
||||
data_torch = data_torch.squeeze(-1)
|
||||
|
||||
T = gguf.MODEL_TENSOR
|
||||
if name.startswith("decoder.convnext."):
|
||||
_, _, blk, key, *suffix = name.split(".")
|
||||
block_map = {
|
||||
"dwconv": T.A_GEN_WAV_UP_DWCONV,
|
||||
"norm": T.A_GEN_WAV_UP_NORM,
|
||||
"pwconv1": T.A_GEN_WAV_UP_PW1,
|
||||
"pwconv2": T.A_GEN_WAV_UP_PW2,
|
||||
"gamma": T.A_GEN_WAV_UP_GAMMA,
|
||||
}
|
||||
yield self.format_tensor_name(block_map[key], int(blk), suffix="".join("." + s for s in suffix)), data_torch
|
||||
return
|
||||
|
||||
tensor_map = {
|
||||
"decoder.embed": T.A_GEN_WAV_INPUT,
|
||||
"decoder.norm": T.A_GEN_WAV_NORM,
|
||||
"decoder.final_layer_norm": T.A_GEN_WAV_OUTPUT_NORM,
|
||||
"head.out": T.A_GEN_WAV_OUTPUT,
|
||||
}
|
||||
key, suffix = name.rsplit(".", 1)
|
||||
yield self.format_tensor_name(tensor_map[key], suffix="." + suffix), data_torch
|
||||
@@ -267,11 +267,15 @@ def main() -> None:
|
||||
sys.exit(1)
|
||||
|
||||
if args.dspark:
|
||||
if is_mistral_format or model_architecture != "DeepseekV4ForCausalLM":
|
||||
logger.error("--dspark is only supported for DeepseekV4ForCausalLM")
|
||||
if is_mistral_format or model_architecture not in ("DeepseekV4ForCausalLM", "DeepseekV41ForCausalLM"):
|
||||
logger.error("--dspark is only supported for DeepseekV4ForCausalLM and DeepseekV41ForCausalLM")
|
||||
sys.exit(1)
|
||||
from conversion.deepseek import DeepseekV4DSparkModel
|
||||
model_class = DeepseekV4DSparkModel
|
||||
if model_architecture == "DeepseekV41ForCausalLM":
|
||||
from conversion.deepseek41 import DeepseekV41DSparkModel
|
||||
model_class = DeepseekV41DSparkModel
|
||||
else:
|
||||
from conversion.deepseek import DeepseekV4DSparkModel
|
||||
model_class = DeepseekV4DSparkModel
|
||||
|
||||
if args.mtp or args.no_mtp:
|
||||
if not model_class.supports_mtp_export:
|
||||
|
||||
@@ -926,6 +926,7 @@ extern "C" {
|
||||
enum ggml_type type);
|
||||
|
||||
// dst[i0, i1, i2] = a[i0, i1, i2] + b[i0, ids[i1, i2]]
|
||||
// an id of -1 adds nothing: dst[i0, i1, i2] = a[i0, i1, i2]
|
||||
GGML_API struct ggml_tensor * ggml_add_id(
|
||||
struct ggml_context * ctx,
|
||||
struct ggml_tensor * a,
|
||||
@@ -1477,6 +1478,16 @@ extern "C" {
|
||||
enum ggml_prec prec,
|
||||
int idx);
|
||||
|
||||
// [TAG_GGML_PREC]
|
||||
// get the accumulator / src[idx] precision set by the functions above
|
||||
// return GGML_PREC_UNDEFINED when the op does not carry the requested precision
|
||||
GGML_API enum ggml_prec ggml_prec_get_acc(
|
||||
const struct ggml_tensor * a);
|
||||
|
||||
GGML_API enum ggml_prec ggml_prec_get_src(
|
||||
const struct ggml_tensor * a,
|
||||
int idx);
|
||||
|
||||
// A: k columns, n rows => [ne03, ne02, n, k]
|
||||
// B: k columns, m rows (i.e. we transpose it internally) => [ne03 * x, ne02 * y, m, k]
|
||||
// result is n columns, m rows => [ne03 * x, ne02 * y, m, n]
|
||||
@@ -1498,6 +1509,7 @@ extern "C" {
|
||||
enum ggml_op_hint hint);
|
||||
|
||||
// indirect matrix multiplication
|
||||
// an id of -1 skips the slot and zeroes the matching row of the result
|
||||
//
|
||||
// [TAG_MUL_MAT_ID_DUPLICATE_ID]
|
||||
// note: this op has undefined behaviour if a given `a` is referenced more than once for a given `b`
|
||||
|
||||
@@ -774,13 +774,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
|
||||
return src_ss[0];
|
||||
};
|
||||
|
||||
auto handle_pool_1d = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
|
||||
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
|
||||
return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
|
||||
}
|
||||
return src_ss[0];
|
||||
};
|
||||
|
||||
auto handle_flash_attn_ext = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
|
||||
GGML_ASSERT(tensor->src[3] == nullptr || src_ss[3].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
||||
|
||||
@@ -997,14 +990,12 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
|
||||
case GGML_OP_CONV_3D:
|
||||
case GGML_OP_CONV_2D_DW:
|
||||
case GGML_OP_CONV_TRANSPOSE_2D:
|
||||
case GGML_OP_POOL_1D:
|
||||
case GGML_OP_POOL_2D:
|
||||
case GGML_OP_POOL_2D_BACK:
|
||||
case GGML_OP_UPSCALE: {
|
||||
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
|
||||
} break;
|
||||
case GGML_OP_POOL_1D: {
|
||||
split_state = handle_pool_1d(src_ss);
|
||||
} break;
|
||||
case GGML_OP_PAD: {
|
||||
split_state = handle_pad(src_ss);
|
||||
} break;
|
||||
|
||||
@@ -1652,7 +1652,12 @@ static void ggml_compute_forward_mul_mat_id(
|
||||
for (int id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = *(const int32_t *) ((const char *) ids->data + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
|
||||
assert(i02 >= 0 && i02 < n_as);
|
||||
assert(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = (struct mmid_row_mapping) {id, iid1};
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
@@ -743,12 +743,17 @@ static void ggml_compute_forward_add_id_f32(
|
||||
// src1 indices
|
||||
const int i11 = *(int32_t *) ((char *) src2->data + i1*nb20 + i2*nb21);
|
||||
|
||||
GGML_ASSERT(i11 >= 0 && i11 < ne11);
|
||||
GGML_ASSERT(i11 == -1 || (i11 >= 0 && i11 < ne11));
|
||||
|
||||
ggml_vec_add_f32(ne0,
|
||||
(float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1 ),
|
||||
(float *) ((char *) src0->data + i3*nb03 + i2*nb02 + i1*nb01),
|
||||
(float *) ((char *) src1->data + i11*nb11));
|
||||
float * dst_row = (float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1 );
|
||||
const float * src0_row = (float *) ((char *) src0->data + i3*nb03 + i2*nb02 + i1*nb01);
|
||||
|
||||
if (i11 == -1) {
|
||||
memcpy(dst_row, src0_row, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
ggml_vec_add_f32(ne0, dst_row, src0_row, (float *) ((char *) src1->data + i11*nb11));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4855,7 +4855,12 @@ template <typename BLOC_TYPE, int64_t INTER_SIZE, int64_t NB_COLS, ggml_type PAR
|
||||
const int32_t i02 =
|
||||
*(const int32_t *) ((const char *) ids->data + iid1 * ids->nb[1] + id * ids->nb[0]);
|
||||
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = { id, iid1 };
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
@@ -682,7 +682,12 @@ template <typename BLOC_TYPE, int64_t INTER_SIZE, int64_t NB_COLS> class tensor_
|
||||
const int32_t i02 =
|
||||
*(const int32_t *) ((const char *) ids->data + iid1 * ids->nb[1] + id * ids->nb[0]);
|
||||
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id * nb1 + iid1 * nb2, 0, ne0 * sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = { id, iid1 };
|
||||
matrix_row_counts[i02] += 1;
|
||||
|
||||
@@ -78,7 +78,7 @@ static inline float op_expm1(float x) {
|
||||
}
|
||||
|
||||
static inline float op_softplus(float x) {
|
||||
return (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
return (x > 20.0f) ? x : log1pf(expf(x));
|
||||
}
|
||||
|
||||
static inline float op_floor(float x) {
|
||||
|
||||
@@ -1409,14 +1409,14 @@ namespace ggml_cuda_mma {
|
||||
tile<16, 16, float, dl_d> & D, const tile<16, 8, nv_bfloat162, dl_ab> & A, const tile<16, 8, nv_bfloat162, dl_ab> & B) {
|
||||
#if defined(AMD_WMMA_AVAILABLE)
|
||||
#if defined(RDNA4)
|
||||
using bf16x8_t = __attribute__((ext_vector_type(8))) __bf16;
|
||||
using bf16x8_t = __attribute__((ext_vector_type(8))) short;
|
||||
using floatx8_t = __attribute__((ext_vector_type(8))) float;
|
||||
floatx8_t& acc_frag = reinterpret_cast<floatx8_t&>(D.x[0]);
|
||||
const bf16x8_t& a_frag = reinterpret_cast<const bf16x8_t&>(A.x[0]);
|
||||
const bf16x8_t& b_frag = reinterpret_cast<const bf16x8_t&>(B.x[0]);
|
||||
acc_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12(a_frag, b_frag, acc_frag);
|
||||
#elif defined(RDNA3)
|
||||
using bf16x16_t = __attribute__((ext_vector_type(16))) __bf16;
|
||||
using bf16x16_t = __attribute__((ext_vector_type(16))) short;
|
||||
using floatx8_t = __attribute__((ext_vector_type(8))) float;
|
||||
floatx8_t& acc_frag = reinterpret_cast<floatx8_t&>(D.x[0]);
|
||||
const bf16x16_t& a_frag = reinterpret_cast<const bf16x16_t&>(A.x[0]);
|
||||
|
||||
@@ -60,10 +60,10 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
const int v0 = ggml_hip_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = ggml_hip_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = ggml_hip_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = ggml_hip_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
@@ -162,8 +162,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const uint32_t qx_indices = (q & 0x03) | ((q & 0x0C) << 6) | ((q & 0x30) << 12) | ((q & 0xC0) << 18);
|
||||
const uint32_t qy_bits = q >> 8;
|
||||
const uint32_t qy_indices = (qy_bits & 0x03) | ((qy_bits & 0x0C) << 6) | ((qy_bits & 0x30) << 12) | ((qy_bits & 0xC0) << 18);
|
||||
const int qx = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
const int qx = ggml_hip_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = ggml_hip_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
#else
|
||||
// unpack even and odd crumbs into byte values
|
||||
const int qe = __byte_perm(0x020100FF, 0x020100FF, q >> 0);
|
||||
|
||||
@@ -435,6 +435,7 @@ template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __de
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
|
||||
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
|
||||
static_assert(I % warp_size == 0, "DP4A MMQ tile height must be a multiple of the physical warp size");
|
||||
|
||||
const bool y_scale_used = y_scale != nullptr;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ __device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], co
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
const int idx = lane + i * WARP_SIZE;
|
||||
const bool active = !use_limit || (idx < limit);
|
||||
vals[i] = active ? sqrtf(vals[i] > 20.0f ? vals[i] : logf(1.0f + expf(vals[i]))) : -INFINITY;
|
||||
vals[i] = active ? sqrtf(vals[i] > 20.0f ? vals[i] : log1pf(expf(vals[i]))) : -INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ static __device__ __forceinline__ float op_expm1(float x) {
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ float op_softplus(float x) {
|
||||
return (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
return (x > 20.0f) ? x : log1pf(expf(x));
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ float op_elu(float x) {
|
||||
|
||||
@@ -40,18 +40,18 @@ static __device__ __forceinline__ int2 get_int_from_table_16(const int & q4, con
|
||||
const uint32_t q_odd = (q4 >> 4);
|
||||
|
||||
// Perform lookups in the lower half of the table (indices 0-7).
|
||||
uint32_t v_even_low = __builtin_amdgcn_perm(values[1], values[0], q_even & 0x07070707);
|
||||
uint32_t v_odd_low = __builtin_amdgcn_perm(values[1], values[0], q_odd & 0x07070707);
|
||||
uint32_t v_even_low = ggml_hip_perm(values[1], values[0], q_even & 0x07070707);
|
||||
uint32_t v_odd_low = ggml_hip_perm(values[1], values[0], q_odd & 0x07070707);
|
||||
|
||||
// Perform lookups in the upper half of the table (indices 8-15).
|
||||
uint32_t v_even_high = __builtin_amdgcn_perm(values[3], values[2], q_even & 0x07070707);
|
||||
uint32_t v_odd_high = __builtin_amdgcn_perm(values[3], values[2], q_odd & 0x07070707);
|
||||
uint32_t v_even_high = ggml_hip_perm(values[3], values[2], q_even & 0x07070707);
|
||||
uint32_t v_odd_high = ggml_hip_perm(values[3], values[2], q_odd & 0x07070707);
|
||||
|
||||
// Select between the low and high results based on the MSB of each index nibble.
|
||||
uint32_t mask_even = 0x03020100 | ((q_even & 0x08080808) >> 1);
|
||||
uint32_t res_x = __builtin_amdgcn_perm(v_even_high, v_even_low, mask_even);
|
||||
uint32_t res_x = ggml_hip_perm(v_even_high, v_even_low, mask_even);
|
||||
uint32_t mask_odd = 0x03020100 | ((q_odd & 0x08080808) >> 1);
|
||||
uint32_t res_y = __builtin_amdgcn_perm(v_odd_high, v_odd_low, mask_odd);
|
||||
uint32_t res_y = ggml_hip_perm(v_odd_high, v_odd_low, mask_odd);
|
||||
|
||||
return make_int2(res_x, res_y);
|
||||
#elif !defined(GGML_USE_MUSA)
|
||||
@@ -720,10 +720,10 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
const int v0 = ggml_hip_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = ggml_hip_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = ggml_hip_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = ggml_hip_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
@@ -777,8 +777,8 @@ static __device__ __forceinline__ float vec_dot_q2_0_q8_1(
|
||||
const uint32_t qx_indices = (q & 0x03) | ((q & 0x0C) << 6) | ((q & 0x30) << 12) | ((q & 0xC0) << 18);
|
||||
const uint32_t qy_bits = q >> 8;
|
||||
const uint32_t qy_indices = (qy_bits & 0x03) | ((qy_bits & 0x0C) << 6) | ((qy_bits & 0x30) << 12) | ((qy_bits & 0xC0) << 18);
|
||||
const int qx = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
const int qx = ggml_hip_perm(0x020100FF, 0x020100FF, qx_indices);
|
||||
const int qy = ggml_hip_perm(0x020100FF, 0x020100FF, qy_indices);
|
||||
#else
|
||||
// unpack even and odd crumbs into byte values
|
||||
const int qe = __byte_perm(0x020100FF, 0x020100FF, q >> 0);
|
||||
|
||||
Vendored
+19
@@ -311,3 +311,22 @@ static __device__ __forceinline__ unsigned int __vcmpne4(unsigned int a, unsigne
|
||||
|
||||
return (ne_any_bit >> 7) * 0xff;
|
||||
}
|
||||
|
||||
// v_perm_b32 with selector bytes in [0, 7]: 0-3 pick bytes of lo, 4-7 pick bytes of hi.
|
||||
static __device__ __forceinline__ uint32_t ggml_hip_perm(const uint32_t hi, const uint32_t lo, const uint32_t sel) {
|
||||
#if __has_builtin(__builtin_amdgcn_is_invocable)
|
||||
if (__builtin_amdgcn_is_invocable(__builtin_amdgcn_perm)) {
|
||||
return __builtin_amdgcn_perm(hi, lo, sel);
|
||||
}
|
||||
const uint64_t src = ((uint64_t) hi << 32) | lo;
|
||||
uint32_t res = 0;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const uint32_t byte_sel = (sel >> (8 * i)) & 0x07;
|
||||
res |= (uint32_t) ((src >> (8 * byte_sel)) & 0xFF) << (8 * i);
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
return __builtin_amdgcn_perm(hi, lo, sel);
|
||||
#endif // __has_builtin(__builtin_amdgcn_is_invocable)
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ size_t opt_mbuf_total = 0; // total buffer space limit (
|
||||
|
||||
int opt_mm_select = 2; // 2 = HMX -> HVX -> CPU, 1 = HVX -> CPU, 0 = CPU (unsupported)
|
||||
int opt_fa_select = 2; // 2 = HMX -> HVX -> CPU, 1 = HVX -> CPU, 0 = CPU (unsupported)
|
||||
int opt_fa_head_split = 1; // 1 = partition flash_attn by KV heads in multicore (default on), 0 = token-based (original)
|
||||
int opt_fa_split = 1; // 1 = head-split + team-split when n_kv_heads < N_cores (default), 0 = off
|
||||
int opt_gdn_select = 2; // 2 = HMX -> HVX, 1 = HVX, 0 = CPU (unsupported)
|
||||
int opt_ar_select = 2; // 2 = fused ALLREDUCE+ADD (default), 1 = unfused ALLREDUCE, 0 = fallback to CPY+FENCE
|
||||
int opt_ar_scatter = 1; // 1 = reduce-scatter the fused ALLREDUCE+ADD (default), 0 = full reduction
|
||||
@@ -3393,7 +3393,7 @@ static void ggml_hexagon_init(ggml_backend_reg * reg) {
|
||||
const char * str_nhmx = getenv("GGML_HEXAGON_NHMX");
|
||||
const char * str_mm_select = getenv("GGML_HEXAGON_MM_SELECT");
|
||||
const char * str_fa_select = getenv("GGML_HEXAGON_FA_SELECT");
|
||||
const char * str_fa_head_split = getenv("GGML_HEXAGON_FA_HEAD_SPLIT");
|
||||
const char * str_fa_split = getenv("GGML_HEXAGON_FA_SPLIT");
|
||||
const char * str_gdn_select = getenv("GGML_HEXAGON_GDN_SELECT");
|
||||
const char * str_ar_select = getenv("GGML_HEXAGON_AR_SELECT");
|
||||
const char * str_ar_scatter = getenv("GGML_HEXAGON_AR_SCATTER");
|
||||
@@ -3458,7 +3458,7 @@ static void ggml_hexagon_init(ggml_backend_reg * reg) {
|
||||
opt_nhmx = str_nhmx ? atoi(str_nhmx) : opt_nhmx;
|
||||
opt_mm_select = str_mm_select ? atoi(str_mm_select) : opt_mm_select;
|
||||
opt_fa_select = str_fa_select ? atoi(str_fa_select) : opt_fa_select;
|
||||
opt_fa_head_split = str_fa_head_split ? atoi(str_fa_head_split) : opt_fa_head_split;
|
||||
opt_fa_split = str_fa_split ? atoi(str_fa_split) : opt_fa_split;
|
||||
opt_gdn_select = str_gdn_select ? atoi(str_gdn_select) : opt_gdn_select;
|
||||
opt_ar_select = str_ar_select ? atoi(str_ar_select) : opt_ar_select;
|
||||
opt_ar_scatter = str_ar_scatter ? atoi(str_ar_scatter) : opt_ar_scatter;
|
||||
|
||||
@@ -28,7 +28,7 @@ extern size_t opt_mbuf_total;
|
||||
|
||||
extern int opt_mm_select;
|
||||
extern int opt_fa_select;
|
||||
extern int opt_fa_head_split;
|
||||
extern int opt_fa_split;
|
||||
extern int opt_gdn_select;
|
||||
extern int opt_ar_select;
|
||||
extern int opt_ar_scatter;
|
||||
|
||||
@@ -73,6 +73,7 @@ htp_op_code htp_op_remap(const struct ggml_tensor * t) {
|
||||
case GGML_UNARY_OP_RELU: return HTP_OP_UNARY_RELU;
|
||||
case GGML_UNARY_OP_STEP: return HTP_OP_UNARY_STEP;
|
||||
case GGML_UNARY_OP_CEIL: return HTP_OP_UNARY_CEIL;
|
||||
case GGML_UNARY_OP_SGN: return HTP_OP_UNARY_SGN;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
@@ -396,9 +397,20 @@ static bool htp_kparams_solve_flash_attn(
|
||||
kparams->scale = scale;
|
||||
kparams->max_bias = max_bias;
|
||||
kparams->logit_softcap = logit_softcap;
|
||||
kparams->head_split = (opt_fa_head_split != 0) ? 1 : 0;
|
||||
kparams->head_split = (opt_fa_split != 0) ? 1 : 0;
|
||||
kparams->flags = 0;
|
||||
kparams->G = G;
|
||||
|
||||
// team-split only when a team has >1 member (n_kv_heads < N_cores)
|
||||
uint32_t fa_teams = 1, fa_team_size = 1;
|
||||
const uint32_t mdev_count = htp_session_mdev_count(sess);
|
||||
if (opt_fa_split && mdev_count > 1) {
|
||||
htp_fa_mdev_teams(n_kv_heads, mdev_count, kparams->head_split != 0, &fa_teams, &fa_team_size);
|
||||
if (fa_team_size > 1) {
|
||||
kparams->flags |= HTP_FA_FLAG_TEAM_SPLIT;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t n_head = q->ne[2];
|
||||
kparams->n_head_log2 = 1u << (uint32_t) std::floor(std::log2(n_head));
|
||||
kparams->m0 = std::pow(2.0f, -(max_bias) / kparams->n_head_log2);
|
||||
@@ -408,7 +420,7 @@ static bool htp_kparams_solve_flash_attn(
|
||||
const uint32_t DK_pad = hex_round_up(DK, 64);
|
||||
const uint32_t DV_pad = hex_round_up(DV, 64);
|
||||
size_t Br = 0, Bc = 0;
|
||||
int ret = hmx_fa_find_chunk_size(&Br, &Bc, G, DK_pad, DV_pad, neq1, nek1, vtcm_size, n_threads, (q->type == GGML_TYPE_F32), sinks != nullptr, n_head);
|
||||
int ret = hmx_fa_find_chunk_size(&Br, &Bc, G, DK_pad, DV_pad, neq1, nek1, vtcm_size, n_threads, (q->type == GGML_TYPE_F32), sinks != nullptr, n_head, fa_team_size);
|
||||
if (ret == 0) {
|
||||
kparams->kernel_type = HTP_FA_KERNEL_HMX;
|
||||
if (logit_softcap == 0.0f) {
|
||||
@@ -1392,6 +1404,52 @@ static bool htp_kparams_solve_binary(
|
||||
return true;
|
||||
}
|
||||
|
||||
// collapse contiguous dims of elementwise ops so that rows fill at least one HVX vector
|
||||
static void htp_unary_collapse_dims(struct ggml_tensor * src, struct ggml_tensor * dst, size_t elem_size) {
|
||||
if (src->nb[0] != elem_size || dst->nb[0] != elem_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (src->ne[1] > 1 || src->ne[2] > 1 || src->ne[3] > 1) {
|
||||
const size_t row_size = (size_t) src->ne[0] * elem_size;
|
||||
const bool rows_contig = src->nb[1] == row_size && dst->nb[1] == (size_t) dst->ne[0] * elem_size;
|
||||
const bool can_merge = (src->ne[1] == 1 && dst->ne[1] == 1) ||
|
||||
(rows_contig && (src->ne[0] == 1 || (row_size < 128 && row_size * src->ne[1] <= 2048)));
|
||||
if (!can_merge) {
|
||||
break;
|
||||
}
|
||||
src->ne[0] *= src->ne[1];
|
||||
src->ne[1] = src->ne[2];
|
||||
src->ne[2] = src->ne[3];
|
||||
src->ne[3] = 1;
|
||||
dst->ne[0] *= dst->ne[1];
|
||||
dst->ne[1] = dst->ne[2];
|
||||
dst->ne[2] = dst->ne[3];
|
||||
dst->ne[3] = 1;
|
||||
src->nb[1] = src->nb[2];
|
||||
src->nb[2] = src->nb[3];
|
||||
dst->nb[1] = dst->nb[2];
|
||||
dst->nb[2] = dst->nb[3];
|
||||
}
|
||||
|
||||
// ne1 too big to merge in full: merge it in chunks of k rows (k divides ne1)
|
||||
const size_t row_size = (size_t) src->ne[0] * elem_size;
|
||||
if (src->ne[1] > 1 && row_size < 128 && src->nb[1] == row_size && dst->nb[1] == row_size) {
|
||||
int64_t k = 2048 / row_size;
|
||||
while (k > 1 && src->ne[1] % k != 0) {
|
||||
k--;
|
||||
}
|
||||
if (k > 1) {
|
||||
src->ne[0] *= k;
|
||||
src->ne[1] /= k;
|
||||
src->nb[1] *= k;
|
||||
dst->ne[0] *= k;
|
||||
dst->ne[1] /= k;
|
||||
dst->nb[1] *= k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool htp_kparams_solve_unary_impl(
|
||||
const struct ggml_hexagon_session * sess,
|
||||
uint32_t op,
|
||||
@@ -1435,6 +1493,7 @@ static bool htp_kparams_solve_unary_impl(
|
||||
case HTP_OP_UNARY_ABS:
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_UNARY_CEIL:
|
||||
case HTP_OP_UNARY_SGN:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -1451,9 +1510,14 @@ static bool htp_kparams_solve_unary_impl(
|
||||
struct ggml_tensor src0_c = *src0;
|
||||
struct ggml_tensor dst_c = *dst;
|
||||
if (htp_unary_is_elementwise(op)) {
|
||||
HTP_UNARY_COLLAPSE_DIMS(src0_c.ne, src0_c.nb, dst_c.ne, dst_c.nb, elem_size);
|
||||
htp_unary_collapse_dims(&src0_c, &dst_c, elem_size);
|
||||
src0 = &src0_c;
|
||||
dst = &dst_c;
|
||||
for (int i = 0; i < HTP_OP_MAX_DIMS; i++) {
|
||||
kparams->ne[i] = (uint32_t) src0->ne[i];
|
||||
kparams->src0_nb[i] = (uint32_t) src0->nb[i];
|
||||
kparams->dst_nb[i] = (uint32_t) dst->nb[i];
|
||||
}
|
||||
}
|
||||
|
||||
const size_t dst_data_row_size = dst->ne[0] * ggml_type_size(dst->type);
|
||||
|
||||
@@ -2094,7 +2094,29 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
const uint32_t dst_e_size = (dst->type == HTP_TYPE_F32) ? sizeof(float) : sizeof(__fp16);
|
||||
const bool can_split = htp_tensor_can_row_partition(dst, dst_e_size);
|
||||
|
||||
if (kparams->head_split && can_split && n_kv_heads >= mdev_count && n_kv_heads % mdev_count == 0) {
|
||||
if ((kparams->flags & HTP_FA_FLAG_TEAM_SPLIT) && can_split) {
|
||||
// KV heads split across teams, Q-blocks split within a team
|
||||
uint32_t n_teams, team_size;
|
||||
htp_fa_mdev_teams(n_kv_heads, mdev_count, kparams->head_split != 0, &n_teams, &team_size);
|
||||
|
||||
const uint32_t team = mdev_idx / team_size;
|
||||
const uint32_t member = mdev_idx - team * team_size;
|
||||
const uint32_t kv_per_team = n_kv_heads / n_teams;
|
||||
kv_head_min = team * kv_per_team;
|
||||
kv_head_max = kv_head_min + kv_per_team;
|
||||
|
||||
const uint32_t base = n_q_blocks / team_size;
|
||||
const uint32_t rem = n_q_blocks - base * team_size;
|
||||
const uint32_t b0 = member * base + MIN(member, rem);
|
||||
const uint32_t nblk = base + (member < rem ? 1 : 0);
|
||||
|
||||
if (nblk == 0) {
|
||||
return HTP_STATUS_OK;
|
||||
}
|
||||
|
||||
q_start_min = b0 * Br;
|
||||
q_start_max = MIN((b0 + nblk) * Br, neq1);
|
||||
} else if (kparams->head_split && can_split && n_kv_heads >= mdev_count && n_kv_heads % mdev_count == 0) {
|
||||
const uint32_t kv_per_core = n_kv_heads / mdev_count;
|
||||
kv_head_min = mdev_idx * kv_per_core;
|
||||
kv_head_max = kv_head_min + kv_per_core;
|
||||
|
||||
@@ -32,9 +32,20 @@ enum htp_fa_kernel_type {
|
||||
HTP_FA_KERNEL_HMX
|
||||
};
|
||||
|
||||
// Multicore: gcd(n_kv_heads, N) teams own the KV heads, team members share the Q-blocks
|
||||
#define HTP_FA_FLAG_TEAM_SPLIT 0x01
|
||||
|
||||
static inline void htp_fa_mdev_teams(uint32_t n_kv_heads, uint32_t mdev_count, bool head_split,
|
||||
uint32_t * n_teams, uint32_t * team_size) {
|
||||
const uint32_t t = (head_split && n_kv_heads > 0 && mdev_count > 1) ? hex_gcd_u32(n_kv_heads, mdev_count) : 1;
|
||||
*n_teams = t;
|
||||
*team_size = mdev_count > 1 ? mdev_count / t : 1;
|
||||
}
|
||||
|
||||
struct htp_fa_kernel_params {
|
||||
uint8_t kernel_type; // enum htp_fa_kernel_type
|
||||
uint8_t head_split; // 1 = partition by KV heads in multicore, 0 = token partition
|
||||
uint8_t flags; // HTP_FA_FLAG_*
|
||||
uint8_t n_threads; // Number of threads to run
|
||||
|
||||
// Common parameters
|
||||
@@ -321,7 +332,8 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
|
||||
size_t n_threads,
|
||||
bool is_q_fp32,
|
||||
bool has_sinks,
|
||||
size_t n_heads) {
|
||||
size_t n_heads,
|
||||
size_t q_split) {
|
||||
const size_t T = HMX_FP16_TILE_N_ROWS; // 32
|
||||
const size_t br_unit = hmx_ceil_div(T, gqa_factor);
|
||||
const size_t bc_unit = HMX_FP16_TILE_N_COLS * 2; // 64
|
||||
@@ -361,7 +373,8 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
|
||||
const size_t vecs_per_t = n_use > 0 ? (n_row_vec_cnt + n_use - 1) / n_use : 1;
|
||||
|
||||
const size_t c_iter_actual = c_iter_base + c_softmax * vecs_per_t;
|
||||
const size_t cost = q_blocks * (c_q_fixed + kv_blocks * c_iter_actual);
|
||||
const size_t q_blocks_nsp = (q_blocks + q_split - 1) / (q_split ? q_split : 1);
|
||||
const size_t cost = q_blocks_nsp * (c_q_fixed + kv_blocks * c_iter_actual);
|
||||
const size_t mn = Br * Bc;
|
||||
|
||||
if (cost < best_cost || (cost == best_cost && mn > best_mn)) {
|
||||
|
||||
@@ -113,6 +113,7 @@ enum htp_op_code {
|
||||
HTP_OP_GLU_REGLU,
|
||||
HTP_OP_GLU_SIGMOID,
|
||||
HTP_OP_UNARY_CEIL,
|
||||
HTP_OP_UNARY_SGN,
|
||||
|
||||
HTP_OP_INVALID
|
||||
};
|
||||
|
||||
@@ -677,6 +677,56 @@ static inline void hvx_step_f32_aa(uint8_t * restrict dst, const uint8_t * restr
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_sgn_f32_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
assert((unsigned long) dst % 128 == 0);
|
||||
assert((unsigned long) src % 128 == 0);
|
||||
|
||||
HVX_Vector * restrict vdst = (HVX_Vector *) dst;
|
||||
HVX_Vector * restrict vsrc = (HVX_Vector *) src;
|
||||
|
||||
const uint32_t elem_size = sizeof(float);
|
||||
const uint32_t epv = 128 / elem_size;
|
||||
const uint32_t nvec = n / epv;
|
||||
const uint32_t nloe = n % epv;
|
||||
|
||||
uint32_t i = 0;
|
||||
|
||||
_Pragma("unroll(4)")
|
||||
for (; i < nvec; i++) {
|
||||
vdst[i] = hvx_vec_sgn_f32(vsrc[i]);
|
||||
}
|
||||
if (nloe) {
|
||||
HVX_Vector v = hvx_vec_sgn_f32(vsrc[i]);
|
||||
hvx_vec_store_a((void *) &vdst[i], nloe * elem_size, v);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_sgn_f16_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
assert((unsigned long) dst % 128 == 0);
|
||||
assert((unsigned long) src % 128 == 0);
|
||||
|
||||
HVX_Vector * restrict vdst = (HVX_Vector *) dst;
|
||||
HVX_Vector * restrict vsrc = (HVX_Vector *) src;
|
||||
|
||||
const uint32_t elem_size = sizeof(_Float16);
|
||||
const uint32_t epv = 128 / elem_size;
|
||||
const uint32_t nvec = n / epv;
|
||||
const uint32_t nloe = n % epv;
|
||||
|
||||
uint32_t i = 0;
|
||||
|
||||
_Pragma("unroll(4)")
|
||||
for (; i < nvec; i++) {
|
||||
HVX_VectorPair p = hvx_vec_f16_to_f32(vsrc[i]);
|
||||
vdst[i] = hvx_vec_f32_to_f16(hvx_vec_sgn_f32(Q6_V_lo_W(p)), hvx_vec_sgn_f32(Q6_V_hi_W(p)));
|
||||
}
|
||||
if (nloe) {
|
||||
HVX_VectorPair p = hvx_vec_f16_to_f32(vsrc[i]);
|
||||
HVX_Vector v = hvx_vec_f32_to_f16(hvx_vec_sgn_f32(Q6_V_lo_W(p)), hvx_vec_sgn_f32(Q6_V_hi_W(p)));
|
||||
hvx_vec_store_a((void *) &vdst[i], nloe * elem_size, v);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_step_f16_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
assert((unsigned long) dst % 128 == 0);
|
||||
assert((unsigned long) src % 128 == 0);
|
||||
|
||||
@@ -118,6 +118,17 @@ static inline HVX_Vector hvx_vec_step_f32(HVX_Vector v) {
|
||||
return Q6_V_vmux_QVV(q, one, zero);
|
||||
}
|
||||
|
||||
static inline HVX_Vector hvx_vec_sgn_f32(HVX_Vector v) {
|
||||
const HVX_Vector zero = Q6_V_vzero();
|
||||
const HVX_Vector one = hvx_vec_splat_f32(1.0f);
|
||||
const HVX_Vector sign_mask = Q6_V_vsplat_R(0x80000000);
|
||||
const HVX_Vector mag = hvx_vec_abs_f32(v);
|
||||
const HVX_Vector signed_one = Q6_V_vor_VV(one, Q6_V_vand_VV(v, sign_mask));
|
||||
// The ordered magnitude comparison maps NaN and both signed zeros to zero.
|
||||
const HVX_VectorPred nonzero = Q6_Q_vcmp_gt_VsfVsf(mag, zero);
|
||||
return Q6_V_vmux_QVV(nonzero, signed_one, zero);
|
||||
}
|
||||
|
||||
static inline HVX_Vector hvx_vec_step_f16(HVX_Vector v) {
|
||||
const HVX_Vector zero = Q6_V_vzero();
|
||||
const HVX_Vector one = hvx_vec_splat_f16((_Float16) 1.0f);
|
||||
|
||||
@@ -946,6 +946,7 @@ static int execute_op(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU:
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_UNARY_CEIL:
|
||||
case HTP_OP_UNARY_SGN:
|
||||
case HTP_OP_L2_NORM:
|
||||
case HTP_OP_HADAMARD:
|
||||
return op_unary(octx);
|
||||
|
||||
@@ -1413,10 +1413,10 @@ static void hvx_mv_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
for (uint32_t ie1 = 0; ie1 < n_aids; ++ie1) { // for each expert
|
||||
const int32_t eid = *(const int32_t *) ((const uint8_t *) src2->data + ie1 * src2->nb[0]);
|
||||
if (eid < 0) {
|
||||
if (eid == -1) {
|
||||
continue;
|
||||
}
|
||||
assert(eid < (int32_t) n_ids);
|
||||
assert(eid >= 0 && eid < (int32_t) n_ids);
|
||||
|
||||
const dma_addr_t src0_row = src0->data + eid * nb02;
|
||||
const uint8_t * restrict act_col = (const uint8_t *) act_data;
|
||||
@@ -4166,10 +4166,10 @@ static inline void scan_expert_ids_n(
|
||||
const int32_t * row_ptr = (const int32_t *) (ids_data + iid1 * ids_nb1);
|
||||
for (uint32_t id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = row_ptr[id];
|
||||
if (i02 < 0) {
|
||||
if (i02 == -1) {
|
||||
continue;
|
||||
}
|
||||
assert(i02 < n_as);
|
||||
assert(i02 >= 0 && i02 < n_as);
|
||||
|
||||
if (matrix_rows) {
|
||||
matrix_rows[i02 * mapping_stride + counts[i02]] = (struct mmid_row_mapping) { id, iid1 };
|
||||
@@ -4179,6 +4179,28 @@ static inline void scan_expert_ids_n(
|
||||
}
|
||||
}
|
||||
|
||||
// the matmuls only write the rows that an expert owns, so the rows of the skipped slots are zeroed here
|
||||
static inline void zero_skipped_dst_rows(
|
||||
const struct htp_tensor * ids,
|
||||
uint32_t n_ids,
|
||||
const struct htp_tensor * dst,
|
||||
uint32_t ne0,
|
||||
size_t nb1,
|
||||
size_t nb2
|
||||
) {
|
||||
const uint8_t * ids_data = (const uint8_t *) ids->data;
|
||||
|
||||
for (uint32_t iid1 = 0; iid1 < ids->ne[1]; ++iid1) {
|
||||
for (uint32_t id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = *(const int32_t *) (ids_data + iid1 * ids->nb[1] + id * ids->nb[0]);
|
||||
if (i02 != -1) {
|
||||
continue;
|
||||
}
|
||||
memset((uint8_t *) (dst->data + id * nb1 + iid1 * nb2), 0, ne0 * sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void scan_expert_ids(
|
||||
const struct htp_tensor * ids,
|
||||
uint32_t n_ids,
|
||||
@@ -4204,10 +4226,10 @@ static inline void scan_expert_ids(
|
||||
const int32_t * row_ptr = (const int32_t *) (ids_data + iid1 * ids_nb1);
|
||||
for (uint32_t id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = *(const int32_t *) ((const uint8_t *) row_ptr + id * ids_nb0);
|
||||
if (i02 < 0) {
|
||||
if (i02 == -1) {
|
||||
continue;
|
||||
}
|
||||
assert(i02 < n_as);
|
||||
assert(i02 >= 0 && i02 < n_as);
|
||||
|
||||
if (matrix_rows) {
|
||||
matrix_rows[i02 * mapping_stride + counts[i02]] = (struct mmid_row_mapping) { id, iid1 };
|
||||
@@ -4287,6 +4309,8 @@ int op_matmul_id(struct htp_ops_context * octx) {
|
||||
scan_expert_ids(ids, n_ids, n_as, matrix_row_counts, matrix_rows, mapping_stride);
|
||||
}
|
||||
|
||||
zero_skipped_dst_rows(ids, n_ids, dst, ne0, nb1, nb2);
|
||||
|
||||
mmctx->matrix_row_counts = matrix_row_counts;
|
||||
mmctx->matrix_rows = matrix_rows;
|
||||
mmctx->mapping_stride = mapping_stride;
|
||||
|
||||
@@ -440,6 +440,20 @@ static void ceil_f16(const void * restrict src,
|
||||
}
|
||||
}
|
||||
|
||||
static void sgn_f16(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_sgn_f16_aa((uint8_t *) dst_local, (const uint8_t *) src_local, ne0);
|
||||
}
|
||||
}
|
||||
|
||||
static void l2_norm_f16(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
@@ -976,6 +990,20 @@ static void ceil_f32(const void * restrict src,
|
||||
}
|
||||
}
|
||||
|
||||
static void sgn_f32(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_sgn_f32_aa(dst_local, src_local, ne0);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_f32(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
@@ -1094,6 +1122,11 @@ static void tile_ceil_f32(void * restrict dst, const void * restrict src, uint32
|
||||
hvx_ceil_f32_aa((uint8_t *) dst, (const uint8_t *) src, tw);
|
||||
}
|
||||
|
||||
static void tile_sgn_f32(void * restrict dst, const void * restrict src, uint32_t tw, const struct htp_unary_context * uctx) {
|
||||
(void) uctx;
|
||||
hvx_sgn_f32_aa((uint8_t *) dst, (const uint8_t *) src, tw);
|
||||
}
|
||||
|
||||
static void tri_apply_tile_f32(const void * restrict src, void * restrict dst,
|
||||
uint32_t tile_elems, uint32_t col_start, uint32_t i01,
|
||||
uint32_t ne0, int32_t ttype) {
|
||||
@@ -1738,6 +1771,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU: op_type = "relu-f32"; break;
|
||||
case HTP_OP_UNARY_STEP: op_type = is_f16 ? "step-f16" : "step-f32"; break;
|
||||
case HTP_OP_UNARY_CEIL: op_type = is_f16 ? "ceil-f16" : "ceil-f32"; break;
|
||||
case HTP_OP_UNARY_SGN: op_type = is_f16 ? "sgn-f16" : "sgn-f32"; break;
|
||||
case HTP_OP_L2_NORM: op_type = is_f16 ? "l2norm-f16" : "l2norm-f32"; break;
|
||||
case HTP_OP_TRI: op_type = "tri-f32"; break;
|
||||
case HTP_OP_HADAMARD: op_type = is_f16 ? "hadamard-f16" : "hadamard-f32"; break;
|
||||
@@ -1752,9 +1786,9 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
return HTP_STATUS_INVAL_PARAMS;
|
||||
}
|
||||
|
||||
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
|
||||
const size_t src0_elem_size = is_f16 ? sizeof(_Float16) : sizeof(float);
|
||||
const size_t dst_elem_size = (dst->type == HTP_TYPE_F16) ? sizeof(_Float16) : sizeof(float);
|
||||
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
|
||||
const size_t src0_data_row_size = src0->ne[0] * src0_elem_size;
|
||||
const size_t dst_data_row_size = dst->ne[0] * dst_elem_size;
|
||||
|
||||
@@ -1872,6 +1906,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU: compute_func = (void *) tile_relu_f32; break;
|
||||
case HTP_OP_UNARY_STEP: compute_func = (void *) tile_step_f32; break;
|
||||
case HTP_OP_UNARY_CEIL: compute_func = (void *) tile_ceil_f32; break;
|
||||
case HTP_OP_UNARY_SGN: compute_func = (void *) tile_sgn_f32; break;
|
||||
case HTP_OP_TRI:
|
||||
task_func = unary_thread_tiled_tri_f32;
|
||||
compute_func = (void *) tri_apply_tile_f32;
|
||||
@@ -1892,6 +1927,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_LOG: compute_func = (void *) log_f16; break;
|
||||
case HTP_OP_UNARY_STEP: compute_func = (void *) step_f16; break;
|
||||
case HTP_OP_UNARY_CEIL: compute_func = (void *) ceil_f16; break;
|
||||
case HTP_OP_UNARY_SGN: compute_func = (void *) sgn_f16; break;
|
||||
case HTP_OP_HADAMARD: compute_func = (void *) hadamard_f16; break;
|
||||
default: break;
|
||||
}
|
||||
@@ -1923,6 +1959,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU: compute_func = (void *) relu_f32; break;
|
||||
case HTP_OP_UNARY_STEP: compute_func = (void *) step_f32; break;
|
||||
case HTP_OP_UNARY_CEIL: compute_func = (void *) ceil_f32; break;
|
||||
case HTP_OP_UNARY_SGN: compute_func = (void *) sgn_f32; break;
|
||||
case HTP_OP_L2_NORM: compute_func = (void *) l2_norm_f32; break;
|
||||
case HTP_OP_HADAMARD: compute_func = (void *) hadamard_f32; break;
|
||||
case HTP_OP_TRI:
|
||||
@@ -1952,10 +1989,14 @@ int op_unary(struct htp_ops_context * octx) {
|
||||
return execute_op_unary(octx);
|
||||
}
|
||||
|
||||
const struct htp_unary_kernel_params * kparams = (const struct htp_unary_kernel_params *) octx->kernel_params;
|
||||
|
||||
struct htp_tensor src0_c = *octx->src[0];
|
||||
struct htp_tensor dst_c = *octx->dst;
|
||||
const size_t elem_size = (src0_c.type == HTP_TYPE_F16) ? sizeof(_Float16) : sizeof(float);
|
||||
HTP_UNARY_COLLAPSE_DIMS(src0_c.ne, src0_c.nb, dst_c.ne, dst_c.nb, elem_size);
|
||||
memcpy(src0_c.ne, kparams->ne, sizeof(src0_c.ne));
|
||||
memcpy(dst_c.ne, kparams->ne, sizeof(dst_c.ne));
|
||||
memcpy(src0_c.nb, kparams->src0_nb, sizeof(src0_c.nb));
|
||||
memcpy(dst_c.nb, kparams->dst_nb, sizeof(dst_c.nb));
|
||||
|
||||
const struct htp_tensor * orig_src0 = octx->src[0];
|
||||
const struct htp_tensor * orig_dst = octx->dst;
|
||||
|
||||
@@ -24,6 +24,11 @@ struct htp_unary_kernel_params {
|
||||
struct fastdiv_values div_ne02;
|
||||
struct fastdiv_values div_ne012;
|
||||
struct fastdiv_values div_tpr;
|
||||
|
||||
// collapsed shape of elementwise ops
|
||||
uint32_t ne[HTP_OP_MAX_DIMS];
|
||||
uint32_t src0_nb[HTP_OP_MAX_DIMS];
|
||||
uint32_t dst_nb[HTP_OP_MAX_DIMS];
|
||||
};
|
||||
|
||||
#if defined(__cplusplus)
|
||||
@@ -56,6 +61,7 @@ static inline bool htp_op_is_unary(uint32_t opcode) {
|
||||
case HTP_OP_UNARY_RELU:
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_UNARY_CEIL:
|
||||
case HTP_OP_UNARY_SGN:
|
||||
case HTP_OP_L2_NORM:
|
||||
case HTP_OP_TRI:
|
||||
case HTP_OP_HADAMARD:
|
||||
@@ -79,31 +85,6 @@ static inline bool htp_unary_is_elementwise(uint32_t opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
#define HTP_UNARY_COLLAPSE_DIMS(src_ne, src_nb, dst_ne, dst_nb, elem_size) \
|
||||
do { \
|
||||
while (((src_ne)[1] > 1 || (src_ne)[2] > 1 || (src_ne)[3] > 1) && \
|
||||
(size_t) (src_nb)[0] == (elem_size) && \
|
||||
(size_t) (dst_nb)[0] == (elem_size) && \
|
||||
(((src_ne)[1] == 1 && (dst_ne)[1] == 1) || \
|
||||
((size_t) (src_nb)[1] == (size_t) (src_ne)[0] * (size_t) (src_nb)[0] && \
|
||||
(size_t) (dst_nb)[1] == (size_t) (dst_ne)[0] * (size_t) (dst_nb)[0] && \
|
||||
((src_ne)[0] == 1 || \
|
||||
(uint64_t) (src_ne)[0] * (uint64_t) (src_ne)[1] * (elem_size) <= 128)))) { \
|
||||
(src_ne)[0] *= (src_ne)[1]; \
|
||||
(src_ne)[1] = (src_ne)[2]; \
|
||||
(src_ne)[2] = (src_ne)[3]; \
|
||||
(src_ne)[3] = 1; \
|
||||
(dst_ne)[0] *= (dst_ne)[1]; \
|
||||
(dst_ne)[1] = (dst_ne)[2]; \
|
||||
(dst_ne)[2] = (dst_ne)[3]; \
|
||||
(dst_ne)[3] = 1; \
|
||||
(src_nb)[1] = (src_nb)[2]; \
|
||||
(src_nb)[2] = (src_nb)[3]; \
|
||||
(dst_nb)[1] = (dst_nb)[2]; \
|
||||
(dst_nb)[2] = (dst_nb)[3]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct htp_unary_vtcm_layout {
|
||||
size_t total_bytes;
|
||||
size_t off_src0;
|
||||
|
||||
@@ -1225,40 +1225,6 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv(ggml_meta
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_amax_part(ggml_metal_library_t lib) {
|
||||
char base[256];
|
||||
char name[256];
|
||||
|
||||
snprintf(base, 256, "kernel_mul_mm_id_amax_part_f32");
|
||||
snprintf(name, 256, "%s", base);
|
||||
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
|
||||
if (!res.pipeline) {
|
||||
res = ggml_metal_library_compile_pipeline(lib, base, name, nullptr);
|
||||
}
|
||||
|
||||
res.smem = 32*sizeof(float);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_amax(ggml_metal_library_t lib) {
|
||||
char base[256];
|
||||
char name[256];
|
||||
|
||||
snprintf(base, 256, "kernel_mul_mm_id_amax_f32");
|
||||
snprintf(name, 256, "%s", base);
|
||||
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
|
||||
if (!res.pipeline) {
|
||||
res = ggml_metal_library_compile_pipeline(lib, base, name, nullptr);
|
||||
}
|
||||
|
||||
res.smem = 32*sizeof(float);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_map0(ggml_metal_library_t lib, int ne02, int ne20) {
|
||||
char base[256];
|
||||
char name[256];
|
||||
@@ -1283,6 +1249,15 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_map0(g
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_zero(ggml_metal_library_t lib) {
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, "kernel_mul_mm_id_zero");
|
||||
if (!res.pipeline) {
|
||||
res = ggml_metal_library_compile_pipeline(lib, "kernel_mul_mm_id_zero", "kernel_mul_mm_id_zero", nullptr);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id(ggml_metal_library_t lib, const ggml_tensor * op) {
|
||||
char base[256];
|
||||
char name[256];
|
||||
@@ -1292,18 +1267,14 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id(ggml_m
|
||||
|
||||
const bool bc_inp = op->src[0]->ne[0] % 32 != 0;
|
||||
|
||||
// src1 prec [TAG_GGML_PREC]
|
||||
const bool amax = ggml_get_op_params_i32(op, 3) == GGML_PREC_F32;
|
||||
|
||||
snprintf(base, 256, "kernel_mul_mm_id_%s_%s", ggml_type_name(tsrc0), ggml_type_name(tsrc1));
|
||||
snprintf(name, 256, "%s_bci=%d_amax=%d", base, bc_inp, amax);
|
||||
snprintf(name, 256, "%s_bci=%d", base, bc_inp);
|
||||
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
|
||||
if (!res.pipeline) {
|
||||
ggml_metal_cv_t cv = ggml_metal_cv_init();
|
||||
|
||||
ggml_metal_cv_set_bool(cv, bc_inp, FC_MUL_MM + 0);
|
||||
ggml_metal_cv_set_bool(cv, amax, FC_MUL_MM + 6);
|
||||
|
||||
res = ggml_metal_library_compile_pipeline(lib, base, name, cv);
|
||||
|
||||
|
||||
@@ -140,8 +140,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_mm
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_map0 (ggml_metal_library_t lib, int ne02, int ne20);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_amax(ggml_metal_library_t lib);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_amax_part(ggml_metal_library_t lib);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_zero (ggml_metal_library_t lib);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_id (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_argmax (ggml_metal_library_t lib, const struct ggml_tensor * op);
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#define N_MM_SIMD_GROUP_X 2
|
||||
#define N_MM_SIMD_GROUP_Y 2
|
||||
|
||||
#define N_MM_NPART_AMAX 256
|
||||
|
||||
#define N_ARGMAX_NPART_MAX 256
|
||||
#define N_ARGMAX_CHUNK 4096
|
||||
|
||||
@@ -573,12 +571,11 @@ typedef struct {
|
||||
} ggml_metal_kargs_mul_mm_id_map0;
|
||||
|
||||
typedef struct {
|
||||
int32_t ne00;
|
||||
int32_t ne01;
|
||||
int32_t ne02;
|
||||
uint64_t nb01;
|
||||
uint64_t nb02;
|
||||
} ggml_metal_kargs_mul_mm_id_amax;
|
||||
int32_t ne0;
|
||||
uint64_t nb1;
|
||||
uint64_t nb2;
|
||||
uint64_t nb21;
|
||||
} ggml_metal_kargs_mul_mm_id_zero;
|
||||
|
||||
typedef struct {
|
||||
int32_t ne00;
|
||||
|
||||
@@ -2812,15 +2812,6 @@ size_t ggml_metal_op_mul_mat_id_extra_ids(const ggml_tensor * op) {
|
||||
return ggml_type_size(GGML_TYPE_I32)*(ne20*ne21 + ne02);
|
||||
}
|
||||
|
||||
size_t ggml_metal_op_mul_mat_id_extra_amax(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_MUL_MAT_ID);
|
||||
|
||||
GGML_UNUSED(op);
|
||||
|
||||
// 2 scaling factors (8 bytes) + N_MM_NPART_AMAX per-threadgroup scales for stage-1
|
||||
return 8 + N_MM_NPART_AMAX*sizeof(float);
|
||||
}
|
||||
|
||||
int ggml_metal_op_mul_mat_id(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_tensor * op = ctx->node(idx);
|
||||
|
||||
@@ -2872,39 +2863,6 @@ int ggml_metal_op_mul_mat_id(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_buffer_id bid_ids = bid_tpe;
|
||||
bid_ids.offs += ggml_metal_op_mul_mat_id_extra_tpe(op);
|
||||
|
||||
ggml_metal_buffer_id bid_amax = bid_ids;
|
||||
bid_amax.offs += ggml_metal_op_mul_mat_id_extra_ids(op);
|
||||
|
||||
// src1 prec [TAG_GGML_PREC]
|
||||
const bool use_amax = ggml_get_op_params_i32(op, 3) == GGML_PREC_F32;
|
||||
|
||||
// src1 rescale factors, computed before the matmul
|
||||
// ref: https://github.com/ggml-org/llama.cpp/pull/26223
|
||||
if (use_amax) {
|
||||
ggml_metal_kargs_mul_mm_id_amax args = {
|
||||
/*.ne00 =*/ ne10,
|
||||
/*.ne01 =*/ ne11,
|
||||
/*.ne02 =*/ ne12,
|
||||
/*.nb01 =*/ nb11,
|
||||
/*.nb02 =*/ nb12,
|
||||
};
|
||||
|
||||
auto pipeline = ggml_metal_library_get_pipeline_mul_mm_id_amax_part(lib);
|
||||
|
||||
const size_t smem = pipeline.smem;
|
||||
|
||||
GGML_ASSERT(smem <= props_dev->max_theadgroup_memory_size);
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline);
|
||||
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_amax, 2);
|
||||
|
||||
ggml_metal_encoder_set_threadgroup_memory_size(enc, smem, 0);
|
||||
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, N_MM_NPART_AMAX, 1, 1, 256, 1, 1);
|
||||
}
|
||||
|
||||
{
|
||||
ggml_metal_kargs_mul_mm_id_map0 args = {
|
||||
ne10,
|
||||
@@ -2935,20 +2893,28 @@ int ggml_metal_op_mul_mat_id(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, 1, 1, 1, ne02, 1, 1);
|
||||
}
|
||||
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
// zero the rows that no expert owns - disjoint from what kernel_mul_mm_id writes
|
||||
{
|
||||
ggml_metal_kargs_mul_mm_id_zero args = {
|
||||
/*.ne0 =*/ ne0,
|
||||
/*.nb1 =*/ nb1,
|
||||
/*.nb2 =*/ nb2,
|
||||
/*.nb21 =*/ nb21,
|
||||
};
|
||||
|
||||
if (use_amax) {
|
||||
auto pipeline = ggml_metal_library_get_pipeline_mul_mm_id_amax(lib);
|
||||
auto pipeline = ggml_metal_library_get_pipeline_mul_mm_id_zero(lib);
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_amax, 0);
|
||||
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_dst, 2);
|
||||
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, 1, 1, 1, 32, 1, 1);
|
||||
|
||||
// the next kernel has to wait for the amax data
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, ne20, ne21, 1, 32, 1, 1);
|
||||
}
|
||||
|
||||
// this barrier is always needed because the next kernel has to wait for the id maps to be computed
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
|
||||
{
|
||||
auto pipeline = ggml_metal_library_get_pipeline_mul_mm_id(lib, op);
|
||||
|
||||
@@ -2977,7 +2943,6 @@ int ggml_metal_op_mul_mat_id(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_encoder_set_buffer (enc, bid_tpe, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_ids, 4);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_dst, 5);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_amax, 6);
|
||||
|
||||
const size_t smem = pipeline.smem;
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ size_t ggml_metal_op_mul_mat_id_extra_tpe(const struct ggml_tensor * op);
|
||||
|
||||
// id map [n_tokens, n_expert]
|
||||
size_t ggml_metal_op_mul_mat_id_extra_ids(const struct ggml_tensor * op);
|
||||
size_t ggml_metal_op_mul_mat_id_extra_amax(const struct ggml_tensor * op);
|
||||
|
||||
// return true if we should use the FA vector kernel for this op
|
||||
bool ggml_metal_op_flash_attn_ext_use_vec(const struct ggml_tensor * op);
|
||||
|
||||
@@ -226,7 +226,6 @@ static size_t ggml_backend_metal_buffer_type_get_alloc_size(ggml_backend_buffer_
|
||||
{
|
||||
res += ggml_metal_op_mul_mat_id_extra_tpe(tensor);
|
||||
res += ggml_metal_op_mul_mat_id_extra_ids(tensor);
|
||||
res += ggml_metal_op_mul_mat_id_extra_amax(tensor);
|
||||
} break;
|
||||
case GGML_OP_FLASH_ATTN_EXT:
|
||||
{
|
||||
|
||||
@@ -185,6 +185,14 @@ kernel void kernel_add_id(
|
||||
|
||||
device float * dst_row = (device float *)((device char *)dst + i1*nb1 + i2*nb2);
|
||||
device const float * src0_row = (device const float *)((device char *)src0 + i1*args.nb01 + i2*args.nb02);
|
||||
|
||||
if (i11 == -1) {
|
||||
for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) {
|
||||
dst_row[i0] = src0_row[i0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
device const float * src1_row = (device const float *)((device char *)src1 + i11*args.nb11);
|
||||
|
||||
for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) {
|
||||
|
||||
@@ -7,7 +7,6 @@ constant short FC_mul_mm_ne12 [[function_constant(FC_MUL_MM + 2)]];
|
||||
constant short FC_mul_mm_ne13 [[function_constant(FC_MUL_MM + 3)]];
|
||||
constant short FC_mul_mm_r2 [[function_constant(FC_MUL_MM + 4)]];
|
||||
constant short FC_mul_mm_r3 [[function_constant(FC_MUL_MM + 5)]];
|
||||
constant bool FC_mul_mm_id_amax [[function_constant(FC_MUL_MM + 6)]];
|
||||
constant int32_t FC_mul_mm_id_map0_ne02 [[function_constant(FC_MUL_MM + 7)]];
|
||||
|
||||
// each block_q contains 16*nl weights
|
||||
@@ -391,8 +390,10 @@ kernel void kernel_mul_mm_id_map0(
|
||||
if (i21 + tpitg < args.ne21) {
|
||||
device const int32_t * src2_i32 = (device const int32_t *) (src2 + (i21 + tpitg)*args.nb21);
|
||||
|
||||
// 0xFFFF marks a skipped slot, it never matches an expert since ne02 fits in a threadgroup
|
||||
FOR_UNROLL (short i20 = 0; i20 < ne20; i20++) {
|
||||
sids[tpitg*ne20 + i20] = src2_i32[i20];
|
||||
const int32_t id = src2_i32[i20];
|
||||
sids[tpitg*ne20 + i20] = id == -1 ? 0xFFFF : (uint16_t) id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,82 +507,27 @@ kernel void kernel_mul_mm_id_map0(
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_mul_mm_id_amax_part_f32(
|
||||
constant ggml_metal_kargs_mul_mm_id_amax & args,
|
||||
device const char * src1,
|
||||
device char * dst,
|
||||
threadgroup char * shmem [[threadgroup(0)]],
|
||||
uint tgpig[[threadgroup_position_in_grid]],
|
||||
ushort tiitg[[thread_index_in_threadgroup]],
|
||||
ushort tiisg[[thread_index_in_simdgroup]],
|
||||
ushort sgitg[[simdgroup_index_in_threadgroup]],
|
||||
ushort ntg[[threads_per_threadgroup]]) {
|
||||
const int nrow = args.ne01*args.ne02;
|
||||
// kernel_mul_mm_id only writes the rows that an expert owns, so the skipped rows are zeroed here
|
||||
kernel void kernel_mul_mm_id_zero(
|
||||
constant ggml_metal_kargs_mul_mm_id_zero & args,
|
||||
device const char * src2,
|
||||
device char * dst,
|
||||
uint3 tgpig[[threadgroup_position_in_grid]],
|
||||
ushort3 tpitg[[thread_position_in_threadgroup]],
|
||||
ushort3 ntg[[threads_per_threadgroup]]) {
|
||||
const int i20 = tgpig.x; // slot
|
||||
const int i21 = tgpig.y; // token
|
||||
|
||||
float lmax = 0.0f;
|
||||
const int32_t id = ((device const int32_t *) (src2 + i21*args.nb21))[i20];
|
||||
|
||||
for (int ir = tgpig; ir < nrow; ir += N_MM_NPART_AMAX) {
|
||||
const int i01 = ir % args.ne01;
|
||||
const int i02 = ir / args.ne01;
|
||||
|
||||
device const float * row = (device const float *) (src1 + i02*args.nb02 + i01*args.nb01);
|
||||
|
||||
for (int i00 = tiitg; i00 < args.ne00; i00 += ntg) {
|
||||
lmax = max(lmax, fabs(row[i00]));
|
||||
}
|
||||
if (id != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
float amax = simd_max(lmax);
|
||||
device float * dst_row = (device float *) (dst + i20*args.nb1 + i21*args.nb2);
|
||||
|
||||
threadgroup float * shared_amax = (threadgroup float *) shmem;
|
||||
|
||||
if (ntg > N_SIMDWIDTH) {
|
||||
if (sgitg == 0) {
|
||||
shared_amax[tiisg] = 0.0f;
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
|
||||
if (tiisg == 0) {
|
||||
shared_amax[sgitg] = amax;
|
||||
}
|
||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||
|
||||
amax = shared_amax[tiisg];
|
||||
amax = simd_max(amax);
|
||||
}
|
||||
|
||||
if (tiitg == 0) {
|
||||
((device float *) (dst + 8))[tgpig] = amax;
|
||||
}
|
||||
}
|
||||
|
||||
kernel void kernel_mul_mm_id_amax_f32(
|
||||
device char * dst,
|
||||
ushort tiitg[[thread_index_in_threadgroup]]) {
|
||||
device const float * part = (device const float *) (dst + 8);
|
||||
|
||||
float amax = 0.0f;
|
||||
|
||||
for (int i = tiitg; i < N_MM_NPART_AMAX; i += N_SIMDWIDTH) {
|
||||
amax = max(amax, part[i]);
|
||||
}
|
||||
|
||||
amax = simd_max(amax);
|
||||
|
||||
if (tiitg == 0) {
|
||||
// leave a comfortable margin below the f16 max of 65504
|
||||
float scale = 1.0f;
|
||||
|
||||
// isfinite: src1 already inf/nan is not ours to fix - keep the
|
||||
// scale at 1.0 instead of turning it into a different failure
|
||||
if (isfinite(amax) && amax > 32768.0f) {
|
||||
scale = exp2(ceil(log2(amax)) - 15.0f);
|
||||
}
|
||||
|
||||
device float * d = (device float *) dst;
|
||||
|
||||
d[0] = 1.0f/scale; // exact: scale is a power of two
|
||||
d[1] = scale;
|
||||
for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) {
|
||||
dst_row[i0] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,7 +553,6 @@ kernel void kernel_mul_mm_id(
|
||||
device const char * htpe,
|
||||
device const char * hids,
|
||||
device char * dst,
|
||||
device const char * amax,
|
||||
threadgroup char * shmem [[threadgroup(0)]],
|
||||
uint3 tgpig[[threadgroup_position_in_grid]],
|
||||
ushort tiitg[[thread_index_in_threadgroup]],
|
||||
@@ -663,10 +608,6 @@ kernel void kernel_mul_mm_id(
|
||||
// skip the upper half of the token tile when the expert did not fill it
|
||||
constexpr short NR1H = NR1/2;
|
||||
|
||||
// power-of-two rescaling
|
||||
const float s1_inv = FC_mul_mm_id_amax ? ((device const float *) amax)[0] : 1.0f;
|
||||
const float s1_scale = FC_mul_mm_id_amax ? ((device const float *) amax)[1] : 1.0f;
|
||||
|
||||
#ifndef GGML_METAL_HAS_TENSOR
|
||||
S0_8x8 ma[4];
|
||||
S1_8x8 mb[2];
|
||||
@@ -772,7 +713,7 @@ kernel void kernel_mul_mm_id(
|
||||
|
||||
const short ib = 4*sx + sy;
|
||||
|
||||
*(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) (*((device T1 *) y + i) * (T1) s1_inv) : 0;
|
||||
*(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0;
|
||||
}
|
||||
} else {
|
||||
const short sx = (tiitg%NL1);
|
||||
@@ -785,7 +726,7 @@ kernel void kernel_mul_mm_id(
|
||||
|
||||
const short ib = 4*sx + sy;
|
||||
|
||||
*(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)((*((device T1_2x4 *) y)) * (T1) s1_inv);
|
||||
*(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y));
|
||||
}
|
||||
#else
|
||||
// load data and store to threadgroup memory
|
||||
@@ -833,7 +774,7 @@ kernel void kernel_mul_mm_id(
|
||||
//const short lx = (tiitg/NL1)%8;
|
||||
//const short ly = i;
|
||||
|
||||
*(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) (*((device T1 *) y + i) * (T1) s1_inv) : 0;
|
||||
*(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0;
|
||||
}
|
||||
} else {
|
||||
const short sx = (tiitg%NL1);
|
||||
@@ -844,7 +785,7 @@ kernel void kernel_mul_mm_id(
|
||||
//const short lx = (tiitg/NL1)%8;
|
||||
//const short ly = i;
|
||||
|
||||
*(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)((*((device T1_2x4 *) y)) * (T1) s1_inv);
|
||||
*(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -935,12 +876,12 @@ kernel void kernel_mul_mm_id(
|
||||
|
||||
int i = tiisg;
|
||||
for (; i < nr0/4; i += 32) {
|
||||
*(D4 + i) = *(C4 + i) * s1_scale;
|
||||
*(D4 + i) = *(C4 + i);
|
||||
}
|
||||
|
||||
i = (4*(nr0/4)) + tiisg;
|
||||
for (; i < nr0; i += 32) {
|
||||
*(D + i) = *(C + i) * s1_scale;
|
||||
*(D + i) = *(C + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3302,7 +3302,8 @@ kernel void kernel_mul_mv_id(
|
||||
uint3 tgpig[[threadgroup_position_in_grid]],
|
||||
ushort tiitg[[thread_index_in_threadgroup]],
|
||||
ushort tiisg[[thread_index_in_simdgroup]],
|
||||
ushort sgitg[[simdgroup_index_in_threadgroup]]) {
|
||||
ushort sgitg[[simdgroup_index_in_threadgroup]],
|
||||
ushort3 ntg [[threads_per_threadgroup]]) {
|
||||
const int iid1 = tgpig.z/args.nei0;
|
||||
const int idx = tgpig.z%args.nei0;
|
||||
|
||||
@@ -3316,11 +3317,20 @@ kernel void kernel_mul_mv_id(
|
||||
const int64_t i1 = idx;
|
||||
const int64_t i2 = i12;
|
||||
|
||||
device char * dst_cur = dst + (i1*args.ne0 + i2*args.ne1*args.ne0)*sizeof(float);
|
||||
|
||||
if (i02 == -1) {
|
||||
device float * dst_f32 = (device float *) dst_cur;
|
||||
const ushort nth = ntg.x*ntg.y*ntg.z;
|
||||
for (int i0 = tiitg; i0 < args.ne0; i0 += nth) {
|
||||
dst_f32[i0] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
device const char * src0_cur = src0s + i02*args.nb02;
|
||||
device const char * src1_cur = src1 + i11*args.nb11 + i12*args.nb12;
|
||||
|
||||
device char * dst_cur = dst + (i1*args.ne0 + i2*args.ne1*args.ne0)*sizeof(float);
|
||||
|
||||
ggml_metal_kargs_mul_mv args0 = {
|
||||
/*.ne00 =*/ args.ne00,
|
||||
/*.ne01 =*/ args.ne01,
|
||||
|
||||
@@ -1047,6 +1047,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_moe_reorder_b;
|
||||
cl_kernel kernel_moe_histogram, kernel_moe_scan, kernel_moe_fill, kernel_moe_scatter;
|
||||
cl_kernel kernel_moe_scatter_stable = nullptr; // deterministic slot assignment
|
||||
cl_kernel kernel_moe_zero_dst;
|
||||
cl_kernel kernel_moe_combine_f32 = nullptr; // fused router-weight mul + cross-expert sum
|
||||
cl_kernel kernel_moe_combine_bias_f32 = nullptr; // same, with the down-projection bias add folded in
|
||||
cl_kernel kernel_mul_mv_id_q4_0_f32_8x_flat;
|
||||
@@ -5547,6 +5548,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
build_program_from_source(backend_ctx, kernel_src.c_str(), CL_moe_compile_opts);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_moe_histogram = clCreateKernel(prog, "kernel_moe_histogram", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_zero_dst = clCreateKernel(prog, "kernel_moe_zero_dst", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_scan = clCreateKernel(prog, "kernel_moe_scan", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_fill = clCreateKernel(prog, "kernel_moe_fill", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_scatter = clCreateKernel(prog, "kernel_moe_scatter", &err), err));
|
||||
@@ -26961,6 +26963,39 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
|
||||
GGML_ASSERT(ne00 == ne10);
|
||||
|
||||
// the matmul kernels only write the rows an expert owns, so zero the skipped rows here
|
||||
{
|
||||
cl_int err;
|
||||
|
||||
cl_buffer_region region;
|
||||
region.origin = offset2;
|
||||
region.size = nb21 * ne21;
|
||||
cl_mem buf_ids = clCreateSubBuffer(extra2->data_device, 0, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err);
|
||||
CL_CHECK(err);
|
||||
|
||||
region.origin = offsetd;
|
||||
region.size = (size_t) ne0 * ne1 * ne2 * sizeof(float);
|
||||
cl_mem buf_dst = clCreateSubBuffer(extrad->data_device, 0, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err);
|
||||
CL_CHECK(err);
|
||||
|
||||
const cl_uint ids_stride = nb21 / sizeof(int);
|
||||
|
||||
cl_kernel kernel_zero = backend_ctx->kernel_moe_zero_dst;
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 0, sizeof(cl_mem), &buf_ids));
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 1, sizeof(cl_mem), &buf_dst));
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 2, sizeof(cl_uint), &ne21));
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 3, sizeof(cl_uint), &ne20));
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 4, sizeof(cl_uint), &ids_stride));
|
||||
CL_CHECK(clSetKernelArg(kernel_zero, 5, sizeof(cl_uint), &ne0));
|
||||
|
||||
size_t zero_local[3] = {1, 1, 64};
|
||||
size_t zero_global[3] = {(size_t) ne21, (size_t) ne20, 64};
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel_zero, 3, zero_global, zero_local, dst);
|
||||
|
||||
CL_CHECK(clReleaseMemObject(buf_ids));
|
||||
CL_CHECK(clReleaseMemObject(buf_dst));
|
||||
}
|
||||
|
||||
int sgs = 32; // subgroup size
|
||||
int nsg = 1; // number of subgroups
|
||||
int nrows = 1; // number of row in src1
|
||||
|
||||
@@ -34,6 +34,14 @@ kernel void kernel_add_id(
|
||||
|
||||
global float * dst_row = (global float *)((global char *)dst + i1*nb1 + i2*nb2);
|
||||
global float * src0_row = (global float *)((global char *)src0 + i1*nb01 + i2*nb02);
|
||||
|
||||
if (i11 == -1) { // skipped slot, add nothing
|
||||
for (int i0 = get_local_id(0); i0 < ne0; i0 += get_local_size(0)) {
|
||||
dst_row[i0] = src0_row[i0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
global float * src1_row = (global float *)((global char *)src1 + i11*nb11);
|
||||
|
||||
for (int i0 = get_local_id(0); i0 < ne0; i0 += get_local_size(0)) {
|
||||
|
||||
@@ -89,6 +89,9 @@ __kernel void kernel_gemv_moe_mxfp4_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f; // each thread calculate partial sum of one output
|
||||
@@ -188,6 +191,9 @@ __kernel void kernel_gemv_moe_mxfp4_f32_ns_wimg(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f;
|
||||
|
||||
@@ -44,6 +44,9 @@ __kernel void kernel_gemv_moe_q4_0_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f; // each thread calculate partial sum of one output
|
||||
|
||||
@@ -45,6 +45,9 @@ __kernel void kernel_gemv_moe_q4_1_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f; // each thread calculate partial sum of one output
|
||||
|
||||
@@ -61,6 +61,9 @@ __kernel void kernel_gemv_moe_q4_k_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int num_superblocks = ne00 / QK_K;
|
||||
int num_subblocks = ne00 / 32;
|
||||
@@ -180,6 +183,9 @@ __kernel void kernel_gemv_moe_q4_k_f32_ns_wimg(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int num_superblocks = ne00 / QK_K;
|
||||
int num_subblocks = ne00 / 32;
|
||||
|
||||
@@ -45,6 +45,9 @@ __kernel void kernel_gemv_moe_q5_0_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f; // each thread calculate partial sum of one output
|
||||
|
||||
@@ -46,6 +46,9 @@ __kernel void kernel_gemv_moe_q5_1_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
uint expert_offset = expert_id * ne00 * ne01 / 32;
|
||||
|
||||
__private float sum = 0.0f; // each thread calculate partial sum of one output
|
||||
|
||||
@@ -62,6 +62,9 @@ __kernel void kernel_gemv_moe_q5_k_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int num_superblocks = ne00 / QK_K;
|
||||
int num_subblocks = ne00 / 32;
|
||||
|
||||
@@ -45,6 +45,9 @@ __kernel void kernel_gemv_moe_q6_k_f32_ns(
|
||||
uint i11 = i20 % ne11;
|
||||
|
||||
uint expert_id = src2[i20];
|
||||
if ((int)expert_id == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int num_superblocks = ne00 / QK_K;
|
||||
int num_subblocks = ne00 / 32; // 8 sub-blocks of 32 per super-block
|
||||
|
||||
@@ -18,6 +18,9 @@ __kernel void kernel_moe_histogram(
|
||||
}
|
||||
|
||||
int expert_id = input[n * n_experts + k];
|
||||
if (expert_id == -1) { // skipped slot, owned by no expert
|
||||
return;
|
||||
}
|
||||
atomic_inc(&hist[expert_id]);
|
||||
}
|
||||
|
||||
@@ -63,6 +66,9 @@ __kernel void kernel_moe_scatter(
|
||||
}
|
||||
|
||||
int val = input[n * n_experts + k];
|
||||
if (val == -1) { // skipped slot, owned by no expert
|
||||
return;
|
||||
}
|
||||
|
||||
int local_slot = atomic_inc(&slot_counter[val]);
|
||||
|
||||
@@ -74,6 +80,32 @@ __kernel void kernel_moe_scatter(
|
||||
emap[tile_idx] = val;
|
||||
}
|
||||
|
||||
// the gemm only writes the rows an expert owns, so the skipped rows (negative ids) are zeroed here
|
||||
__kernel void kernel_moe_zero_dst(
|
||||
__global const int * input,
|
||||
__global float * dst,
|
||||
uint N,
|
||||
uint topK,
|
||||
uint n_experts,
|
||||
uint ne0
|
||||
) {
|
||||
uint n = get_global_id(0);
|
||||
uint k = get_global_id(1);
|
||||
|
||||
if (n >= N || k >= topK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input[n * n_experts + k] != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
__global float * dst_row = dst + ((ulong)n * topK + k) * ne0;
|
||||
for (uint i = get_local_id(2); i < ne0; i += get_local_size(2)) {
|
||||
dst_row[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic replacement for kernel_moe_scatter.
|
||||
//
|
||||
// kernel_moe_scatter takes each token's slot from atomic_inc(slot_counter[expert]),
|
||||
|
||||
@@ -172,6 +172,9 @@ kernel void kernel_mul_mv_id_mxfp4_f32(
|
||||
const int idx = get_group_id(2)%ne20;
|
||||
|
||||
int i02 = ((global int *) (src2 + iid1*nb21))[idx];
|
||||
if (i02 == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int i11 = idx % ne11;
|
||||
int i12 = iid1;
|
||||
|
||||
@@ -101,6 +101,9 @@ kernel void kernel_mul_mv_id_mxfp4_f32_flat(
|
||||
const int idx = get_group_id(2) % ne20;
|
||||
|
||||
uint i02 = ((global uint *) (src2 + offset2 + iid1 * nb21))[idx];
|
||||
if ((int)i02 == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int i11 = idx % ne11;
|
||||
|
||||
|
||||
@@ -267,6 +267,9 @@ kernel void kernel_mul_mv_id_q4_0_f32_8x_flat(
|
||||
const int idx = get_group_id(2)%ne20;
|
||||
|
||||
const int i02 = ((global int *)(src2 + iid1*nb21))[idx];
|
||||
if (i02 == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
const int i11 = idx%ne11;
|
||||
const int i12 = iid1;
|
||||
|
||||
@@ -73,6 +73,9 @@ kernel void kernel_mul_mv_id_q8_0_f32(
|
||||
int idx = get_group_id(2)%ne20;
|
||||
|
||||
int i02 = ((global int *) (src2 + iid1*nb21))[idx];
|
||||
if (i02 == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int i11_ = idx % ne11;
|
||||
int i12_ = iid1;
|
||||
|
||||
@@ -72,6 +72,9 @@ kernel void kernel_mul_mv_id_q8_0_f32_flat(
|
||||
int idx = (int)get_group_id(2)%ne20;
|
||||
|
||||
int i02 = ((global int *) (src2 + iid1*nb21))[idx];
|
||||
if (i02 == -1) { // skipped slot, the dst row is zeroed by kernel_moe_zero_dst
|
||||
return;
|
||||
}
|
||||
|
||||
int i11_ = idx % ne11;
|
||||
int i12_ = iid1;
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
#include <openvino/op/constant.hpp>
|
||||
#include <openvino/op/convert.hpp>
|
||||
#include <openvino/op/gather.hpp>
|
||||
#include <openvino/op/greater_eq.hpp>
|
||||
#include <openvino/op/maximum.hpp>
|
||||
#include <openvino/op/multiply.hpp>
|
||||
#include <openvino/op/reshape.hpp>
|
||||
#include <openvino/op/shape_of.hpp>
|
||||
#include <openvino/op/unsqueeze.hpp>
|
||||
|
||||
namespace ov {
|
||||
namespace frontend {
|
||||
@@ -52,8 +56,16 @@ OutputVector translate_add_id(const NodeContext & context) {
|
||||
ids = std::make_shared<ov::op::v0::Convert>(ids, ov::element::i32);
|
||||
}
|
||||
|
||||
// an id of -1 marks a skipped slot: gather row 0 for it and zero the bias with a mask
|
||||
auto zero = ov::op::v0::Constant::create(ids.get_element_type(), ov::Shape{}, {0});
|
||||
ov::Output<ov::Node> keep = std::make_shared<ov::op::v1::GreaterEqual>(ids, zero);
|
||||
keep = std::make_shared<ov::op::v0::Convert>(keep, bias.get_element_type());
|
||||
keep = std::make_shared<ov::op::v0::Unsqueeze>(keep, ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}));
|
||||
ids = std::make_shared<ov::op::v1::Maximum>(ids, zero);
|
||||
|
||||
auto gather_axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{}, {0});
|
||||
ov::Output<ov::Node> selected_bias = std::make_shared<ov::op::v8::Gather>(bias, ids, gather_axis);
|
||||
selected_bias = std::make_shared<ov::op::v1::Multiply>(selected_bias, keep);
|
||||
selected_bias = std::make_shared<ov::op::v1::Reshape>(
|
||||
selected_bias, std::make_shared<ov::op::v3::ShapeOf>(input, ov::element::i64), false);
|
||||
|
||||
|
||||
@@ -517,6 +517,11 @@ bool socket_t::impl::rdma_recv(void * data, size_t size) {
|
||||
|
||||
int slot = (int)wc.wr_id;
|
||||
size_t got = wc.byte_len;
|
||||
if (got > rem) {
|
||||
LOG_ERROR("RDMA recv overrun: got=%zu rem=%zu\n", got, rem);
|
||||
c->post_rx(slot);
|
||||
return false;
|
||||
}
|
||||
memcpy(dst, c->rx_slot(slot), got);
|
||||
|
||||
if (!c->post_rx(slot)) return false;
|
||||
|
||||
@@ -26,6 +26,15 @@ static void add_id_kernel(
|
||||
float* dst_row = (float*)((char*)dst + i1 * nb1 + i2 * nb2);
|
||||
const float* src0_row =
|
||||
(const float*)((const char*)src0 + i1 * nb01 + i2 * nb02);
|
||||
|
||||
if (i11 == -1) { // skipped slot, add nothing
|
||||
for (int64_t i0 = item_ct1.get_local_id(2); i0 < ne0;
|
||||
i0 += item_ct1.get_local_range(2)) {
|
||||
dst_row[i0] = src0_row[i0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const float* src1_row = (const float*)((const char*)src1 + i11 * nb11);
|
||||
|
||||
for (int64_t i0 = item_ct1.get_local_id(2); i0 < ne0;
|
||||
|
||||
@@ -5700,6 +5700,23 @@ __dpct_inline__ static void k_copy_dst_from_contiguous(
|
||||
}
|
||||
}
|
||||
|
||||
__dpct_inline__ static void k_zero_dst_rows(
|
||||
char *__restrict__ dst_original,
|
||||
const mmid_row_mapping *__restrict__ row_mapping, int64_t ne0, size_t nb1,
|
||||
size_t nb2, const sycl::nd_item<3> &item_ct1) {
|
||||
int32_t i = item_ct1.get_group(2);
|
||||
|
||||
const int32_t i1 = row_mapping[i].i1;
|
||||
const int32_t i2 = row_mapping[i].i2;
|
||||
|
||||
float * dst_row_original = (float *)(dst_original + i1*nb1 + i2*nb2);
|
||||
|
||||
for (int j = item_ct1.get_local_id(2); j < ne0;
|
||||
j += item_ct1.get_local_range(2)) {
|
||||
dst_row_original[j] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true when ggml_sycl_mul_mat_id takes the fallback path that requires stream synchronization
|
||||
static bool ggml_sycl_mul_mat_id_needs_sync(const ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
@@ -5785,14 +5802,18 @@ static void mmid_counting_sort_rows(
|
||||
int64_t n_ids, int64_t n_as, int64_t n_routed_rows,
|
||||
std::vector<int64_t> & expert_counts,
|
||||
std::vector<int64_t> & expert_row_offsets,
|
||||
std::vector<mmid_row_mapping> & routed_row_src) {
|
||||
std::vector<mmid_row_mapping> & routed_row_src,
|
||||
std::vector<mmid_row_mapping> & skipped_row_dst) {
|
||||
|
||||
// frequencies: how many routed rows each expert "owns"
|
||||
expert_counts.assign(n_as, 0);
|
||||
for (int64_t iid1 = 0; iid1 < ids->ne[1]; iid1++) {
|
||||
for (int64_t id = 0; id < n_ids; id++) {
|
||||
const int32_t row_id_i = *(const int32_t *) (ids_host + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
GGML_ASSERT(row_id_i >= 0 && row_id_i < n_as);
|
||||
GGML_ASSERT(row_id_i == -1 || (row_id_i >= 0 && row_id_i < n_as));
|
||||
if (row_id_i == -1) {
|
||||
continue;
|
||||
}
|
||||
expert_counts[row_id_i]++;
|
||||
}
|
||||
}
|
||||
@@ -5805,10 +5826,17 @@ static void mmid_counting_sort_rows(
|
||||
|
||||
std::vector<int64_t> expert_row_next = expert_row_offsets;
|
||||
routed_row_src.resize(n_routed_rows);
|
||||
skipped_row_dst.clear();
|
||||
for (int64_t iid1 = 0; iid1 < ids->ne[1]; iid1++) {
|
||||
for (int64_t id = 0; id < n_ids; id++) {
|
||||
const int32_t row_id_i = *(const int32_t *) (ids_host + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
GGML_ASSERT(row_id_i >= 0 && row_id_i < n_as);
|
||||
GGML_ASSERT(row_id_i == -1 || (row_id_i >= 0 && row_id_i < n_as));
|
||||
|
||||
// a skipped slot has no expert, its dst row is zeroed instead
|
||||
if (row_id_i == -1) {
|
||||
skipped_row_dst.push_back({(int32_t) id, (int32_t) iid1});
|
||||
continue;
|
||||
}
|
||||
|
||||
// find and validate the next free row for a given expert (row_id_i)
|
||||
const int64_t routed_row = expert_row_next[row_id_i]++;
|
||||
@@ -5817,6 +5845,7 @@ static void mmid_counting_sort_rows(
|
||||
routed_row_src[routed_row] = {(int32_t) id, (int32_t) iid1};
|
||||
}
|
||||
}
|
||||
routed_row_src.resize(expert_row_offsets[n_as]);
|
||||
}
|
||||
|
||||
static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
@@ -5884,7 +5913,7 @@ static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
for (int64_t iid1 = 0; iid1 < ids->ne[1]; iid1++) {
|
||||
for (int64_t id = 0; id < n_ids; id++) {
|
||||
const int32_t i02 = *(const int32_t *) (ids_host.data() + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
const int64_t i11 = id % ne11;
|
||||
const int64_t i12 = iid1;
|
||||
@@ -5892,6 +5921,12 @@ static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
const int64_t i1 = id;
|
||||
const int64_t i2 = i12;
|
||||
|
||||
if (i02 == -1) { // skipped slot
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(
|
||||
stream->memset(dst_original + i1*nb1 + i2*nb2, 0, ne0*sizeof(float))));
|
||||
continue;
|
||||
}
|
||||
|
||||
src0_row.data = src0_original + i02*nb02;
|
||||
src1_row.data = src1_original + i11*nb11 + i12*nb12;
|
||||
dst_row.data = dst_original + i1*nb1 + i2*nb2;
|
||||
@@ -5913,20 +5948,43 @@ static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
std::vector<int64_t> expert_row_offsets;
|
||||
// the sources (slot/token pairs) of contiguous rows to guide k_copy_src1_to_contiguous
|
||||
std::vector<mmid_row_mapping> & routed_row_src = ctx.mmid_row_mapping_host;
|
||||
// the slot/token pairs of the skipped slots, whose dst rows are zeroed
|
||||
std::vector<mmid_row_mapping> skipped_row_dst;
|
||||
|
||||
mmid_counting_sort_rows(ids, ids_host.data(), n_ids, n_as, n_routed_rows,
|
||||
expert_row_counts, expert_row_offsets, routed_row_src);
|
||||
expert_row_counts, expert_row_offsets, routed_row_src, skipped_row_dst);
|
||||
|
||||
const int64_t n_valid_rows = (int64_t) routed_row_src.size();
|
||||
|
||||
ggml_sycl_pool_alloc<mmid_row_mapping> dev_row_mapping(ctx.pool(), n_routed_rows);
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(
|
||||
stream->memcpy(dev_row_mapping.get(), routed_row_src.data(), n_routed_rows*sizeof(mmid_row_mapping))));
|
||||
stream->memcpy(dev_row_mapping.get(), routed_row_src.data(), n_valid_rows*sizeof(mmid_row_mapping))));
|
||||
|
||||
const unsigned int max_work_group_size = ggml_sycl_info().max_work_group_sizes[ctx.device];
|
||||
assert(max_work_group_size % (WARP_SIZE * WARP_SIZE) == 0);
|
||||
|
||||
{
|
||||
if (!skipped_row_dst.empty()) {
|
||||
ggml_sycl_pool_alloc<mmid_row_mapping> dev_skipped(ctx.pool(), skipped_row_dst.size());
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(
|
||||
stream->memcpy(dev_skipped.get(), skipped_row_dst.data(), skipped_row_dst.size()*sizeof(mmid_row_mapping))));
|
||||
|
||||
sycl::range<3> block_dims(1, 1, std::min((unsigned int)ne0, max_work_group_size));
|
||||
sycl::range<3> grid_dims(1, 1, skipped_row_dst.size());
|
||||
stream->submit([&](sycl::handler &cgh) {
|
||||
mmid_row_mapping *__restrict dev_skipped_get = dev_skipped.get();
|
||||
|
||||
cgh.parallel_for(
|
||||
sycl::nd_range<3>(grid_dims * block_dims, block_dims),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
k_zero_dst_rows(dst_original, dev_skipped_get, ne0, nb1, nb2, item_ct1);
|
||||
});
|
||||
});
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(stream->wait()));
|
||||
}
|
||||
|
||||
if (n_valid_rows > 0) {
|
||||
sycl::range<3> block_dims(1, 1, std::min((unsigned int)ne10, max_work_group_size));
|
||||
sycl::range<3> grid_dims(1, 1, n_routed_rows);
|
||||
sycl::range<3> grid_dims(1, 1, n_valid_rows);
|
||||
stream->submit([&](sycl::handler &cgh) {
|
||||
char *__restrict src1_contiguous_get =
|
||||
src1_contiguous.get();
|
||||
@@ -5992,9 +6050,9 @@ static void ggml_sycl_mul_mat_id(ggml_backend_sycl_context & ctx,
|
||||
ggml_sycl_mul_mat(ctx, &src0_row, &src1_row, &dst_row);
|
||||
}
|
||||
|
||||
{
|
||||
if (n_valid_rows > 0) {
|
||||
sycl::range<3> block_dims(1, 1, std::min((unsigned int)ne0, max_work_group_size));
|
||||
sycl::range<3> grid_dims(1, 1, n_routed_rows);
|
||||
sycl::range<3> grid_dims(1, 1, n_valid_rows);
|
||||
stream->submit([&](sycl::handler &cgh) {
|
||||
const char *__restrict dst_contiguous_get =
|
||||
dst_contiguous.get();
|
||||
|
||||
@@ -3149,7 +3149,6 @@ static void mul_mat_vec_q_moe(
|
||||
const int expert_idx = item_ct1.get_group(1);
|
||||
const int i02 = ids_dev[expert_idx];
|
||||
|
||||
const char * vx = (const char *) vx_base + (size_t) i02 * expert_weight_stride;
|
||||
const char * vy = (const char *) vy_base + (size_t) expert_idx * src1_row_stride;
|
||||
float * dst = (float *) ((char *) dst_base + (size_t) expert_idx * dst_row_stride);
|
||||
|
||||
@@ -3159,6 +3158,15 @@ static void mul_mat_vec_q_moe(
|
||||
return;
|
||||
}
|
||||
|
||||
if (i02 == -1) { // skipped slot
|
||||
if (item_ct1.get_local_id(2) == 0) {
|
||||
dst[row] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const char * vx = (const char *) vx_base + (size_t) i02 * expert_weight_stride;
|
||||
|
||||
const int blocks_per_row = ncols / qk;
|
||||
constexpr int blocks_per_warp = (vdr * WARP_SIZE + qi - 1) / qi;
|
||||
|
||||
@@ -3355,7 +3363,6 @@ static void mul_mat_vec_q_moe_reorder(
|
||||
const int expert_idx = item_ct1.get_group(1);
|
||||
const int i02 = ids_dev[expert_idx];
|
||||
|
||||
const char * vx = (const char *) vx_base + (size_t) i02 * expert_weight_stride;
|
||||
const char * vy = (const char *) vy_base + (size_t) expert_idx * src1_row_stride;
|
||||
float * dst = (float *) ((char *) dst_base + (size_t) expert_idx * dst_row_stride);
|
||||
|
||||
@@ -3366,6 +3373,15 @@ static void mul_mat_vec_q_moe_reorder(
|
||||
|
||||
const auto sg = item_ct1.get_sub_group();
|
||||
|
||||
if (i02 == -1) { // skipped slot
|
||||
if (sg.leader()) {
|
||||
dst[row] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const char * vx = (const char *) vx_base + (size_t) i02 * expert_weight_stride;
|
||||
|
||||
constexpr int vdr_mmvq = reorder_vec_dot_q_sycl::gtype == GGML_TYPE_Q8_0 ?
|
||||
VDR_Q8_0_Q8_1_MMVQ : block_traits::vdr_mmvq;
|
||||
const int blocks_per_row = ncols / block_traits::qk;
|
||||
|
||||
@@ -222,6 +222,9 @@ struct vk_op_count_experts_push_constants {
|
||||
uint32_t ne00L;
|
||||
uint32_t row_tile_size;
|
||||
uint32_t row_ids_offset;
|
||||
uint32_t ne0_dst;
|
||||
uint32_t stride_dst;
|
||||
uint32_t batch_stride_dst;
|
||||
};
|
||||
|
||||
struct vk_op_glu_push_constants {
|
||||
|
||||
@@ -3585,9 +3585,9 @@ void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_count_equal_i32, "count_equal_i32", count_equal_i32_len, count_equal_i32_data, "main", 3, sizeof(vk_op_push_constants), {512, 1, 1}, { device->subgroup_size }, 1);
|
||||
|
||||
if (device->subgroup_arithmetic && device->subgroup_vote && device->subgroup_require_full_support) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_subgroup_len, count_experts_subgroup_data, "main", 2, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true, true);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_subgroup_len, count_experts_subgroup_data, "main", 3, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true, true);
|
||||
} else {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_len, count_experts_data, "main", 2, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_count_experts, "count_experts", count_experts_len, count_experts_data, "main", 3, sizeof(vk_op_count_experts_push_constants), {1, 1, 1}, {}, 1, true);
|
||||
}
|
||||
|
||||
// comb holds a token's 4x4 matrix in one 16-lane slice of a subgroup, so it
|
||||
@@ -6005,20 +6005,49 @@ void ggml_vk_matmul(
|
||||
ctx->prealloc_split_k_need_sync = true;
|
||||
}
|
||||
|
||||
static bool ggml_vk_get_mul_mat_mat_f16acc(ggml_backend_vk_context * ctx, ggml_type src0_type, ggml_type src1_type, ggml_prec prec) {
|
||||
static bool ggml_vk_src1_prec_allows_q8(const ggml_tensor * dst) {
|
||||
switch (ggml_prec_get_src(dst, 1)) {
|
||||
case GGML_PREC_UNDEFINED:
|
||||
case GGML_PREC_Q8:
|
||||
case GGML_PREC_Q4:
|
||||
return true;
|
||||
case GGML_PREC_F32:
|
||||
case GGML_PREC_BF16:
|
||||
case GGML_PREC_F16:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ggml_vk_prec_allows_f16acc(ggml_prec prec) {
|
||||
switch (prec) {
|
||||
case GGML_PREC_F32:
|
||||
case GGML_PREC_BF16:
|
||||
return false;
|
||||
case GGML_PREC_UNDEFINED:
|
||||
case GGML_PREC_F16:
|
||||
case GGML_PREC_Q8:
|
||||
case GGML_PREC_Q4:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ggml_vk_get_mul_mat_mat_f16acc(ggml_backend_vk_context * ctx, ggml_type src0_type, ggml_type src1_type, ggml_prec acc_prec) {
|
||||
if (src0_type == GGML_TYPE_F32 || src0_type == GGML_TYPE_BF16) return false;
|
||||
if (src1_type == GGML_TYPE_Q8_1) return false;
|
||||
const bool prec_allows_f16 = ggml_vk_prec_allows_f16acc(acc_prec);
|
||||
if (src0_type == GGML_TYPE_F16) {
|
||||
return prec == GGML_PREC_DEFAULT && ctx->device->fp16 && !(ctx->device->coopmat_support && !ctx->device->coopmat_acc_f16_support);
|
||||
return prec_allows_f16 && ctx->device->fp16 && !(ctx->device->coopmat_support && !ctx->device->coopmat_acc_f16_support);
|
||||
}
|
||||
// quant types
|
||||
if (ctx->device->coopmat2) {
|
||||
return prec == GGML_PREC_DEFAULT;
|
||||
return prec_allows_f16;
|
||||
}
|
||||
if (ctx->device->coopmat_support) {
|
||||
return ctx->device->fp16 && ctx->device->coopmat_acc_f16_support && prec == GGML_PREC_DEFAULT;
|
||||
return ctx->device->fp16 && ctx->device->coopmat_acc_f16_support && prec_allows_f16;
|
||||
}
|
||||
return ctx->device->fp16 && prec == GGML_PREC_DEFAULT;
|
||||
return ctx->device->fp16 && prec_allows_f16;
|
||||
}
|
||||
|
||||
static const std::vector<vk_matmul_pipeline_pair>* ggml_vk_get_mul_mat_mat_pipeline_map(
|
||||
@@ -6386,26 +6415,26 @@ static void ggml_vk_mul_mat_q_f16(ggml_backend_vk_context * ctx, vk_context& sub
|
||||
|
||||
const bool is_coopmat = ctx->device->coopmat_support || ctx->device->coopmat2;
|
||||
const bool can_quantize_y = (ctx->device->integer_dot_product || ctx->device->coopmat_int_support) &&
|
||||
src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && (ne11 * ne10) % 4 == 0;
|
||||
src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && (ne11 * ne10) % 4 == 0 && ggml_vk_src1_prec_allows_q8(dst);
|
||||
// coopmat shaders are fast enough that memory bandwidth reduction from f16 activations helps
|
||||
const bool prefer_f16_b = is_coopmat && ggml_is_quantized(src0->type) && src1->type == GGML_TYPE_F32;
|
||||
|
||||
const ggml_type y_kernel_type = ggml_vk_mul_mat_b_type(ctx, src0->type, src1, f16_type,
|
||||
(ggml_prec)dst->op_params[0], false, can_quantize_y, prefer_f16_b);
|
||||
ggml_prec_get_acc(dst), false, can_quantize_y, prefer_f16_b);
|
||||
|
||||
const bool quantize_y = y_kernel_type == GGML_TYPE_Q8_1;
|
||||
const bool y_f32_kernel = y_kernel_type == GGML_TYPE_F32;
|
||||
const bool y_needs_reformat = !quantize_y && (!ggml_vk_dim01_contiguous(src1) || y_kernel_type != src1->type);
|
||||
|
||||
const std::vector<vk_matmul_pipeline_pair>* mmp_map =
|
||||
ggml_vk_get_mul_mat_mat_pipeline_map(ctx, src0->type, y_kernel_type, (ggml_prec)dst->op_params[0]);
|
||||
ggml_vk_get_mul_mat_mat_pipeline_map(ctx, src0->type, y_kernel_type, ggml_prec_get_acc(dst));
|
||||
|
||||
const bool qx_needs_dequant = mmp_map == nullptr || x_non_contig;
|
||||
const bool qy_needs_dequant = y_needs_reformat;
|
||||
|
||||
if (qx_needs_dequant) {
|
||||
// dequant src0 to f16
|
||||
mmp_map = ggml_vk_get_mul_mat_mat_pipeline_map(ctx, f16_type, y_kernel_type, (ggml_prec)dst->op_params[0]);
|
||||
mmp_map = ggml_vk_get_mul_mat_mat_pipeline_map(ctx, f16_type, y_kernel_type, ggml_prec_get_acc(dst));
|
||||
}
|
||||
|
||||
GGML_ASSERT(mmp_map != nullptr);
|
||||
@@ -6736,7 +6765,7 @@ static void ggml_vk_mul_mat_vec_q_f16(ggml_backend_vk_context * ctx, vk_context&
|
||||
|
||||
const ggml_type y_kernel_type = widen_bf16 ? GGML_TYPE_F32 : src1->type;
|
||||
const bool f16_f32_kernel = y_kernel_type == GGML_TYPE_F32;
|
||||
bool quantize_y = ctx->device->integer_dot_product && src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && !y_non_contig && (ne11 * ne10) % 4 == 0 && ggml_vk_should_use_mmvq(ctx->device, ne01, ne11, ne10, src0->type);
|
||||
bool quantize_y = ctx->device->integer_dot_product && src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && !y_non_contig && (ne11 * ne10) % 4 == 0 && (swap_inputs || ggml_vk_src1_prec_allows_q8(dst)) && ggml_vk_should_use_mmvq(ctx->device, ne01, ne11, ne10, src0->type);
|
||||
|
||||
vk_pipeline to_fp16_vk_0 = nullptr;
|
||||
vk_pipeline to_fp16_vk_1 = nullptr;
|
||||
@@ -7435,26 +7464,26 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
|
||||
const bool y_decode_vector_staging = false;
|
||||
#endif
|
||||
const bool can_quantize_y = ctx->device->integer_dot_product && src1->type == GGML_TYPE_F32 &&
|
||||
ggml_is_contiguous(src1) && (ne11 * ne10) % 4 == 0;
|
||||
ggml_is_contiguous(src1) && (ne11 * ne10) % 4 == 0 && ggml_vk_src1_prec_allows_q8(dst);
|
||||
// Intel coopmat1 matrix cores prefer f16 activations for quantized weights
|
||||
const bool prefer_f16_b = ctx->device->coopmat_support && !ctx->device->coopmat2 &&
|
||||
ctx->device->vendor_id == VK_VENDOR_ID_INTEL &&
|
||||
ggml_is_quantized(src0->type) && src1->type == GGML_TYPE_F32;
|
||||
|
||||
const ggml_type y_kernel_type = ggml_vk_mul_mat_b_type(ctx, src0->type, src1, f16_type,
|
||||
(ggml_prec)dst->op_params[0], true, can_quantize_y, prefer_f16_b);
|
||||
ggml_prec_get_acc(dst), true, can_quantize_y, prefer_f16_b);
|
||||
|
||||
const bool quantize_y = y_kernel_type == GGML_TYPE_Q8_1;
|
||||
const bool y_f32_kernel = y_kernel_type == GGML_TYPE_F32;
|
||||
|
||||
const std::vector<vk_matmul_pipeline_pair>* mmp_map =
|
||||
ggml_vk_get_mul_mat_mat_pipeline_map(ctx, src0->type, y_kernel_type, (ggml_prec)dst->op_params[0], true);
|
||||
ggml_vk_get_mul_mat_mat_pipeline_map(ctx, src0->type, y_kernel_type, ggml_prec_get_acc(dst), true);
|
||||
|
||||
const bool qx_needs_dequant = mmp_map == nullptr || x_non_contig;
|
||||
|
||||
if (qx_needs_dequant) {
|
||||
// dequant src0 to f16
|
||||
mmp_map = ggml_vk_get_mul_mat_mat_pipeline_map(ctx, f16_type, y_kernel_type, (ggml_prec)dst->op_params[0], true);
|
||||
mmp_map = ggml_vk_get_mul_mat_mat_pipeline_map(ctx, f16_type, y_kernel_type, ggml_prec_get_acc(dst), true);
|
||||
}
|
||||
|
||||
GGML_ASSERT(mmp_map != nullptr);
|
||||
@@ -7614,16 +7643,20 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
|
||||
ggml_vk_sync_buffers(ctx, subctx);
|
||||
}
|
||||
{
|
||||
// this also zeroes the dst rows of the skipped slots (negative ids)
|
||||
vk_op_count_experts_push_constants pc = { (uint32_t)nei0,
|
||||
(uint32_t)nei1,
|
||||
(uint32_t)(nbi0 / ggml_type_size(ids->type)),
|
||||
(uint32_t)(nbi1 / ggml_type_size(ids->type)),
|
||||
(uint32_t)(get_misalign_bytes(ctx, ids) / ggml_type_size(ids->type)),
|
||||
(uint32_t)n_as,
|
||||
0, 0, bn, (uint32_t)row_ids_offset };
|
||||
0, 0, bn, (uint32_t)row_ids_offset,
|
||||
(uint32_t)ne01,
|
||||
(uint32_t)(dst->nb[1] / sizeof(float)),
|
||||
(uint32_t)(dst->nb[2] / sizeof(float)) };
|
||||
init_pushconst_fastdiv(pc);
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, count_experts,
|
||||
{ d_ids, expert_count_buf }, pc,
|
||||
{ d_ids, expert_count_buf, vk_subbuffer{ d_D, d_buf_offset, d_sz } }, pc,
|
||||
{ 1, 1, 1 });
|
||||
}
|
||||
|
||||
@@ -7749,7 +7782,7 @@ static void ggml_vk_mul_mat_vec_id_q_f16(ggml_backend_vk_context * ctx, vk_conte
|
||||
const bool y_non_contig = !ggml_vk_dim01_contiguous(src1);
|
||||
|
||||
const bool f16_f32_kernel = src1->type == GGML_TYPE_F32;
|
||||
bool quantize_y = ctx->device->integer_dot_product && src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && !y_non_contig && (ne11 * ne10) % 4 == 0 && ggml_vk_should_use_mmvq(ctx->device, ne01, ne12, ne10, src0->type);
|
||||
bool quantize_y = ctx->device->integer_dot_product && src1->type == GGML_TYPE_F32 && ggml_is_contiguous(src1) && !y_non_contig && (ne11 * ne10) % 4 == 0 && ggml_vk_src1_prec_allows_q8(dst) && ggml_vk_should_use_mmvq(ctx->device, ne01, ne12, ne10, src0->type);
|
||||
|
||||
vk_pipeline to_fp16_vk_0 = nullptr;
|
||||
vk_pipeline to_fp16_vk_1 = nullptr;
|
||||
@@ -8128,7 +8161,7 @@ void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx, const
|
||||
uint32_t workgroups_y = (uint32_t)neq2;
|
||||
uint32_t workgroups_z = (uint32_t)neq3;
|
||||
|
||||
const bool f32acc = !ctx->device->fp16 || dst->op_params[3] == GGML_PREC_F32 || k->type == GGML_TYPE_BF16;
|
||||
const bool f32acc = !ctx->device->fp16 || !ggml_vk_prec_allows_f16acc(ggml_prec_get_acc(dst)) || k->type == GGML_TYPE_BF16;
|
||||
|
||||
// dequant K/V once into an f16 scratch, reordered KV layout so FA can read without a stride
|
||||
auto is_dense_kv_cache = [](const ggml_tensor * t) {
|
||||
@@ -15516,9 +15549,9 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
// If there's not enough shared memory for row_ids and the result tile, fallback to CPU
|
||||
return false;
|
||||
}
|
||||
if (ggml_get_op_params_i32(op, 3) == GGML_PREC_F32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ggml_prec_get_src(op, 1) == GGML_PREC_F32) {
|
||||
return false;
|
||||
}
|
||||
switch (src0_type) {
|
||||
case GGML_TYPE_F32:
|
||||
|
||||
@@ -27,14 +27,22 @@ void main() {
|
||||
const uint i1 = gl_WorkGroupID.x;
|
||||
const uint i2 = gl_WorkGroupID.y;
|
||||
|
||||
const uint i11 = data_c[i1 + i2 * p.s21];
|
||||
const int i11 = data_c[i1 + i2 * p.s21];
|
||||
|
||||
const uint s1 = p.ne0;
|
||||
const uint s2 = p.ne0 * p.ne1;
|
||||
|
||||
const uint d0 = i1 * s1 + i2 * s2;
|
||||
const uint a0 = i1 * p.s01 + i2 * p.s02;
|
||||
const uint b0 = i11 * p.s11;
|
||||
|
||||
if (i11 == -1) { // skipped slot, add nothing
|
||||
for (uint i0 = gl_LocalInvocationID.x; i0 < p.ne0; i0 += BLOCK_SIZE) {
|
||||
data_d[d0 + i0] = data_a[a0 + i0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint b0 = uint(i11) * p.s11;
|
||||
|
||||
for (uint i0 = gl_LocalInvocationID.x; i0 < p.ne0; i0 += BLOCK_SIZE) {
|
||||
data_d[d0 + i0] = data_a[a0 + i0] + data_b[b0 + i0];
|
||||
|
||||
@@ -23,6 +23,9 @@ layout (push_constant) uniform parameter
|
||||
uint32_t ne00L;
|
||||
uint32_t row_tile_size;
|
||||
uint32_t row_ids_offset;
|
||||
uint32_t ne0_dst;
|
||||
uint32_t stride_dst;
|
||||
uint32_t batch_stride_dst;
|
||||
} p;
|
||||
|
||||
#define BLOCK_SIZE 256
|
||||
@@ -32,6 +35,7 @@ layout(local_size_x = BLOCK_SIZE, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (binding = 0) readonly buffer A {uint data_a[];};
|
||||
layout (binding = 1) writeonly buffer D {uint data_d[];};
|
||||
layout (binding = 2) writeonly buffer DST {float data_dst[];};
|
||||
|
||||
shared uint vals[EXPERTS_PER_CHUNK];
|
||||
shared uint offsets[EXPERTS_PER_CHUNK];
|
||||
@@ -47,6 +51,26 @@ void write_tiles(uint expert, uint count, uint tile_begin, uint n_tiles) {
|
||||
}
|
||||
}
|
||||
|
||||
// the matmul writes only the rows that an expert owns, so zero the rows of the skipped slots
|
||||
void zero_skipped_rows() {
|
||||
const uint num_elements = p.ne00 * p.ne01;
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
|
||||
for (uint idx = 0; idx < num_elements; idx++) {
|
||||
const uint i01 = fastdiv(idx, p.ne00mp, p.ne00L);
|
||||
const uint i00 = idx - i01 * p.ne00;
|
||||
|
||||
if (int(data_a[p.a_offset + i01 * p.nb01 + i00 * p.nb00]) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint d_off = i00 * p.stride_dst + i01 * p.batch_stride_dst;
|
||||
for (uint i = tid; i < p.ne0_dst; i += BLOCK_SIZE) {
|
||||
data_dst[d_off + i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
const uint num_elements = p.ne00 * p.ne01;
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
@@ -123,4 +147,6 @@ void main() {
|
||||
if (tid == 0) {
|
||||
data_d[0] = total_tiles;
|
||||
}
|
||||
|
||||
zero_skipped_rows();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ layout (push_constant) uniform parameter
|
||||
|
||||
#ifdef MUL_MAT_ID
|
||||
uint expert_id;
|
||||
bool expert_skip; // an id of -1 skips the slot, the result row is zeroed
|
||||
#endif
|
||||
|
||||
void get_offsets(out uint a_offset, out uint b_offset, out uint d_offset) {
|
||||
@@ -63,7 +64,9 @@ void get_offsets(out uint a_offset, out uint b_offset, out uint d_offset) {
|
||||
batch_idx_a = i03 * p.ne02 + i02;
|
||||
}
|
||||
#else
|
||||
expert_id = data_ids[expert_i0 + p.expert_i1 * p.nbi1];
|
||||
const int expert = data_ids[expert_i0 + p.expert_i1 * p.nbi1];
|
||||
expert_skip = expert == -1;
|
||||
expert_id = expert_skip ? 0 : uint(expert); // clamp, so that the offsets below stay in range
|
||||
#endif
|
||||
|
||||
a_offset =
|
||||
@@ -113,6 +116,9 @@ void reduce_result(inout FLOAT_TYPE temp[NUM_COLS][NUM_ROWS], const in uint32_t
|
||||
const uint expert_i0 = gl_GlobalInvocationID.y;
|
||||
temp[j][n] *= FLOAT_TYPE(data_fuse1[expert_i0]);
|
||||
}
|
||||
if (expert_skip) {
|
||||
temp[j][n] = FLOAT_TYPE(0);
|
||||
}
|
||||
#else
|
||||
if ((p.fusion_flags & MAT_VEC_FUSION_FLAGS_BIAS0) != 0) {
|
||||
temp[j][n] += FLOAT_TYPE(data_fuse0[j*p.batch_stride_d + d_offset + first_row + n]);
|
||||
@@ -168,6 +174,9 @@ void reduce_result(FLOAT_TYPE temp[NUM_COLS][NUM_ROWS], const in uint32_t d_offs
|
||||
const uint expert_i0 = gl_GlobalInvocationID.y;
|
||||
temp[j][n] *= FLOAT_TYPE(data_fuse1[expert_i0]);
|
||||
}
|
||||
if (expert_skip) {
|
||||
temp[j][n] = FLOAT_TYPE(0);
|
||||
}
|
||||
#else
|
||||
if ((p.fusion_flags & MAT_VEC_FUSION_FLAGS_BIAS0) != 0) {
|
||||
temp[j][n] += FLOAT_TYPE(data_fuse0[j*p.batch_stride_d + d_offset + first_row + n]);
|
||||
@@ -213,6 +222,9 @@ void reduce_result(FLOAT_TYPE temp[NUM_COLS][NUM_ROWS], const in uint32_t d_offs
|
||||
const uint expert_i0 = gl_GlobalInvocationID.y;
|
||||
tmpsh[j][n][0] *= FLOAT_TYPE(data_fuse1[expert_i0]);
|
||||
}
|
||||
if (expert_skip) {
|
||||
tmpsh[j][n][0] = FLOAT_TYPE(0);
|
||||
}
|
||||
#else
|
||||
if ((p.fusion_flags & MAT_VEC_FUSION_FLAGS_BIAS0) != 0) {
|
||||
tmpsh[j][n][0] += FLOAT_TYPE(data_fuse0[j*p.batch_stride_d + d_offset + first_row + n]);
|
||||
|
||||
@@ -1863,10 +1863,12 @@ static webgpu_encoded_op ggml_webgpu_mul_mat_id(webgpu_context & ctx,
|
||||
// params for mul_mat_id_gather.wgsl
|
||||
std::vector<uint32_t> gather_params = {
|
||||
(uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)),
|
||||
(uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
|
||||
param_n_expert,
|
||||
param_n_expert_used,
|
||||
param_n_tokens,
|
||||
(uint32_t) (src2->nb[1] / ggml_type_size(src2->type)),
|
||||
(uint32_t) dst->ne[0],
|
||||
};
|
||||
|
||||
const size_t dst_offset = ggml_webgpu_tensor_offset(dst);
|
||||
@@ -1888,6 +1890,8 @@ static webgpu_encoded_op ggml_webgpu_mul_mat_id(webgpu_context & ctx,
|
||||
ggml_webgpu_tensor_binding_size(ctx, src2)),
|
||||
ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), ids_cache_align_offset, ids_cache_bind_size),
|
||||
ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(dst), ids_tiles_align_offset, ids_tiles_bind_size),
|
||||
ggml_webgpu_make_bind_group_entry(3, ggml_webgpu_tensor_buf(dst), ggml_webgpu_tensor_align_offset(ctx, dst),
|
||||
ggml_webgpu_tensor_binding_size(ctx, dst)),
|
||||
};
|
||||
|
||||
dispatches.push_back({
|
||||
|
||||
@@ -46,12 +46,24 @@ fn main(@builtin(workgroup_id) wg_id: vec3<u32>,
|
||||
let i2 = wg_linear / params.ne1;
|
||||
let i1 = wg_linear % params.ne1;
|
||||
|
||||
let i11 = u32(ids[params.offset_ids + i1 * params.nb20 + i2 * params.nb21]);
|
||||
let i11 = i32(ids[params.offset_ids + i1 * params.nb20 + i2 * params.nb21]);
|
||||
|
||||
let src0_row = params.offset_src0 + i1 * params.nb01 + i2 * params.nb02;
|
||||
let src1_row = params.offset_src1 + i11 * params.nb11;
|
||||
let dst_row = params.offset_dst + i1 * params.ne0 + i2 * (params.ne0 * params.ne1);
|
||||
|
||||
if (i11 == -1) { // skipped slot, add nothing
|
||||
#ifdef INPLACE
|
||||
// src0 already holds the result
|
||||
#else
|
||||
for (var i = thread_id;i < params.ne0; i += WG_SIZE) {
|
||||
dst[dst_row + i] = src0[src0_row + i];
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
let src1_row = params.offset_src1 + u32(i11) * params.nb11;
|
||||
|
||||
for (var i = thread_id;i < params.ne0; i += WG_SIZE) {
|
||||
#ifdef INPLACE
|
||||
src0[src0_row + i] = src0[src0_row + i] + src1[src1_row + i];
|
||||
|
||||
@@ -2,19 +2,24 @@ enable f16;
|
||||
|
||||
struct MulMatIdGatherParams {
|
||||
offset_ids: u32,
|
||||
offset_dst: u32,
|
||||
|
||||
n_expert: u32,
|
||||
n_expert_used: u32,
|
||||
n_tokens: u32,
|
||||
|
||||
stride_ids_1: u32,
|
||||
|
||||
m: u32,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> ids: array<i32>; // [n_expert_used, n_tokens]
|
||||
@group(0) @binding(1) var<storage, read_write> ids_slot_pos: array<u32>; // [n_expert_used * n_tokens]
|
||||
@group(0) @binding(2) var<storage, read_write> ids_tiles: array<u32>; // [n_tiles, (expert, first slot, n slots) * n_tiles]
|
||||
|
||||
@group(0) @binding(3) var<uniform> params: MulMatIdGatherParams;
|
||||
@group(0) @binding(3) var<storage, read_write> dst: array<f32>; // [m, n_expert_used, n_tokens]
|
||||
|
||||
@group(0) @binding(4) var<uniform> params: MulMatIdGatherParams;
|
||||
|
||||
// per expert: the slot count, replaced by the first slot of the expert in ids_slot_pos by the prefix sum, then advanced as its write cursor
|
||||
var<workgroup> ids_count: array<atomic<u32>, N_EXPERT>;
|
||||
@@ -36,8 +41,10 @@ fn main(@builtin(local_invocation_id) local_id: vec3<u32>) {
|
||||
for (var i = thread_id; i < ids_slots_num; i += WG_SIZE) {
|
||||
let row = i / params.n_expert_used;
|
||||
let col = i % params.n_expert_used;
|
||||
let expert = u32(ids[params.offset_ids + row * params.stride_ids_1 + col]);
|
||||
atomicAdd(&ids_count[expert], 1u);
|
||||
let expert = ids[params.offset_ids + row * params.stride_ids_1 + col];
|
||||
if (expert != -1) { // skipped slot, owned by no expert
|
||||
atomicAdd(&ids_count[u32(expert)], 1u);
|
||||
}
|
||||
}
|
||||
|
||||
workgroupBarrier();
|
||||
@@ -71,8 +78,23 @@ fn main(@builtin(local_invocation_id) local_id: vec3<u32>) {
|
||||
for (var i = thread_id; i < ids_slots_num; i += WG_SIZE) {
|
||||
let row = i / params.n_expert_used;
|
||||
let col = i % params.n_expert_used;
|
||||
let expert = u32(ids[params.offset_ids + row * params.stride_ids_1 + col]);
|
||||
let pos = atomicAdd(&ids_count[expert], 1u);
|
||||
ids_slot_pos[pos] = i;
|
||||
let expert = ids[params.offset_ids + row * params.stride_ids_1 + col];
|
||||
if (expert != -1) {
|
||||
let pos = atomicAdd(&ids_count[u32(expert)], 1u);
|
||||
ids_slot_pos[pos] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// the matmul writes only the rows that an expert owns, so zero the rows of the skipped slots
|
||||
for (var i: u32 = 0; i < ids_slots_num; i += 1) {
|
||||
let row = i / params.n_expert_used;
|
||||
let col = i % params.n_expert_used;
|
||||
if (ids[params.offset_ids + row * params.stride_ids_1 + col] != -1) {
|
||||
continue;
|
||||
}
|
||||
let dst_row = params.offset_dst + i * params.m;
|
||||
for (var r = thread_id; r < params.m; r += WG_SIZE) {
|
||||
dst[dst_row + r] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +70,18 @@ fn main(
|
||||
let own_expert = ids[params.offset_ids + slot];
|
||||
let row_base = (wg_linear % output_groups) * OUTPUTS_PER_WG;
|
||||
|
||||
let src0_batch_offset = params.offset_src0 + own_expert * params.stride_02;
|
||||
// a skipped slot (id -1) keeps the zero accumulator, so the reduction below writes zeros
|
||||
// no early return: the barriers below must stay in uniform control flow
|
||||
let skipped = i32(own_expert) == -1;
|
||||
|
||||
let src0_batch_offset = params.offset_src0 + select(own_expert, 0u, skipped) * params.stride_02;
|
||||
let src1_idx_base = params.offset_src1 + (slot % params.b_ne1) * params.stride_11;
|
||||
let dst_idx_base = params.offset_dst + slot * params.m + row_base;
|
||||
|
||||
let acc = accumulate_vec_dot(thread_id, row_base, src0_batch_offset, src1_idx_base);
|
||||
var acc: array<array<f32, OUTPUTS_PER_WG>, NUM_COLS>;
|
||||
if (!skipped) {
|
||||
acc = accumulate_vec_dot(thread_id, row_base, src0_batch_offset, src1_idx_base);
|
||||
}
|
||||
|
||||
#ifdef USE_SUBGROUP_REDUCTION
|
||||
for (var row = 0u; row < OUTPUTS_PER_WG; row++) {
|
||||
|
||||
@@ -375,7 +375,12 @@ static void ggml_zendnn_compute_forward_mul_mat_id(
|
||||
for (int id = 0; id < n_ids; ++id) {
|
||||
const int32_t i02 = *(const int32_t *)((const char *)ids->data + iid1*ids->nb[1] + id*ids->nb[0]);
|
||||
|
||||
GGML_ASSERT(i02 >= 0 && i02 < n_as);
|
||||
GGML_ASSERT(i02 == -1 || (i02 >= 0 && i02 < n_as));
|
||||
|
||||
if (i02 == -1) {
|
||||
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matrix_row_counts[i02] == 0) {
|
||||
n_active_experts++;
|
||||
|
||||
@@ -3348,6 +3348,36 @@ bool ggml_prec_set_src(
|
||||
return true;
|
||||
}
|
||||
|
||||
enum ggml_prec ggml_prec_get_acc(
|
||||
const struct ggml_tensor * a) {
|
||||
switch (a->op) {
|
||||
case GGML_OP_MUL_MAT:
|
||||
case GGML_OP_MUL_MAT_ID:
|
||||
return (enum ggml_prec) ggml_get_op_params_i32(a, 0);
|
||||
case GGML_OP_FLASH_ATTN_EXT:
|
||||
return (enum ggml_prec) ggml_get_op_params_i32(a, 3);
|
||||
default:
|
||||
return GGML_PREC_UNDEFINED;
|
||||
};
|
||||
}
|
||||
|
||||
enum ggml_prec ggml_prec_get_src(
|
||||
const struct ggml_tensor * a,
|
||||
int idx) {
|
||||
GGML_ASSERT(idx >= 0 && idx < GGML_MAX_SRC);
|
||||
|
||||
switch (a->op) {
|
||||
case GGML_OP_MUL_MAT:
|
||||
case GGML_OP_MUL_MAT_ID:
|
||||
if (idx != 1) {
|
||||
return GGML_PREC_UNDEFINED;
|
||||
}
|
||||
return (enum ggml_prec) ggml_get_op_params_i32(a, 2 + idx);
|
||||
default:
|
||||
return GGML_PREC_UNDEFINED;
|
||||
};
|
||||
}
|
||||
|
||||
// ggml_mul_mat
|
||||
|
||||
static inline bool ggml_can_mul_mat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) {
|
||||
@@ -3408,6 +3438,8 @@ void ggml_mul_mat_set_hint(
|
||||
in b, n_expert_used can be broadcasted to match the n_expert_used of ids
|
||||
|
||||
c ~= as[:,:,i] @ b[:,i%r,t], i = ids[e,t] for all e,t in ids
|
||||
|
||||
an id of -1 skips the slot: no matrix is selected, c[:,e,t] is set to zero
|
||||
*/
|
||||
struct ggml_tensor * ggml_mul_mat_id(
|
||||
struct ggml_context * ctx,
|
||||
|
||||
@@ -211,6 +211,7 @@ class Keys:
|
||||
VALUE_SCALE = "{arch}.attention.value_scale"
|
||||
COMPRESS_RATIOS = "{arch}.attention.compress_ratios"
|
||||
COMPRESS_ROPE_FREQ_BASE = "{arch}.attention.compress_rope_freq_base"
|
||||
KV_SOURCE_LAYERS = "{arch}.attention.kv_source_layers"
|
||||
TEMPERATURE_LENGTH = "{arch}.attention.temperature_length"
|
||||
KEY_LENGTH_MLA = "{arch}.attention.key_length_mla"
|
||||
VALUE_LENGTH_MLA = "{arch}.attention.value_length_mla"
|
||||
@@ -236,6 +237,10 @@ class Keys:
|
||||
TYPES = "{arch}.attention.indexer.types"
|
||||
KPOOL = "{arch}.attention.indexer.kpool" # GLM5-Next
|
||||
KPOOL_SELECT_TAIL = "{arch}.attention.indexer.kpool_select_tail" # GLM5-Next
|
||||
SOURCE_LAYERS = "{arch}.attention.indexer.source_layers"
|
||||
CANDIDATE_SOURCE_LAYER = "{arch}.attention.indexer.candidate_source_layer"
|
||||
CANDIDATE_TOP_K_BLOCKS = "{arch}.attention.indexer.candidate_top_k_blocks"
|
||||
CANDIDATE_BLOCK_SIZE = "{arch}.attention.indexer.candidate_block_size"
|
||||
|
||||
class HyperConnection:
|
||||
COUNT = "{arch}.hyper_connection.count"
|
||||
@@ -245,6 +250,12 @@ class Keys:
|
||||
MAGNITUDE = "{arch}.hyper_connection.magnitude"
|
||||
# absent means the mix projection is full rank (DeepSeek-V4 behaviour)
|
||||
LOW_RANK = "{arch}.hyper_connection.low_rank"
|
||||
# single-pass mHC: each sublayer collapses with the previous pre-mix (DeepSeek-V4.1)
|
||||
SINGLE_PASS = "{arch}.hyper_connection.single_pass"
|
||||
|
||||
class Engram: # deepseek41; the table uses the PerLayerEmbedding keys
|
||||
PAD_TOKEN_ID = "{arch}.engram.pad_token_id"
|
||||
TOKEN_MAP = "{arch}.engram.token_map"
|
||||
|
||||
class PerLayerEmbedding:
|
||||
LAYERS = "{arch}.ple.layers"
|
||||
@@ -636,6 +647,7 @@ class MODEL_ARCH(IntEnum):
|
||||
DEEPSEEK2OCR = auto()
|
||||
DEEPSEEK32 = auto()
|
||||
DEEPSEEK4 = auto()
|
||||
DEEPSEEK41 = auto()
|
||||
CHATGLM = auto()
|
||||
GLM4 = auto()
|
||||
GLM4_MOE = auto()
|
||||
@@ -1272,6 +1284,11 @@ class MODEL_TENSOR(IntEnum):
|
||||
A_GEN_WAV_SEANET_RES_CONV1 = auto()
|
||||
A_GEN_WAV_SEANET_RES_CONV2 = auto()
|
||||
A_GEN_WAV_SEANET_SCALE_CONV = auto() # strided upsample convtr
|
||||
# soprano: Vocos decoder, the ConvNeXt blocks reuse A_GEN_WAV_UP_*
|
||||
A_GEN_WAV_INPUT = auto()
|
||||
A_GEN_WAV_NORM = auto()
|
||||
A_GEN_WAV_OUTPUT_NORM = auto()
|
||||
A_GEN_WAV_OUTPUT = auto()
|
||||
A_MMPROJ = auto()
|
||||
A_MMPROJ_FC = auto()
|
||||
A_MM_NORM_PRE = auto()
|
||||
@@ -1312,6 +1329,8 @@ class MODEL_TENSOR(IntEnum):
|
||||
DSPARK_MARKOV_W1 = auto() # markov head: prev-token embed
|
||||
DSPARK_MARKOV_W2 = auto() # markov head: bias projection
|
||||
DSPARK_CONF_PROJ = auto() # confidence head
|
||||
ENGRAM_KV = auto()
|
||||
ENGRAM_GATE = auto()
|
||||
DFLASH_ATTN_CONV_BASE = auto()
|
||||
DFLASH_ATTN_CONV_PROJ = auto()
|
||||
DFLASH_FFN_CONV_BASE = auto()
|
||||
@@ -1455,6 +1474,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
|
||||
MODEL_ARCH.DEEPSEEK2OCR: "deepseek2-ocr",
|
||||
MODEL_ARCH.DEEPSEEK32: "deepseek32",
|
||||
MODEL_ARCH.DEEPSEEK4: "deepseek4",
|
||||
MODEL_ARCH.DEEPSEEK41: "deepseek41",
|
||||
MODEL_ARCH.CHATGLM: "chatglm",
|
||||
MODEL_ARCH.GLM4: "glm4",
|
||||
MODEL_ARCH.GLM4_MOE: "glm4moe",
|
||||
@@ -2084,6 +2104,10 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_RES_CONV1: "a.gen.wav.seanet.blk.{bid}.res_conv1",
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_RES_CONV2: "a.gen.wav.seanet.blk.{bid}.res_conv2",
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_SCALE_CONV: "a.gen.wav.seanet.blk.{bid}.scale_conv",
|
||||
MODEL_TENSOR.A_GEN_WAV_INPUT: "a.gen.wav.input",
|
||||
MODEL_TENSOR.A_GEN_WAV_NORM: "a.gen.wav.norm",
|
||||
MODEL_TENSOR.A_GEN_WAV_OUTPUT_NORM: "a.gen.wav.output_norm",
|
||||
MODEL_TENSOR.A_GEN_WAV_OUTPUT: "a.gen.wav.output",
|
||||
MODEL_TENSOR.A_MMPROJ: "mm.a.mlp.{bid}",
|
||||
MODEL_TENSOR.A_MMPROJ_FC: "mm.a.fc",
|
||||
MODEL_TENSOR.A_MM_NORM_PRE: "mm.a.norm_pre",
|
||||
@@ -2155,6 +2179,8 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W1: "markov_w1",
|
||||
MODEL_TENSOR.DSPARK_MARKOV_W2: "markov_w2",
|
||||
MODEL_TENSOR.DSPARK_CONF_PROJ: "conf_proj",
|
||||
MODEL_TENSOR.ENGRAM_KV: "blk.{bid}.engram_kv",
|
||||
MODEL_TENSOR.ENGRAM_GATE: "blk.{bid}.engram_gate",
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_BASE: "blk.{bid}.attn_conv_base",
|
||||
MODEL_TENSOR.DFLASH_ATTN_CONV_PROJ: "blk.{bid}.attn_conv_proj",
|
||||
MODEL_TENSOR.DFLASH_FFN_CONV_BASE: "blk.{bid}.ffn_conv_base",
|
||||
@@ -2462,6 +2488,10 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_RES_CONV1,
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_RES_CONV2,
|
||||
MODEL_TENSOR.A_GEN_WAV_SEANET_SCALE_CONV,
|
||||
MODEL_TENSOR.A_GEN_WAV_INPUT,
|
||||
MODEL_TENSOR.A_GEN_WAV_NORM,
|
||||
MODEL_TENSOR.A_GEN_WAV_OUTPUT_NORM,
|
||||
MODEL_TENSOR.A_GEN_WAV_OUTPUT,
|
||||
MODEL_TENSOR.A_ENC_CONV_NORM_MEAN,
|
||||
MODEL_TENSOR.A_ENC_CONV_NORM_VAR,
|
||||
MODEL_TENSOR.A_ENC_MEL_FILTERS,
|
||||
@@ -4345,6 +4375,46 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
|
||||
],
|
||||
MODEL_ARCH.DEEPSEEK41: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
MODEL_TENSOR.ATTN_NORM,
|
||||
MODEL_TENSOR.ATTN_SINKS,
|
||||
MODEL_TENSOR.ATTN_Q_A,
|
||||
MODEL_TENSOR.ATTN_Q_B,
|
||||
MODEL_TENSOR.ATTN_Q_A_NORM,
|
||||
MODEL_TENSOR.ATTN_KV,
|
||||
MODEL_TENSOR.ATTN_KV_NORM,
|
||||
MODEL_TENSOR.ATTN_OUT_A,
|
||||
MODEL_TENSOR.ATTN_OUT_B,
|
||||
MODEL_TENSOR.HC_ATTN_FN,
|
||||
MODEL_TENSOR.HC_ATTN_BASE,
|
||||
MODEL_TENSOR.HC_ATTN_SCALE,
|
||||
MODEL_TENSOR.HC_FFN_FN,
|
||||
MODEL_TENSOR.HC_FFN_BASE,
|
||||
MODEL_TENSOR.HC_FFN_SCALE,
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_WKV,
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_WGATE,
|
||||
MODEL_TENSOR.ATTN_COMPRESSOR_NORM,
|
||||
MODEL_TENSOR.INDEXER_ATTN_K,
|
||||
MODEL_TENSOR.INDEXER_K_NORM,
|
||||
MODEL_TENSOR.INDEXER_ATTN_Q_B,
|
||||
MODEL_TENSOR.INDEXER_PROJ,
|
||||
MODEL_TENSOR.PER_LAYER_TOKEN_EMBD,
|
||||
MODEL_TENSOR.ENGRAM_KV,
|
||||
MODEL_TENSOR.ENGRAM_GATE,
|
||||
MODEL_TENSOR.FFN_GATE_INP,
|
||||
MODEL_TENSOR.FFN_EXP_PROBS_B,
|
||||
MODEL_TENSOR.FFN_EXP_PROBS_B_VL,
|
||||
MODEL_TENSOR.FFN_NORM,
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
MODEL_TENSOR.FFN_GATE_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_SHEXP,
|
||||
MODEL_TENSOR.FFN_UP_SHEXP,
|
||||
],
|
||||
MODEL_ARCH.ERNIE4_5_MOE: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
@@ -6436,6 +6506,7 @@ class VisionProjectorType:
|
||||
DEEPSEEKOCR = "deepseekocr"
|
||||
DEEPSEEKOCR2 = "deepseekocr2"
|
||||
DEEPSEEK4V = "deepseek4v"
|
||||
DEEPSEEK41V = "deepseek41v"
|
||||
LFM2A = "lfm2a" # audio
|
||||
MUSIC_FLAMINGO = "musicflamingo" # audio
|
||||
GLM4V = "glm4v"
|
||||
@@ -6447,6 +6518,7 @@ class VisionProjectorType:
|
||||
QWEN3TTS_GEN = "qwen3tts_gen" # audio generation: code_predictor
|
||||
POCKETTTS_SPKENC = "pockettts_spkenc" # audio: mimi encoder as voice-prompt encoder
|
||||
POCKETTTS_GEN = "pockettts_gen" # audio generation: flow-matching decoder + mimi decoder
|
||||
SOPRANO = "soprano" # audio generation: Vocos decoder
|
||||
HUNYUANVL = "hunyuanvl"
|
||||
PARAKEET = "parakeet" # audio
|
||||
MINIMAXM3 = "minimax_m3"
|
||||
|
||||
@@ -1077,6 +1077,28 @@ class GGUFWriter:
|
||||
def add_attention_compress_rope_freq_base(self, value: float) -> None:
|
||||
self.add_float32(Keys.Attention.COMPRESS_ROPE_FREQ_BASE.format(arch=self.arch), value)
|
||||
|
||||
# deepseek41
|
||||
def add_attention_kv_source_layers(self, layers: Sequence[int]) -> None:
|
||||
self.add_key_value(Keys.Attention.KV_SOURCE_LAYERS.format(arch=self.arch), list(layers), GGUFValueType.ARRAY, GGUFValueType.UINT32)
|
||||
|
||||
def add_indexer_source_layers(self, layers: Sequence[int]) -> None:
|
||||
self.add_key_value(Keys.Attention.Indexer.SOURCE_LAYERS.format(arch=self.arch), list(layers), GGUFValueType.ARRAY, GGUFValueType.UINT32)
|
||||
|
||||
def add_indexer_candidate_source_layer(self, layer: int) -> None:
|
||||
self.add_int32(Keys.Attention.Indexer.CANDIDATE_SOURCE_LAYER.format(arch=self.arch), layer)
|
||||
|
||||
def add_indexer_candidate_top_k_blocks(self, value: int) -> None:
|
||||
self.add_uint32(Keys.Attention.Indexer.CANDIDATE_TOP_K_BLOCKS.format(arch=self.arch), value)
|
||||
|
||||
def add_indexer_candidate_block_size(self, value: int) -> None:
|
||||
self.add_uint32(Keys.Attention.Indexer.CANDIDATE_BLOCK_SIZE.format(arch=self.arch), value)
|
||||
|
||||
def add_engram_pad_token_id(self, value: int) -> None:
|
||||
self.add_uint32(Keys.Engram.PAD_TOKEN_ID.format(arch=self.arch), value)
|
||||
|
||||
def add_engram_token_map(self, values: Sequence[int]) -> None:
|
||||
self.add_key_value(Keys.Engram.TOKEN_MAP.format(arch=self.arch), list(values), GGUFValueType.ARRAY, GGUFValueType.INT32)
|
||||
|
||||
def add_hyper_connection_count(self, count: int) -> None:
|
||||
self.add_uint32(Keys.HyperConnection.COUNT.format(arch=self.arch), count)
|
||||
|
||||
@@ -1092,6 +1114,9 @@ class GGUFWriter:
|
||||
def add_hyper_connection_low_rank(self, value: int) -> None:
|
||||
self.add_uint32(Keys.HyperConnection.LOW_RANK.format(arch=self.arch), value)
|
||||
|
||||
def add_hyper_connection_single_pass(self, value: bool) -> None:
|
||||
self.add_bool(Keys.HyperConnection.SINGLE_PASS.format(arch=self.arch), value)
|
||||
|
||||
def add_ple_layers(self, values: Sequence[int]) -> None:
|
||||
self.add_array(Keys.PerLayerEmbedding.LAYERS.format(arch=self.arch), values)
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
|
||||
{ LLM_ARCH_DEEPSEEK2OCR, "deepseek2-ocr" },
|
||||
{ LLM_ARCH_DEEPSEEK32, "deepseek32" },
|
||||
{ LLM_ARCH_DEEPSEEK4, "deepseek4" },
|
||||
{ LLM_ARCH_DEEPSEEK41, "deepseek41" },
|
||||
{ LLM_ARCH_CHATGLM, "chatglm" },
|
||||
{ LLM_ARCH_GLM4, "glm4" },
|
||||
{ LLM_ARCH_GLM4_MOE, "glm4moe" },
|
||||
@@ -311,6 +312,11 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_ATTENTION_INDEXER_TYPES, "%s.attention.indexer.types" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_KPOOL, "%s.attention.indexer.kpool" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_KPOOL_SELECT_TAIL, "%s.attention.indexer.kpool_select_tail" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_SOURCE_LAYERS, "%s.attention.indexer.source_layers" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_CANDIDATE_SOURCE_LAYER, "%s.attention.indexer.candidate_source_layer" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_CANDIDATE_TOP_K_BLOCKS, "%s.attention.indexer.candidate_top_k_blocks" },
|
||||
{ LLM_KV_ATTENTION_INDEXER_CANDIDATE_BLOCK_SIZE, "%s.attention.indexer.candidate_block_size" },
|
||||
{ LLM_KV_ATTENTION_KV_SOURCE_LAYERS, "%s.attention.kv_source_layers" },
|
||||
{ LLM_KV_ATTENTION_OUTPUT_GROUP_COUNT, "%s.attention.output_group_count" },
|
||||
{ LLM_KV_ATTENTION_OUTPUT_LORA_RANK, "%s.attention.output_lora_rank" },
|
||||
{ LLM_KV_ATTENTION_COMPRESS_ROPE_FREQ_BASE, "%s.attention.compress_rope_freq_base" },
|
||||
@@ -323,6 +329,10 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_HYPER_CONNECTION_EPSILON, "%s.hyper_connection.epsilon" },
|
||||
{ LLM_KV_HYPER_CONNECTION_MAGNITUDE, "%s.hyper_connection.magnitude" },
|
||||
{ LLM_KV_HYPER_CONNECTION_LOW_RANK, "%s.hyper_connection.low_rank" },
|
||||
{ LLM_KV_HYPER_CONNECTION_SINGLE_PASS, "%s.hyper_connection.single_pass" },
|
||||
|
||||
{ LLM_KV_ENGRAM_PAD_TOKEN_ID, "%s.engram.pad_token_id" },
|
||||
{ LLM_KV_ENGRAM_TOKEN_MAP, "%s.engram.token_map" },
|
||||
|
||||
{ LLM_KV_PLE_LAYERS, "%s.ple.layers" },
|
||||
{ LLM_KV_PLE_NGRAM_SIZE, "%s.ple.ngram_size" },
|
||||
@@ -782,6 +792,8 @@ static const std::map<llm_tensor, const char *> LLM_TENSOR_NAMES = {
|
||||
{ LLM_TENSOR_DSPARK_MARKOV_W1, "markov_w1" },
|
||||
{ LLM_TENSOR_DSPARK_MARKOV_W2, "markov_w2" },
|
||||
{ LLM_TENSOR_DSPARK_CONF_PROJ, "conf_proj" },
|
||||
{ LLM_TENSOR_ENGRAM_KV, "blk.%d.engram_kv" },
|
||||
{ LLM_TENSOR_ENGRAM_GATE, "blk.%d.engram_gate" },
|
||||
{ LLM_TENSOR_DFLASH_ATTN_CONV_BASE, "blk.%d.attn_conv_base" },
|
||||
{ LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, "blk.%d.attn_conv_proj" },
|
||||
{ LLM_TENSOR_DFLASH_FFN_CONV_BASE, "blk.%d.ffn_conv_base" },
|
||||
@@ -1120,6 +1132,8 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {
|
||||
{LLM_TENSOR_DSPARK_MARKOV_W1, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}},
|
||||
{LLM_TENSOR_DSPARK_MARKOV_W2, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DSPARK_CONF_PROJ, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_ENGRAM_KV, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_ENGRAM_GATE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_DFLASH_ATTN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
{LLM_TENSOR_DFLASH_ATTN_CONV_PROJ, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}},
|
||||
{LLM_TENSOR_DFLASH_FFN_CONV_BASE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
|
||||
@@ -1239,6 +1253,7 @@ bool llm_arch_is_hybrid(const llm_arch & arch) {
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
case LLM_ARCH_DEEPSEEK4:
|
||||
case LLM_ARCH_DEEPSEEK41:
|
||||
case LLM_ARCH_MINIMAX_01:
|
||||
return true;
|
||||
default:
|
||||
@@ -1265,6 +1280,7 @@ bool llm_arch_supports_rs_rollback(const llm_arch & arch) {
|
||||
case LLM_ARCH_QWEN35MOE:
|
||||
case LLM_ARCH_QWEN4EXP:
|
||||
case LLM_ARCH_DEEPSEEK4:
|
||||
case LLM_ARCH_DEEPSEEK41:
|
||||
case LLM_ARCH_GLM5_NEXT:
|
||||
case LLM_ARCH_NEMOTRON_H:
|
||||
case LLM_ARCH_NEMOTRON_H_MOE:
|
||||
@@ -1282,6 +1298,7 @@ bool llm_arch_supports_mixed_batch(const llm_arch & arch) {
|
||||
switch (arch) {
|
||||
case LLM_ARCH_COGVLM:
|
||||
case LLM_ARCH_DEEPSEEK4:
|
||||
case LLM_ARCH_DEEPSEEK41:
|
||||
case LLM_ARCH_GRANITE_SWITCH:
|
||||
case LLM_ARCH_EAGLE3:
|
||||
case LLM_ARCH_DFLASH:
|
||||
@@ -1319,6 +1336,8 @@ bool llm_arch_supports_sm_tensor(const llm_arch & arch) {
|
||||
case LLM_ARCH_MINIMAX_M2:
|
||||
case LLM_ARCH_MINIMAX_M3:
|
||||
case LLM_ARCH_MISTRAL4:
|
||||
case LLM_ARCH_MELLUM: // [TAG_MOE_AMAX_SCALE]
|
||||
case LLM_ARCH_FLINT: // [TAG_MOE_AMAX_SCALE]
|
||||
case LLM_ARCH_KIMI_LINEAR:
|
||||
case LLM_ARCH_BAILINGMOE3:
|
||||
case LLM_ARCH_KIMI_K3:
|
||||
|
||||
@@ -92,6 +92,7 @@ enum llm_arch {
|
||||
LLM_ARCH_DEEPSEEK2OCR,
|
||||
LLM_ARCH_DEEPSEEK32,
|
||||
LLM_ARCH_DEEPSEEK4,
|
||||
LLM_ARCH_DEEPSEEK41,
|
||||
LLM_ARCH_CHATGLM,
|
||||
LLM_ARCH_GLM4,
|
||||
LLM_ARCH_GLM4_MOE,
|
||||
@@ -316,6 +317,11 @@ enum llm_kv {
|
||||
LLM_KV_ATTENTION_INDEXER_TYPES,
|
||||
LLM_KV_ATTENTION_INDEXER_KPOOL,
|
||||
LLM_KV_ATTENTION_INDEXER_KPOOL_SELECT_TAIL,
|
||||
LLM_KV_ATTENTION_INDEXER_SOURCE_LAYERS,
|
||||
LLM_KV_ATTENTION_INDEXER_CANDIDATE_SOURCE_LAYER,
|
||||
LLM_KV_ATTENTION_INDEXER_CANDIDATE_TOP_K_BLOCKS,
|
||||
LLM_KV_ATTENTION_INDEXER_CANDIDATE_BLOCK_SIZE,
|
||||
LLM_KV_ATTENTION_KV_SOURCE_LAYERS,
|
||||
LLM_KV_ATTENTION_OUTPUT_GROUP_COUNT,
|
||||
LLM_KV_ATTENTION_OUTPUT_LORA_RANK,
|
||||
LLM_KV_ATTENTION_COMPRESS_ROPE_FREQ_BASE,
|
||||
@@ -328,6 +334,10 @@ enum llm_kv {
|
||||
LLM_KV_HYPER_CONNECTION_EPSILON,
|
||||
LLM_KV_HYPER_CONNECTION_MAGNITUDE,
|
||||
LLM_KV_HYPER_CONNECTION_LOW_RANK,
|
||||
LLM_KV_HYPER_CONNECTION_SINGLE_PASS,
|
||||
|
||||
LLM_KV_ENGRAM_PAD_TOKEN_ID,
|
||||
LLM_KV_ENGRAM_TOKEN_MAP,
|
||||
|
||||
LLM_KV_PLE_LAYERS,
|
||||
LLM_KV_PLE_NGRAM_SIZE,
|
||||
@@ -788,6 +798,8 @@ enum llm_tensor {
|
||||
LLM_TENSOR_DSPARK_MARKOV_W1,
|
||||
LLM_TENSOR_DSPARK_MARKOV_W2,
|
||||
LLM_TENSOR_DSPARK_CONF_PROJ,
|
||||
LLM_TENSOR_ENGRAM_KV,
|
||||
LLM_TENSOR_ENGRAM_GATE,
|
||||
LLM_TENSOR_DFLASH_ATTN_CONV_BASE,
|
||||
LLM_TENSOR_DFLASH_ATTN_CONV_PROJ,
|
||||
LLM_TENSOR_DFLASH_FFN_CONV_BASE,
|
||||
|
||||
@@ -1339,8 +1339,9 @@ void llama_context::set_abort_callback(bool (*abort_callback)(void * data), void
|
||||
void llama_context::set_embeddings(bool value) {
|
||||
LLAMA_LOG_DEBUG("%s: value = %d\n", __func__, value);
|
||||
|
||||
// a pooled context computes the logits only without embeddings, so the toggle changes the graph shape
|
||||
if (cparams.embeddings != value && cparams.pooling_type != LLAMA_POOLING_TYPE_NONE) {
|
||||
// the toggle changes the graph shape (pooled: logits only; pooling NONE: the reserve graph outputs all tokens,
|
||||
// which crashed ggml_gallocr on the first /completion), so re-reserve on every toggle
|
||||
if (cparams.embeddings != value) {
|
||||
sched_need_reserve = true;
|
||||
}
|
||||
|
||||
@@ -2536,6 +2537,7 @@ uint32_t llama_context::graph_max_nodes(uint32_t n_tokens) const {
|
||||
model.arch == LLM_ARCH_CLEF ||
|
||||
model.arch == LLM_ARCH_QWEN4EXP ||
|
||||
model.arch == LLM_ARCH_DEEPSEEK4 ||
|
||||
model.arch == LLM_ARCH_DEEPSEEK41 ||
|
||||
(model.arch == LLM_ARCH_DFLASH && model.hparams.dsv4_hc_mult > 0) ||
|
||||
model.arch == LLM_ARCH_NANBEIGE ||
|
||||
model.arch == LLM_ARCH_MINIMAX_01 ||
|
||||
@@ -4071,7 +4073,7 @@ llama_context * llama_init_from_model(
|
||||
}
|
||||
}
|
||||
|
||||
if ((model->hparams.is_mla() || model->arch == LLM_ARCH_DEEPSEEK4) && params.type_k != params.type_v) {
|
||||
if ((model->hparams.is_mla() || model->arch == LLM_ARCH_DEEPSEEK4 || model->arch == LLM_ARCH_DEEPSEEK41) && params.type_k != params.type_v) {
|
||||
LLAMA_LOG_ERROR("%s: model does not support different K (%s) and V (%s) cache types\n", __func__, ggml_type_name(params.type_k), ggml_type_name(params.type_v));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+10
-4
@@ -2010,7 +2010,7 @@ ggml_tensor * llm_graph_context::build_ffn(
|
||||
const float limit = hparams.swiglu_clamp_shexp[il];
|
||||
constexpr float eps = 1e-6f;
|
||||
if (limit > eps) {
|
||||
if (arch == LLM_ARCH_DEEPSEEK4 || arch == LLM_ARCH_GLM5_NEXT || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0)) {
|
||||
if (arch == LLM_ARCH_DEEPSEEK4 || arch == LLM_ARCH_DEEPSEEK41 || arch == LLM_ARCH_GLM5_NEXT || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0)) {
|
||||
cur = ggml_swiglu_clamp(ctx0, cur, tmp, limit);
|
||||
} else {
|
||||
tmp = ggml_clamp(ctx0, tmp, -limit, limit);
|
||||
@@ -2340,8 +2340,13 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
ggml_tensor * weights_sum = ggml_sum_rows(ctx0, weights); // [1, n_tokens]
|
||||
cb(weights_sum, "ffn_moe_weights_sum", il);
|
||||
|
||||
// Avoid division by zero, clamp to smallest number representable by F16
|
||||
weights_sum = ggml_clamp(ctx0, weights_sum, 6.103515625e-5, INFINITY);
|
||||
if (arch == LLM_ARCH_DEEPSEEK41) {
|
||||
// model.py Gate.forward: weights / (sum + 1e-20); the sum can fall below the F16 clamp below
|
||||
weights_sum = ggml_scale_bias(ctx0, weights_sum, 1.0f, 1e-20f);
|
||||
} else {
|
||||
// Avoid division by zero, clamp to smallest number representable by F16
|
||||
weights_sum = ggml_clamp(ctx0, weights_sum, 6.103515625e-5, INFINITY);
|
||||
}
|
||||
cb(weights_sum, "ffn_moe_weights_sum_clamped", il);
|
||||
|
||||
weights = ggml_div(ctx0, weights, weights_sum); // [n_expert_used, n_tokens]
|
||||
@@ -2431,7 +2436,7 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
const float limit = hparams.swiglu_clamp_exp[il];
|
||||
constexpr float eps = 1e-6f;
|
||||
if (limit > eps) {
|
||||
if (arch == LLM_ARCH_MAPLE || arch == LLM_ARCH_DEEPSEEK4 || arch == LLM_ARCH_GLM5_NEXT || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0) || arch == LLM_ARCH_HY_V4) {
|
||||
if (arch == LLM_ARCH_MAPLE || arch == LLM_ARCH_DEEPSEEK4 || arch == LLM_ARCH_DEEPSEEK41 || arch == LLM_ARCH_GLM5_NEXT || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0) || arch == LLM_ARCH_HY_V4) {
|
||||
cur = ggml_swiglu_clamp(ctx0, cur, up, limit);
|
||||
} else {
|
||||
up = ggml_clamp(ctx0, up, -limit, limit);
|
||||
@@ -2510,6 +2515,7 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
{
|
||||
// TODO: this graph-level rescaling should only be applied when the corresponding backend does not support
|
||||
// ggml_prec_set_src(experts, GGML_PREC_BF16, 1);
|
||||
// the amax op cannot be split across devices, so these architectures are denied -sm tensor [TAG_MOE_AMAX_SCALE]
|
||||
// ref: https://github.com/ggml-org/llama.cpp/pull/30656#issuecomment-6269597922
|
||||
llm_graph_amax_src1 amax_src1 = { nullptr, nullptr };
|
||||
|
||||
|
||||
+14
-2
@@ -10,8 +10,8 @@
|
||||
// bump if necessary
|
||||
#define LLAMA_MAX_LAYERS 512
|
||||
#define LLAMA_MAX_EXPERTS 1024 // Kimi K3
|
||||
#define LLAMA_MAX_PLE_NGRAM 8 // qwen4exp
|
||||
#define LLAMA_MAX_PLE_HEADS 64 // qwen4exp
|
||||
#define LLAMA_MAX_PLE_NGRAM 8 // qwen4exp, deepseek41 (all hash modules)
|
||||
#define LLAMA_MAX_PLE_HEADS 64 // qwen4exp, deepseek41 (all hash modules)
|
||||
|
||||
enum llama_expert_gating_func_type {
|
||||
LLAMA_EXPERT_GATING_FUNC_TYPE_NONE = 0,
|
||||
@@ -326,12 +326,20 @@ struct llama_hparams {
|
||||
uint32_t dsv4_o_group_count = 0;
|
||||
uint32_t dsv4_o_lora_rank = 0;
|
||||
uint32_t dsv4_hc_mult = 0;
|
||||
bool dsv4_hc_single_pass = false; // DeepSeek-V4.1 DSpark draft: lagged pre-mix, no hc_head
|
||||
uint32_t dsv4_hc_sinkhorn_iters = 0;
|
||||
uint32_t dsv4_hash_layer_count = 0;
|
||||
float dsv4_compress_rope_base = 0.0f;
|
||||
float dsv4_hc_eps = 0.0f;
|
||||
std::array<uint32_t, LLAMA_MAX_LAYERS> dsv4_compress_ratios;
|
||||
|
||||
// deepseek41
|
||||
std::array<int32_t, LLAMA_MAX_LAYERS> dsv41_kv_src; // layer whose compressed KV this layer reads, -1 = none
|
||||
std::array<int32_t, LLAMA_MAX_LAYERS> dsv41_topk_src; // layer whose top-k this layer uses, -1 = none
|
||||
int32_t dsv41_cand_src = -1; // -1 = no candidate block selection
|
||||
uint32_t dsv41_cand_topk_blocks = 0;
|
||||
uint32_t dsv41_cand_block_size = 0;
|
||||
|
||||
// 0 = full rank (DeepSeek-V4)
|
||||
uint32_t hc_low_rank = 0;
|
||||
|
||||
@@ -346,6 +354,10 @@ struct llama_hparams {
|
||||
uint32_t ple_eos_token_id = 0;
|
||||
// the id the PLE hash stands in at image positions; 0 makes the loader fall back to EOS
|
||||
uint32_t ple_image_token_id = 0;
|
||||
// the id the KV cells record at embedding (image) positions for the n-gram history; LLAMA_TOKEN_NULL = none
|
||||
llama_token ple_embd_token = LLAMA_TOKEN_NULL;
|
||||
// hash constant sets, stored back to back: 1 shared by all PLE layers (qwen4exp), or 1 per PLE layer in layer order (deepseek41)
|
||||
uint32_t ple_n_modules = 1;
|
||||
// the file lists PLE layer indices, so this is never a per-layer gguf array and can hold one bit per layer
|
||||
std::bitset<LLAMA_MAX_LAYERS> is_ple_impl;
|
||||
// the hash multipliers reach ~2e13 and have to stay 64-bit
|
||||
|
||||
+237
-42
@@ -333,12 +333,24 @@ static void dsv4_state_read_tensor_streams(
|
||||
}
|
||||
}
|
||||
|
||||
// serialized compressed-KV layers: all except per-device mirrors (kv_src[il] != il), refilled from their source on read
|
||||
static std::vector<uint32_t> dsv4_k_cache_state_layers(const llama_kv_cache * kv, const std::vector<int32_t> & kv_src) {
|
||||
std::vector<uint32_t> res;
|
||||
for (uint32_t il : kv->get_layer_ids()) {
|
||||
if (kv_src[il] == (int32_t) il) {
|
||||
res.push_back(il);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void dsv4_state_write_k_cache(
|
||||
llama_io_write_i & io,
|
||||
const llama_kv_cache * kv,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags,
|
||||
uint32_t n_rows) {
|
||||
uint32_t n_rows,
|
||||
const std::vector<int32_t> & kv_src) {
|
||||
GGML_UNUSED(flags);
|
||||
|
||||
uint32_t s0;
|
||||
@@ -347,7 +359,8 @@ static void dsv4_state_write_k_cache(
|
||||
|
||||
const uint32_t version = DSV4_K_CACHE_STATE_VER;
|
||||
const uint32_t kv_size = kv->get_size();
|
||||
const auto layer_ids = kv->get_layer_ids();
|
||||
// mirrors are never serialized, so the state format does not depend on the split
|
||||
const auto layer_ids = dsv4_k_cache_state_layers(kv, kv_src);
|
||||
const uint32_t n_layer = layer_ids.size();
|
||||
|
||||
if (n_rows > kv_size) {
|
||||
@@ -369,7 +382,8 @@ static void dsv4_state_read_k_cache(
|
||||
llama_io_read_i & io,
|
||||
llama_kv_cache * kv,
|
||||
llama_seq_id seq_id,
|
||||
llama_state_seq_flags flags) {
|
||||
llama_state_seq_flags flags,
|
||||
const std::vector<int32_t> & kv_src) {
|
||||
GGML_UNUSED(flags);
|
||||
|
||||
uint32_t version;
|
||||
@@ -399,7 +413,7 @@ static void dsv4_state_read_k_cache(
|
||||
uint32_t s0;
|
||||
dsv4_state_dst_stream_range(kv->get_n_stream(), seq_id, ns, s0);
|
||||
|
||||
const auto layer_ids = kv->get_layer_ids();
|
||||
const auto layer_ids = dsv4_k_cache_state_layers(kv, kv_src);
|
||||
if (n_layer_ref != layer_ids.size()) {
|
||||
throw std::runtime_error("DSV4 K-cache layer count mismatch");
|
||||
}
|
||||
@@ -413,6 +427,31 @@ static void dsv4_state_read_k_cache(
|
||||
|
||||
dsv4_state_read_tensor_streams(io, kv->get_k_storage(il), kv_size, n_rows_ref, s0, ns);
|
||||
}
|
||||
|
||||
// refill the mirrors from their source rows just read
|
||||
std::vector<uint8_t> buf;
|
||||
for (uint32_t il : kv->get_layer_ids()) {
|
||||
const int32_t src = kv_src[il];
|
||||
if (src == (int32_t) il) {
|
||||
continue;
|
||||
}
|
||||
ggml_tensor * t_src = kv->get_k_storage(src);
|
||||
ggml_tensor * t_dst = kv->get_k_storage(il);
|
||||
GGML_ASSERT(t_src->type == t_dst->type && t_src->ne[0] == t_dst->ne[0]);
|
||||
|
||||
const size_t row_size = ggml_row_size(t_src->type, t_src->ne[0]);
|
||||
const size_t stream_stride = (size_t) kv_size*row_size;
|
||||
const size_t size = (size_t) n_rows_ref*row_size;
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
buf.resize(size);
|
||||
for (uint32_t s = 0; s < ns; ++s) {
|
||||
const size_t offset = (size_t) (s0 + s)*stream_stride;
|
||||
ggml_backend_tensor_get(t_src, buf.data(), offset, size);
|
||||
ggml_backend_tensor_set(t_dst, buf.data(), offset, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string dsv4_plan_positions(const std::vector<int32_t> & values) {
|
||||
@@ -432,6 +471,7 @@ static llama_kv_cache_dsv4_context::comp_plan dsv4_build_comp_plan(
|
||||
const llama_ubatch & ubatch,
|
||||
uint32_t ratio,
|
||||
bool overlap,
|
||||
bool pad_blocks,
|
||||
uint32_t state_size,
|
||||
uint32_t kv_size,
|
||||
uint32_t n_stream,
|
||||
@@ -548,7 +588,7 @@ static llama_kv_cache_dsv4_context::comp_plan dsv4_build_comp_plan(
|
||||
}
|
||||
}
|
||||
|
||||
if (ratio == DSV4_CSA_RATIO && !plan.state_pos.empty()) {
|
||||
if (pad_blocks && !plan.state_pos.empty()) {
|
||||
assert(kv_size > 0);
|
||||
|
||||
// Pad each stream to the reserve plan's block count.
|
||||
@@ -603,7 +643,7 @@ static llama_kv_cache_dsv4_context::comp_plan dsv4_build_comp_plan(
|
||||
}
|
||||
}
|
||||
|
||||
if (ratio == DSV4_HCA_RATIO && !plan.state_pos.empty() && plan.state_write_idxs.empty()) {
|
||||
if (!pad_blocks && !plan.state_pos.empty() && plan.state_write_idxs.empty()) {
|
||||
assert(kv_size > 0);
|
||||
// the last slot must not be live, or the dummy write would corrupt it;
|
||||
// a full stream implies a completed block, which implies real writes
|
||||
@@ -744,6 +784,7 @@ static std::vector<llama_kv_cache_dsv4_context::comp_plan> dsv4_build_comp_plans
|
||||
const std::vector<llama_ubatch> & ubatches,
|
||||
uint32_t ratio,
|
||||
bool overlap,
|
||||
bool pad_blocks,
|
||||
uint32_t state_size,
|
||||
uint32_t kv_size,
|
||||
uint32_t n_stream,
|
||||
@@ -755,7 +796,7 @@ static std::vector<llama_kv_cache_dsv4_context::comp_plan> dsv4_build_comp_plans
|
||||
// the first ubatch touching a seq consumes its rollback restore
|
||||
std::vector<uint32_t> rs(rs_idx);
|
||||
for (const llama_ubatch & ubatch : ubatches) {
|
||||
plans.push_back(dsv4_build_comp_plan(ubatch, ratio, overlap, state_size, kv_size, n_stream, n_rs_seq, rs));
|
||||
plans.push_back(dsv4_build_comp_plan(ubatch, ratio, overlap, pad_blocks, state_size, kv_size, n_stream, n_rs_seq, rs));
|
||||
|
||||
for (uint32_t s = 0; s < ubatch.n_seqs_unq; ++s) {
|
||||
const llama_seq_id seq_id = ubatch.seq_id_unq[s];
|
||||
@@ -1056,6 +1097,10 @@ uint32_t llama_dsv4_comp_state::get_ratio() const {
|
||||
return ratio;
|
||||
}
|
||||
|
||||
bool llama_dsv4_comp_state::is_overlap() const {
|
||||
return state_size == 2*ratio;
|
||||
}
|
||||
|
||||
uint32_t llama_dsv4_comp_state::get_state_size() const {
|
||||
return state_size;
|
||||
}
|
||||
@@ -1261,6 +1306,23 @@ llama_kv_cache_dsv4::llama_kv_cache_dsv4(
|
||||
v_trans, offload, swa_full, unified_raw, kv_size, n_seq_max, n_ubatch, n_pad,
|
||||
nullptr, filter_raw, reuse, nullptr);
|
||||
|
||||
// V4.1: CSA slot = ratio 2, HCA slot = ratio 1, no overlap and no indexer compressor; the index K is stored
|
||||
// next to the compressed KV ([kv | index K]) and only the KV source layers own rows
|
||||
const bool is_v41 = model.arch == LLM_ARCH_DEEPSEEK41;
|
||||
const uint32_t ratio_csa = is_v41 ? 2u : DSV4_CSA_RATIO;
|
||||
const uint32_t ratio_hca = is_v41 ? 1u : DSV4_HCA_RATIO;
|
||||
|
||||
if (is_v41) {
|
||||
const uint32_t n_embd_comp = model.hparams.n_embd_head_k() + model.hparams.indexer_head_size;
|
||||
for (llama_hparams * hp : { &hparams_csa, &hparams_hca }) {
|
||||
std::fill(hp->n_head_kv_arr.begin(), hp->n_head_kv_arr.end(), 1);
|
||||
hp->n_embd_head_k_full = n_embd_comp;
|
||||
hp->n_embd_head_v_full = n_embd_comp;
|
||||
hp->n_embd_head_k_swa = n_embd_comp;
|
||||
hp->n_embd_head_v_swa = n_embd_comp;
|
||||
}
|
||||
}
|
||||
|
||||
dsv4_make_k_only(hparams_csa);
|
||||
dsv4_make_k_only(hparams_hca);
|
||||
|
||||
@@ -1272,12 +1334,50 @@ llama_kv_cache_dsv4::llama_kv_cache_dsv4(
|
||||
hparams_lid.rope_type = LLAMA_ROPE_TYPE_NEOX;
|
||||
dsv4_make_k_only(hparams_lid);
|
||||
|
||||
// V4.1 cross-layer KV sharing: a compressed layer reads the storage of its kv source or, when the source is on
|
||||
// another device (layer split), of a per-device mirror fed with the source's new rows, so the whole cache never
|
||||
// crosses devices. no mirrors without offload
|
||||
const uint32_t n_layer = model.hparams.n_layer();
|
||||
comp_read.assign(n_layer, -1);
|
||||
comp_kv_src.resize(n_layer);
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
const int32_t src = is_v41 ? model.hparams.dsv41_kv_src[il] : (int32_t) il;
|
||||
comp_kv_src[il] = src >= 0 ? src : (int32_t) il;
|
||||
if (model.hparams.dsv4_compress_ratios[il] == 0) {
|
||||
continue;
|
||||
}
|
||||
comp_read[il] = src;
|
||||
if (src == (int32_t) il || !offload || model.dev_layer(src) == model.dev_layer(il)) {
|
||||
continue;
|
||||
}
|
||||
// the first layer of this source on this device holds the mirror
|
||||
comp_read[il] = il;
|
||||
for (int32_t j = src + 1; j < (int32_t) il; ++j) {
|
||||
if (comp_read[j] == j && comp_kv_src[j] == src && model.dev_layer(j) == model.dev_layer(il)) {
|
||||
comp_read[il] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compressed-KV storage: kv sources and mirrors; compressor state: kv sources only
|
||||
const auto is_comp_owner = [&](int32_t il, uint32_t ratio) {
|
||||
return model.hparams.dsv4_compress_ratios[il] == ratio && comp_read[il] == (int32_t) il;
|
||||
};
|
||||
|
||||
const auto is_state_owner = [&](int32_t il, uint32_t ratio) {
|
||||
if (model.hparams.dsv4_compress_ratios[il] != ratio) {
|
||||
return false;
|
||||
}
|
||||
return !is_v41 || model.hparams.dsv41_kv_src[il] == il;
|
||||
};
|
||||
|
||||
const layer_filter_cb filter_csa = [&](int32_t il) {
|
||||
if (filter && !filter(il)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return model.hparams.dsv4_compress_ratios[il] == DSV4_CSA_RATIO;
|
||||
return is_comp_owner(il, ratio_csa);
|
||||
};
|
||||
|
||||
const layer_filter_cb filter_hca = [&](int32_t il) {
|
||||
@@ -1285,52 +1385,67 @@ llama_kv_cache_dsv4::llama_kv_cache_dsv4(
|
||||
return false;
|
||||
}
|
||||
|
||||
return model.hparams.dsv4_compress_ratios[il] == DSV4_HCA_RATIO;
|
||||
return is_comp_owner(il, ratio_hca);
|
||||
};
|
||||
|
||||
const layer_filter_cb filter_csa_state = [&](int32_t il) {
|
||||
return (!filter || filter(il)) && is_state_owner(il, ratio_csa);
|
||||
};
|
||||
|
||||
const layer_filter_cb filter_hca_state = [&](int32_t il) {
|
||||
return (!filter || filter(il)) && is_state_owner(il, ratio_hca);
|
||||
};
|
||||
|
||||
const layer_filter_cb filter_lid = [&](int32_t il) {
|
||||
return !is_v41 && filter_csa(il);
|
||||
};
|
||||
|
||||
const bool unified_compressed = false;
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 CSA compressed KV cache, size = %u cells\n",
|
||||
__func__, dsv4_comp_size(kv_size, DSV4_CSA_RATIO));
|
||||
__func__, dsv4_comp_size(kv_size, ratio_csa));
|
||||
|
||||
kv_csa = std::make_unique<llama_kv_cache>(
|
||||
model, hparams_csa, type_k, type_v,
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, DSV4_CSA_RATIO), 256u), n_seq_max, n_pad,
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, ratio_csa), 256u), n_seq_max, n_pad,
|
||||
0, LLAMA_SWA_TYPE_NONE, nullptr, filter_csa, nullptr, nullptr);
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 HCA compressed KV cache, size = %u cells\n",
|
||||
__func__, dsv4_comp_size(kv_size, DSV4_HCA_RATIO));
|
||||
__func__, dsv4_comp_size(kv_size, ratio_hca));
|
||||
|
||||
kv_hca = std::make_unique<llama_kv_cache>(
|
||||
model, hparams_hca, type_k, type_v,
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, DSV4_HCA_RATIO), 256u), n_seq_max, n_pad,
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, ratio_hca), 256u), n_seq_max, n_pad,
|
||||
0, LLAMA_SWA_TYPE_NONE, nullptr, filter_hca, nullptr, nullptr);
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 lightning-indexer KV cache, size = %u cells\n",
|
||||
__func__, dsv4_comp_size(kv_size, DSV4_CSA_RATIO));
|
||||
__func__, dsv4_comp_size(kv_size, ratio_csa));
|
||||
|
||||
kv_lid = std::make_unique<llama_kv_cache>(
|
||||
model, hparams_lid, type_k, type_v,
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, DSV4_CSA_RATIO), 256u), n_seq_max, n_pad,
|
||||
0, LLAMA_SWA_TYPE_NONE, nullptr, filter_csa, nullptr, nullptr);
|
||||
v_trans, offload, unified_compressed, GGML_PAD(dsv4_comp_size(kv_size, ratio_csa), 256u), n_seq_max, n_pad,
|
||||
0, LLAMA_SWA_TYPE_NONE, nullptr, filter_lid, nullptr, nullptr);
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 CSA compressor state\n", __func__);
|
||||
|
||||
const uint32_t csa_state_size = is_v41 ? ratio_csa : 2*ratio_csa;
|
||||
const uint32_t csa_state_embd = is_v41 ? model.hparams.n_embd_head_k() : 2*model.hparams.n_embd_head_k();
|
||||
|
||||
csa_state = std::make_unique<llama_dsv4_comp_state>(
|
||||
model, offload, unified_compressed, n_seq_max, DSV4_CSA_RATIO, 2*DSV4_CSA_RATIO,
|
||||
2*model.hparams.n_embd_head_k(), n_rs_seq, "csa", filter_csa);
|
||||
model, offload, unified_compressed, n_seq_max, ratio_csa, csa_state_size,
|
||||
csa_state_embd, n_rs_seq, "csa", filter_csa_state);
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 HCA compressor state\n", __func__);
|
||||
|
||||
hca_state = std::make_unique<llama_dsv4_comp_state>(
|
||||
model, offload, unified_compressed, n_seq_max, DSV4_HCA_RATIO, DSV4_HCA_RATIO,
|
||||
model.hparams.n_embd_head_k(), n_rs_seq, "hca", filter_hca);
|
||||
model, offload, unified_compressed, n_seq_max, ratio_hca, ratio_hca,
|
||||
model.hparams.n_embd_head_k(), n_rs_seq, "hca", filter_hca_state);
|
||||
|
||||
LLAMA_LOG_INFO("%s: creating DSV4 lightning-indexer compressor state\n", __func__);
|
||||
|
||||
lid_state = std::make_unique<llama_dsv4_comp_state>(
|
||||
model, offload, unified_compressed, n_seq_max, DSV4_CSA_RATIO, 2*DSV4_CSA_RATIO,
|
||||
2*model.hparams.indexer_head_size, n_rs_seq, "lid", filter_csa);
|
||||
2*model.hparams.indexer_head_size, n_rs_seq, "lid", filter_lid);
|
||||
|
||||
// DSV4 attention reads compressed-K / compressor-state rows that the current
|
||||
// graph does not necessarily overwrite; uninitialized buffer contents would
|
||||
@@ -1339,12 +1454,76 @@ llama_kv_cache_dsv4::llama_kv_cache_dsv4(
|
||||
clear_compressed(-1, true);
|
||||
}
|
||||
|
||||
// Engram hashes the n-gram ending at each token, so a token shared by several sequences (llama-batched prompt)
|
||||
// is well-defined only if all of them agree on its predecessors; returns false (and logs) otherwise
|
||||
static bool dsv41_shared_history_ok(const llama_batch & batch, const llama_kv_cache * kv, uint32_t n_hist) {
|
||||
if (n_hist == 0 || !batch.token || !batch.n_seq_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool any_shared = false;
|
||||
for (int32_t i = 0; i < batch.n_tokens && !any_shared; ++i) {
|
||||
any_shared = batch.n_seq_id[i] > 1;
|
||||
}
|
||||
if (!any_shared) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<std::pair<llama_seq_id, llama_pos>, llama_token> in_batch;
|
||||
for (int32_t i = 0; i < batch.n_tokens; ++i) {
|
||||
for (int32_t s = 0; s < batch.n_seq_id[i]; ++s) {
|
||||
in_batch[{ batch.seq_id[i][s], batch.pos[i] }] = batch.token[i];
|
||||
}
|
||||
}
|
||||
|
||||
const auto tok_at = [&](llama_seq_id seq_id, llama_pos p) -> llama_token {
|
||||
// batch first: the batch continues the cache
|
||||
auto it = in_batch.upper_bound({ seq_id, p });
|
||||
if (it != in_batch.begin()) {
|
||||
--it;
|
||||
if (it->first.first == seq_id) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return kv ? kv->get_cells(seq_id).seq_pos_tok_le(seq_id, p) : LLAMA_TOKEN_NULL;
|
||||
};
|
||||
|
||||
for (int32_t i = 0; i < batch.n_tokens; ++i) {
|
||||
if (batch.n_seq_id[i] <= 1) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t d = 1; d <= n_hist; ++d) {
|
||||
const llama_pos p = batch.pos[i] - (llama_pos) d;
|
||||
if (p < 0) {
|
||||
break;
|
||||
}
|
||||
const llama_token t0 = tok_at(batch.seq_id[i][0], p);
|
||||
for (int32_t s = 1; s < batch.n_seq_id[i]; ++s) {
|
||||
const llama_token ts = tok_at(batch.seq_id[i][s], p);
|
||||
if (ts != t0) {
|
||||
LLAMA_LOG_ERROR("%s: token %d at pos %d is shared by seq %d and seq %d, whose Engram n-gram histories "
|
||||
"differ at pos %d (%d vs %d); decode such tokens per sequence\n", __func__,
|
||||
i, batch.pos[i], batch.seq_id[i][0], batch.seq_id[i][s], p, t0, ts);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
llama_memory_context_ptr llama_kv_cache_dsv4::init_batch(
|
||||
llama_batch_allocr & balloc,
|
||||
uint32_t n_ubatch,
|
||||
bool embd_all) {
|
||||
GGML_UNUSED(embd_all);
|
||||
|
||||
if (hparams_raw.ple_n_heads > 0 &&
|
||||
!dsv41_shared_history_ok(balloc.get_batch(), kv_raw->get_swa(), hparams_raw.ple_ngram_size - 1)) {
|
||||
return std::make_unique<llama_kv_cache_dsv4_context>(LLAMA_MEMORY_STATUS_FAILED_PREPARE);
|
||||
}
|
||||
|
||||
const bool raw_per_seq = kv_raw->get_base()->get_n_stream() != 1;
|
||||
const bool comp_per_seq = csa_state->get_n_stream() > 1;
|
||||
const bool has_coupled = dsv4_batch_has_coupled(balloc.get_batch());
|
||||
@@ -1475,9 +1654,9 @@ bool llama_kv_cache_dsv4::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1
|
||||
bool res = true;
|
||||
|
||||
res = res & kv_raw->seq_rm(seq_id, p0, -1);
|
||||
res = res & kv_csa->seq_rm(seq_id, p0/DSV4_CSA_RATIO, -1);
|
||||
res = res & kv_hca->seq_rm(seq_id, p0/DSV4_HCA_RATIO, -1);
|
||||
res = res & kv_lid->seq_rm(seq_id, p0/DSV4_CSA_RATIO, -1);
|
||||
res = res & kv_csa->seq_rm(seq_id, p0/csa_state->get_ratio(), -1);
|
||||
res = res & kv_hca->seq_rm(seq_id, p0/hca_state->get_ratio(), -1);
|
||||
res = res & kv_lid->seq_rm(seq_id, p0/lid_state->get_ratio(), -1);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1613,15 +1792,15 @@ void llama_kv_cache_dsv4::state_write(llama_io_write_i & io, llama_seq_id seq_id
|
||||
|
||||
//FIXME : note that we conflate token positions with rows, which is not true for multi-modal case.
|
||||
const uint32_t n_rows_csa = seq_id >= 0 ?
|
||||
dsv4_state_n_used_k_rows(pos_max, DSV4_CSA_RATIO, kv_csa->get_size()) : kv_csa->get_size();
|
||||
dsv4_state_n_used_k_rows(pos_max, csa_state->get_ratio(), kv_csa->get_size()) : kv_csa->get_size();
|
||||
const uint32_t n_rows_hca = seq_id >= 0 ?
|
||||
dsv4_state_n_used_k_rows(pos_max, DSV4_HCA_RATIO, kv_hca->get_size()) : kv_hca->get_size();
|
||||
dsv4_state_n_used_k_rows(pos_max, hca_state->get_ratio(), kv_hca->get_size()) : kv_hca->get_size();
|
||||
const uint32_t n_rows_lid = seq_id >= 0 ?
|
||||
dsv4_state_n_used_k_rows(pos_max, DSV4_CSA_RATIO, kv_lid->get_size()) : kv_lid->get_size();
|
||||
dsv4_state_n_used_k_rows(pos_max, lid_state->get_ratio(), kv_lid->get_size()) : kv_lid->get_size();
|
||||
|
||||
dsv4_state_write_k_cache(io, kv_csa.get(), seq_id, flags, n_rows_csa);
|
||||
dsv4_state_write_k_cache(io, kv_hca.get(), seq_id, flags, n_rows_hca);
|
||||
dsv4_state_write_k_cache(io, kv_lid.get(), seq_id, flags, n_rows_lid);
|
||||
dsv4_state_write_k_cache(io, kv_csa.get(), seq_id, flags, n_rows_csa, comp_kv_src);
|
||||
dsv4_state_write_k_cache(io, kv_hca.get(), seq_id, flags, n_rows_hca, comp_kv_src);
|
||||
dsv4_state_write_k_cache(io, kv_lid.get(), seq_id, flags, n_rows_lid, comp_kv_src);
|
||||
}
|
||||
|
||||
csa_state->state_write(io, seq_id, flags, rs_idx);
|
||||
@@ -1659,9 +1838,9 @@ void llama_kv_cache_dsv4::state_read(llama_io_read_i & io, llama_seq_id seq_id,
|
||||
if (!partial_only) {
|
||||
clear_compressed(seq_id, true);
|
||||
|
||||
dsv4_state_read_k_cache(io, kv_csa.get(), seq_id, flags);
|
||||
dsv4_state_read_k_cache(io, kv_hca.get(), seq_id, flags);
|
||||
dsv4_state_read_k_cache(io, kv_lid.get(), seq_id, flags);
|
||||
dsv4_state_read_k_cache(io, kv_csa.get(), seq_id, flags, comp_kv_src);
|
||||
dsv4_state_read_k_cache(io, kv_hca.get(), seq_id, flags, comp_kv_src);
|
||||
dsv4_state_read_k_cache(io, kv_lid.get(), seq_id, flags, comp_kv_src);
|
||||
}
|
||||
|
||||
csa_state->state_read(io, seq_id, flags);
|
||||
@@ -1704,6 +1883,10 @@ llama_dsv4_comp_state * llama_kv_cache_dsv4::get_lid_state() const {
|
||||
return lid_state.get();
|
||||
}
|
||||
|
||||
int32_t llama_kv_cache_dsv4::get_comp_read_layer(int32_t il) const {
|
||||
return comp_read[il];
|
||||
}
|
||||
|
||||
uint32_t llama_kv_cache_dsv4::get_n_rs_seq() const {
|
||||
return n_rs_seq;
|
||||
}
|
||||
@@ -1932,6 +2115,11 @@ void llama_kv_cache_dsv4_raw_context::set_input_kq_mask(ggml_tensor * dst, const
|
||||
kv_swa->set_input_kq_mask(dst, ubatch, causal_attn);
|
||||
}
|
||||
|
||||
void llama_kv_cache_dsv4_raw_context::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const {
|
||||
GGML_ASSERT(kv_swa != nullptr);
|
||||
kv_swa->get_prev_tokens(ubatch, n, res);
|
||||
}
|
||||
|
||||
void llama_kv_cache_dsv4_raw_context::set_input_k_rot(ggml_tensor * dst) const {
|
||||
kv_swa->set_input_k_rot(dst);
|
||||
}
|
||||
@@ -2016,7 +2204,8 @@ llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
reserve_plans(true),
|
||||
status(llama_memory_status_combine(
|
||||
llama_memory_status_combine(ctx_raw->get_status(), ctx_csa_mem->get_status()),
|
||||
llama_memory_status_combine(ctx_hca_mem->get_status(), ctx_lid_mem->get_status()))) {
|
||||
llama_memory_status_combine(ctx_hca_mem->get_status(), ctx_lid_mem->get_status()))),
|
||||
kv(kv) {
|
||||
}
|
||||
|
||||
llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
@@ -2041,7 +2230,8 @@ llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
llama_memory_status_combine(ctx_raw->get_status(), ctx_csa_mem->get_status()),
|
||||
llama_memory_status_combine(ctx_hca_mem->get_status(), ctx_lid_mem->get_status())),
|
||||
this->sc_info_csa.empty() && this->sc_info_hca.empty() && this->sc_info_lid.empty() ?
|
||||
LLAMA_MEMORY_STATUS_NO_UPDATE : LLAMA_MEMORY_STATUS_SUCCESS)) {
|
||||
LLAMA_MEMORY_STATUS_NO_UPDATE : LLAMA_MEMORY_STATUS_SUCCESS)),
|
||||
kv(kv) {
|
||||
}
|
||||
|
||||
llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
@@ -2052,13 +2242,13 @@ llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
std::vector<llama_ubatch> ubatches,
|
||||
std::vector<llama_ubatch> ubatches_raw) :
|
||||
ubatches(std::move(ubatches)),
|
||||
plans_csa(dsv4_build_comp_plans(this->ubatches, DSV4_CSA_RATIO, true,
|
||||
plans_csa(dsv4_build_comp_plans(this->ubatches, kv->get_csa_state()->get_ratio(), kv->get_csa_state()->is_overlap(), true,
|
||||
kv->get_csa_state()->get_state_size(), kv->get_csa()->get_size(), kv->get_csa_state()->get_n_stream(),
|
||||
kv->get_n_rs_seq(), kv->get_rs_idx())),
|
||||
plans_hca(dsv4_build_comp_plans(this->ubatches, DSV4_HCA_RATIO, false,
|
||||
plans_hca(dsv4_build_comp_plans(this->ubatches, kv->get_hca_state()->get_ratio(), kv->get_hca_state()->is_overlap(), false,
|
||||
kv->get_hca_state()->get_state_size(), kv->get_hca()->get_size(), kv->get_hca_state()->get_n_stream(),
|
||||
kv->get_n_rs_seq(), kv->get_rs_idx())),
|
||||
plans_lid(dsv4_build_comp_plans(this->ubatches, DSV4_CSA_RATIO, true,
|
||||
plans_lid(dsv4_build_comp_plans(this->ubatches, kv->get_lid_state()->get_ratio(), kv->get_lid_state()->is_overlap(), true,
|
||||
kv->get_lid_state()->get_state_size(), kv->get_lid()->get_size(), kv->get_lid_state()->get_n_stream(),
|
||||
kv->get_n_rs_seq(), kv->get_rs_idx())),
|
||||
ctx_raw(std::make_unique<llama_kv_cache_dsv4_raw_context>(
|
||||
@@ -2086,7 +2276,8 @@ llama_kv_cache_dsv4_context::llama_kv_cache_dsv4_context(
|
||||
csa_state(kv->get_csa_state()),
|
||||
hca_state(kv->get_hca_state()),
|
||||
lid_state(kv->get_lid_state()),
|
||||
status(ctx_raw->get_status()) {
|
||||
status(ctx_raw->get_status()),
|
||||
kv(kv) {
|
||||
kv->reset_rs_idx_for_ubatches(this->ubatches);
|
||||
}
|
||||
|
||||
@@ -2181,6 +2372,10 @@ const llama_dsv4_comp_state * llama_kv_cache_dsv4_context::get_lid_state() const
|
||||
return lid_state;
|
||||
}
|
||||
|
||||
int32_t llama_kv_cache_dsv4_context::get_comp_read_layer(int32_t il) const {
|
||||
return kv->get_comp_read_layer(il);
|
||||
}
|
||||
|
||||
const llama_kv_cache_dsv4_context::comp_plan & llama_kv_cache_dsv4_context::get_csa_plan() const {
|
||||
assert(status == LLAMA_MEMORY_STATUS_SUCCESS);
|
||||
|
||||
@@ -2222,7 +2417,7 @@ const llama_kv_cache_dsv4_context::comp_plan & llama_kv_cache_dsv4_context::get_
|
||||
}
|
||||
|
||||
reserve_plan_csa = dsv4_build_reserve_comp_plan(
|
||||
ubatch, DSV4_CSA_RATIO, true,
|
||||
ubatch, csa_state->get_ratio(), csa_state->is_overlap(),
|
||||
csa_state->get_state_size(), get_csa()->get_n_kv(), csa_state->get_n_stream(), csa_state->get_n_rs_seq());
|
||||
|
||||
return reserve_plan_csa;
|
||||
@@ -2236,7 +2431,7 @@ const llama_kv_cache_dsv4_context::comp_plan & llama_kv_cache_dsv4_context::get_
|
||||
}
|
||||
|
||||
reserve_plan_hca = dsv4_build_reserve_comp_plan(
|
||||
ubatch, DSV4_HCA_RATIO, false,
|
||||
ubatch, hca_state->get_ratio(), hca_state->is_overlap(),
|
||||
hca_state->get_state_size(), get_hca()->get_n_kv(), hca_state->get_n_stream(), hca_state->get_n_rs_seq());
|
||||
|
||||
return reserve_plan_hca;
|
||||
@@ -2250,7 +2445,7 @@ const llama_kv_cache_dsv4_context::comp_plan & llama_kv_cache_dsv4_context::get_
|
||||
}
|
||||
|
||||
reserve_plan_lid = dsv4_build_reserve_comp_plan(
|
||||
ubatch, DSV4_CSA_RATIO, true,
|
||||
ubatch, lid_state->get_ratio(), lid_state->is_overlap(),
|
||||
lid_state->get_state_size(), get_lid()->get_n_kv(), lid_state->get_n_stream(), lid_state->get_n_rs_seq());
|
||||
|
||||
return reserve_plan_lid;
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
void apply_copies(const stream_copy_info & sc_info) const;
|
||||
|
||||
uint32_t get_ratio() const;
|
||||
bool is_overlap() const; // V4 CSA: state holds 2*ratio rows of 2x width
|
||||
uint32_t get_state_size() const;
|
||||
uint32_t get_n_stream() const;
|
||||
uint32_t get_n_rs_seq() const;
|
||||
@@ -148,6 +149,9 @@ public:
|
||||
llama_dsv4_comp_state * get_hca_state() const;
|
||||
llama_dsv4_comp_state * get_lid_state() const;
|
||||
|
||||
// layer whose compressed-KV storage `il` reads, -1 = no compressed KV
|
||||
int32_t get_comp_read_layer(int32_t il) const;
|
||||
|
||||
uint32_t get_n_rs_seq() const;
|
||||
const std::vector<uint32_t> & get_rs_idx() const;
|
||||
void reset_rs_idx_for_ubatches(const std::vector<llama_ubatch> & ubatches);
|
||||
@@ -163,6 +167,12 @@ private:
|
||||
|
||||
std::vector<uint32_t> rs_idx;
|
||||
|
||||
// [n_layer] storage layer -> layer whose rows it holds (itself, or the source of a mirror)
|
||||
std::vector<int32_t> comp_kv_src;
|
||||
|
||||
// [n_layer] layer -> layer whose compressed-KV storage it reads (itself, its V4.1 kv source or a mirror), -1 = none
|
||||
std::vector<int32_t> comp_read;
|
||||
|
||||
std::unique_ptr<llama_kv_cache_iswa> kv_raw;
|
||||
std::unique_ptr<llama_kv_cache> kv_csa;
|
||||
std::unique_ptr<llama_kv_cache> kv_hca;
|
||||
@@ -214,6 +224,9 @@ public:
|
||||
void set_input_kq_mask(ggml_tensor * dst, const llama_ubatch * ubatch, bool causal_attn) const;
|
||||
void set_input_k_rot(ggml_tensor * dst) const;
|
||||
|
||||
// token history of the raw (window) cells, for host-side n-gram hashing (deepseek41 Engram)
|
||||
void get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const;
|
||||
|
||||
private:
|
||||
size_t i_next = 0;
|
||||
|
||||
@@ -371,6 +384,8 @@ public:
|
||||
const comp_plan & get_hca_plan(const llama_ubatch & ubatch) const;
|
||||
const comp_plan & get_lid_plan(const llama_ubatch & ubatch) const;
|
||||
|
||||
int32_t get_comp_read_layer(int32_t il) const;
|
||||
|
||||
private:
|
||||
size_t i_next = 0;
|
||||
|
||||
@@ -403,4 +418,6 @@ private:
|
||||
mutable comp_plan reserve_plan_lid;
|
||||
|
||||
const llama_memory_status status;
|
||||
|
||||
const llama_kv_cache_dsv4 * kv = nullptr;
|
||||
};
|
||||
|
||||
@@ -1168,9 +1168,7 @@ void llama_kv_cache::apply_ubatch(const slot_info & sinfo, const llama_ubatch &
|
||||
} else if (hparams.ple_n_heads > 0) {
|
||||
// embd batch (multimodal input) has no token ids, need to pad it with the correct ID for PLE layers
|
||||
// TODO @ngxson : check if we can do the same as gemma 3n / gemma 4
|
||||
ext.tok = hparams.ple_image_token_id != 0
|
||||
? (llama_token) hparams.ple_image_token_id
|
||||
: (llama_token) hparams.ple_eos_token_id;
|
||||
ext.tok = hparams.ple_embd_token;
|
||||
}
|
||||
|
||||
cells.ext_set(idx, ext);
|
||||
@@ -1849,7 +1847,7 @@ void llama_kv_cache::set_input_v_rot(ggml_tensor * dst) const {
|
||||
}
|
||||
|
||||
bool llama_kv_cache::has_cell_ext() const {
|
||||
// M-RoPE needs the 2D position, the PLE n-gram hash needs the token id
|
||||
// M-RoPE needs the 2D position, the PLE / Engram n-gram hashes need the token id
|
||||
return hparams.n_pos_per_embd() > 1 || hparams.ple_n_heads > 0;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user