Compare commits

..
25 Commits
Author SHA1 Message Date
Aman GuptaandDaniel Han 74bca39500 model: DSV4.1 flash (#31086)
* model : add DeepSeek-V4.1-Flash (deepseek41)

* deepseek41 : remove MXFP8, convert the Engram table to BF16

* deepseek41 : compute the compressed-KV read layers once in the cache

* deepseek41 : use get_cells() for the shared-history check, drop llama_kv_cache::seq_pos_tok_le

* deepseek41 : gather the Engram rows in-graph like qwen4exp, drop the host gather

* deepseek41 : keep the Engram hash constants in hparams.ple_*, one module per Engram layer

* deepseek41 : drop the LLAMA_DSV41_KV_MIRROR toggle

* deepseek41 : keep il last in build_hc_mixes

* deepseek41 : sort the top-k rows before the sparse gather

* tests : add deepseek41 to the Metal fusion baseline

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-10-25 09:43:51 +08:00
Joseph Patricio d327bf1f07 jinja : fix select and selectattr argument handling (#31059)
* jinja : fix select and selectattr argument handling

* jinja : address review feedback

* jinja : Simplified test attr
2026-10-24 22:54:46 +02:00
Ehsan Bateni 78a8ae14be hexagon: KV-head team split for multicore HMX flash attention (#31055)
* hexagon: KV-head team split for HMX flash attention in multicore

* hexagon: merge FA_HEAD_SPLIT and FA_SPLIT into single GGML_HEXAGON_FA_SPLIT flag

0 = off (token-split only); 1 = head-split, plus team-split when
n_kv_heads < N_cores so stranded cores still participate (e.g. gemma-4
with 2 KV heads on 4 cores). Where heads divide evenly across cores,
plain head-split is used.
2026-10-24 12:19:32 -07:00
Xuan-Son Nguyen f3269ef96a server: refactor server entrypoint (#31113)
* server_role

* cont
2026-10-24 20:04:28 +02:00
Georgi Gerganov 3f03b17724 metal : remove mul_mm_id amax rescaling (#30924)
The src1 power-of-two rescale in kernel_mul_mm_id is superseded by the
per expert-token amax scaling done in the compute graph
(ref: https://github.com/ggml-org/llama.cpp/pull/30861), which keeps the
MoE down-projection activations within f16 range before the op runs.

Drop the two-stage reduction kernels, their scratch buffer and the
GGML_PREC_F32 dispatch added in https://github.com/ggml-org/llama.cpp/pull/26223,
as well as the test cases that drive MUL_MAT_ID src1 out of f16 range.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-24 18:40:19 +03:00
Katara 9a0fde9967 rpc : add bounds check in rdma_recv (#30399) 2026-10-24 18:34:46 +03:00
Aleksander Grygier e168e041ec ui: Models Manager as Route + UI improvements (#30988)
* ui : move models manager to its own route, keep Discover as a dialog

Models now lives at #/models instead of a dialog. Discover opens as a
standalone dialog over it, and leaving the page returns to the chat it
was opened from.

Assisted by: Claude (Opus 5.5 Medium)

* ui : polish the models page layout and filters

Give the models page a page header with the Discover action, calmer
column labels, tinted section and family headers, and an empty state.
Load the model list on a direct visit, slide the detail pane over the
table below xl, and draw the filter toggles as one box matching the
context select, with room for their shadow.

Assisted by: Claude (Opus 5.5 Medium)

* ui : round the models table family headers

Assisted by: Claude (Opus 5.5 Medium)

* ui : fix models page navigation and Discover gating

Record the models page's way back after every navigation, so Back,
a typed URL or a reload no longer return to a stale chat. Escape
closes the detail pane, then leaves the page. Gate every Discover
entry point on one shared check, recheck the API key on /models,
skip the duplicate router fetch on a direct load, and share the
route load between pages.

Assisted by: Claude (Opus 5.5 Medium)

* ui : show model details in a drawer

Open the models manager's detail pane in a drawer, from the right on
desktop and from the bottom on a phone, instead of an inline side
panel and a full-screen cover. Pad the pane on both sides, drop its
close button on a phone, and assert that filtered rows are gone from
the page rather than merely hidden.

Assisted by: Claude (Opus 5.5 Medium)

* ui : make whole models table rows clickable

A click anywhere on a model or quant row opens it, unless it lands on
one of the row's own controls (load toggle, actions menu). The name
button stays the keyboard path to the same action.

Assisted by: Claude (Opus 5.5 Medium)
2026-10-24 18:34:11 +03:00
Aleksander Grygier ea1d7d66f3 fix(ui): align hover card and context popup styling with dropdown menu (#30978)
Use the dropdown menu surface (border, radius, background, shadow,
padding) for the hover card and the context gauge popup, and fix the
hover card open/close animation to use bits-ui data-state attributes.

Assisted-by: Claude (Opus 5.5 Medium)
2026-10-24 18:33:27 +03:00
Georgi Gerganov 71255fefc0 ci : pass per-runner results dir to self-hosted jobs on T4 runners (#31100)
The two T4 runners in the ggml-ci group (ggml-6-x86-vulkan-t4,
ggml-8-x86-nvidia-a10) share the same labels and can both pick up the
same jobs, so extend the per-runner results dir from #31046 to them.

Assisted-by: dsh:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-24 17:17:04 +03:00
Satyam Thakurandorigin-labs-ai 73adf89e38 minimax-m3 : forward quant scales to the graph (fixes #31063) (#31097)
The generic scale pass in llama-model.cpp loads the per-expert
NVFP4 scale2 tensors (ffn_{up,gate,down}_exps_s) and the per-tensor
MXFP8 scales for the dense FFN and the shared expert, but
minimax-m3.cpp never forwarded any of them to the graph.

build_moe_ffn() defaults these to nullptr, so build_lora_mm_id()
skipped its w_s branch entirely and the routed-expert weights came
out global_scale times too large, swamping the residual (reported
G ~= 15123 for M3.1 Flash) and producing token soup.

Forward all scale tensors at the three call sites, matching the
pattern used by flint and the other NVFP4-capable MoE models.

Co-authored-by: origin-labs-ai <origin-labs-ai@users.noreply.github.com>
2026-10-24 16:09:02 +02:00
Alexey Kopytko df9beac715 tests : fix test-llama-context for large n_batch (#31083) 2026-10-24 14:14:58 +03:00
Georgi Gerganov 4d32b29aaa ci : exclude test-speculative-rejection-sampling from macos-latest-arm64 (#31088)
Add test-speculative-rejection-sampling to the ctest -E exclusion list of the macos-latest-arm64 CI job.

Assisted-by: dsh:llama.cpp/ggml-org/Flint-GGUF (RTX 5090 32GB, DDR5 128GB)
2026-10-24 14:14:31 +03:00
Georgi Gerganov 3032209758 llama : deny -sm tensor for arches with moe amax rescaling (#31081)
The amax POOL_1D reduction spans the whole matmul row, so a device cannot
compute it from an axis-0 split. The Meta split-state case reported
MIRRORED, which gives each device a partial max of its own slice. Drop
that case and deny -sm tensor for the arches which build the amax graph,
see [TAG_MOE_AMAX_SCALE].

Assisted-by: dsh:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-24 13:48:32 +03:00
Xuan-Son Nguyenandyomaytk 701fd25b3a ggml : allow MUL_MAT_ID -1 index to skip the computation (#26631)
* ggml : allow MUL_MAT_ID -1 index to skip the computation

* nits

* update vulkan code

* ggml-webgpu: ensure all threads reach workgroup barriers in mul_mat_id_vec

(cherry picked from commit d8a4b9efb6e2b9abf3a866e847756b7e6bbc6b69)

* fix cuda

* openvino

* exclude CUDA from this PR

* add TODO tag

---------

Co-authored-by: yomaytk <yoshimura.masashi.frbs@gmail.com>
2026-10-24 12:15:36 +02:00
Daniel Bevenius 3642238cc1 tests : add speculative rejection draft sampling test (#30213)
* tests : add speculative rejection draft sampling test

This commit adds a test for speculative rejection sampling test that
excercises the speculative rejection sampling mechanism in
common/speculative.cpp.

The motivation for this is have a test that verifies that the
speculative draft models emitts tokens that  match the target models
distribution. So when rejection sampling occurs it should still
produce the same probability distribution as if we directly sampled
from the target model.

Running the test:
```console
$ ctest --test-dir build -R "^test-speculative-rejection-sampling$" \
    --n_trials 5000 -V
```
This will generate two models, one target and one draft model.

For subsequent runs use the "fixture exclude any" option to avoid
recreating the models:
```console
$ ctest --test-dir build-cpu-debug -R \
"^test-speculative-rejection-sampling$" \
    -FA "^speculative-models$" --n_trails 5000 -V
```

* remove draft model
2026-10-24 12:47:49 +03:00
Georgi Gerganov 6b9ba4f2e8 ci : fix data race between runners sharing the model cache (#31046)
* ci : fix data race between runners sharing the model cache

Two runners per mac mini share HOME, so concurrent jobs corrupt each
other downloads, converted models and venv under the shared MNT cache.

- gg_wget: download in a temp dir, publish with mv, so readers only
  ever see complete files (-N cannot be combined with -O)
- convert, quantize and wikitext unzip now produce under a per-runner
  scratch dir in OUT, published to MNT via hardlink when missing
- gguf-split and quantize tests get a per-runner TMP_DIR
- python venv moved from MNT to OUT
- metal, vulkan and webgpu mac jobs pass a per-runner OUT path

Assisted-by: dsh:llama.cpp/agent-qwen4 (RTX 5090 machine)

* ci : clean up scratch models after tests

Assisted-by: dsh:llama.cpp/agent-qwen4 (RTX 5090 machine)

* ci : fix q4_0 publish when a cached model exists

cp -n skips an existing file with exit code 1, so the ln fallback
aborted the qwen3 job under set -e. Publish through a unique temp
file with an atomic mv: readers never see a partial file and a
stale cached q4_0 gets replaced.

Assisted-by: dsh:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (RTX 5090 machine)

* ci : cache-first gg_wget to avoid re-downloads

HF sends no Last-Modified, so wget -N re-downloads the full files on
every run. Skip the download when a non-empty cache file exists: the
HF CI artifacts are static. Downloads still go through a temp dir and
an atomic mv, so concurrent runners never read partial files.

Assisted-by: dsh:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (RTX 5090 machine)
2026-10-24 09:21:24 +03:00
Ruben Ortlam 8e5a58df87 vulkan: ggml src1 precision support (#30656)
* vulkan: update accumulator precision selection

* handle fp32 overflows through scaling

* handle overflows in copy-to-fp16 activations

* add missing cases

* simplify code

* add tests

* add ggml prec get helpers, use in Vulkan backend

* clean up comments

* revert comment edit

* trim tests

* also handle fp32 src0, fix test scaling src0 values too

* don't scale if disabled

* remove fp16 scaling

* add check to mul_mat

* fix inconsistency

* rename to acc_prec
2026-10-24 06:05:05 +02:00
Arseniy Obolenskiy 1ddffe9f70 HIP: add byte permute fallback when __builtin_amdgcn_perm is unavailable (#30915)
* HIP: add byte permute fallback when __builtin_amdgcn_perm is unavailable

amdgcnspirv rejects target-specific builtins unless they are guarded by `__builtin_amdgcn_is_invocable`, so 66 of 145 HIP files failed to build

Wrap the perm calls in a helper that keeps v_perm_b32 when the target has it and falls back to a portable byte select otherwise

* move perm helper to vendors/hip.h
2026-10-24 12:04:05 +08:00
Xuan-Son Nguyen 7894e8b924 mtmd: move build_inp_temporal_merge to base class (#31065) 2026-10-24 03:38:25 +02:00
Yuri KhrustalevandXuan Son Nguyen edc353842c mtmd : lfm2 resize matches torchvision (#30943)
* mtmd : lfm2 bilinear resize matches torchvision

Lfm2VlImageProcessor and d1-omni vision.py resize uint8 images with
torchvision (antialias=True). That is Pillow's algorithm, except that the
weights are int16 with as many fractional bits as the largest weight allows,
where Pillow uses 22 bits. The Pillow port was 1-2 levels off on about 1% of
the values of the d1-omni and LFM2.5-VL crops.

RESIZE_ALGO_BILINEAR_TORCH rounds the weights of the Pillow port to that
precision and scales them back to 22 bits, which gives the output of
torchvision with the same passes, and "bilinear" maps to it for LFM2 and
d1-omni. Other models keep the Pillow algorithms.

* mtmd : lfm2 bicubic resize matches torchvision

Lfm2VlImageProcessor resizes with torchvision for the bicubic checkpoints too
(d1-3B, LFM2-VL), so "bicubic" maps to RESIZE_ALGO_BICUBIC_TORCH for LFM2: the
Pillow bicubic filter (a = -0.5, as torchvision with antialias) with the int16
weights of torchvision. The Pillow port was 1-2 levels off on about 0.25% of
the values of the d1-3B crops.

* use bitfield, add hparams setter

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-24 02:21:06 +02:00
Hans 9ae73e68f5 mtmd : add Soprano TTS support (#28607)
Add Soprano-80M and Soprano-1.1-80M (https://huggingface.co/ekwek/Soprano-1.1-80M)
as an audio generation model: a Qwen3 backbone plus a Vocos decoder that runs
as a gen-only mmproj.

- convert: detect Soprano checkpoints and export the backbone as qwen3 and the
  decoder.pth Vocos weights as the mmproj
- vocab: add the soprano pre-tokenizer (lowercase, whitespace collapse,
  per-digit split, [UNK] fallback)
- mtmd: add the Vocos graph and ISTFT, allow loading a gen-only mmproj for
  Soprano and include the gen-audio context in mtmd_get_memory_usage
- mtmd-helper: add the Soprano pipeline; the decoder runs over the whole
  utterance, so stream mode sends the audio in one chunk after flush()
- tts: document Soprano usage

Assisted-by: Codex
Assisted-by: Claude Opus 5.5
2026-10-24 01:09:50 +02:00
kurquharandMax Krasnyansky 0ae0e19766 hexagon: add SGN op (#30346)
* hexagon : add SGN support

Assisted-by: OpenCode

* hexagon : optimize SGN with sign bit

Assisted-by: OpenCode

* hex-sgn: add f16

* hex-unary: update SGN to use generic unary_colapse and relax colapse checks to apply to more conditions

* hex-unary: improve dims colapsing and run it in the host-side solver

---------

Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
2026-10-23 14:58:44 -07:00
Adrien Gallouët 21ec307146 common : allow concurrent downloads across processes (#29312)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
2026-10-23 23:52:56 +02:00
WalterCool 9145ddb955 hip: fix bf16 WMMA operand vector type for gfx11/gfx12 (#30885)
The __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32 (and _gfx12) builtins
take their A/B operands as vectors of short (_ExtVector<16, short> /
_ExtVector<8, short>), not __bf16. Declaring the fragments as
ext_vector_type(N) __bf16 fails to compile:

  error: cannot initialize a parameter of type 'short
  __attribute__((ext_vector_type(16)))' with an lvalue of type 'const
  bf16x16_t' (vector of 16 '__bf16' values)

short and __bf16 are both 16-bit and the value is only ever passed to
the builtin via reinterpret_cast over raw bytes, so changing the element
type is bit-identical. The operand type has been short since the builtin
was introduced, so this works across LLVM/ROCm versions without a
version gate. Fixes HIP builds for RDNA3 (gfx1150/1151) and RDNA4
(gfx1200/1201).

Signed-off-by: Pablo Cholaky <waltercool@slash.cl>
2026-10-23 23:31:28 +02:00
thelittlefireman 1a24df1639 ggm-cuda: Enforce DP4A MMQ tile height multiple of warp size (#30837)
Added static assertion to enforce DP4A MMQ tile height requirement.
2026-10-23 21:37:20 +02:00
180 changed files with 6305 additions and 1642 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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]
+1 -1
View File
@@ -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
+88 -91
View File
@@ -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
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+179
View 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();
}
+36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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",
+467
View File
@@ -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"]
+116
View File
@@ -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
+8 -4
View File
@@ -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:
+12
View File
@@ -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`
+1 -10
View File
@@ -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;
+6 -1
View File
@@ -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;
+10 -5
View File
@@ -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));
}
}
+6 -1
View File
@@ -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;
+6 -1
View File
@@ -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;
+1 -1
View File
@@ -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) {
+2 -2
View File
@@ -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]);
+6 -6
View File
@@ -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);
+1
View File
@@ -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;
+1 -1
View File
@@ -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;
}
}
+1 -1
View File
@@ -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) {
+12 -12
View File
@@ -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);
+19
View File
@@ -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)
}
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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;
+67 -3
View File
@@ -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);
+23 -1
View File
@@ -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;
+15 -2
View File
@@ -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)) {
+1
View File
@@ -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
};
+50
View File
@@ -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);
+11
View File
@@ -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);
+1
View File
@@ -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);
+30 -6
View File
@@ -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;
+44 -3
View File
@@ -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;
+6 -25
View File
@@ -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;
+10 -39
View File
@@ -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);
+1 -2
View File
@@ -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);
+5 -8
View File
@@ -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;
+16 -51
View File
@@ -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;
-1
View File
@@ -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);
-1
View File
@@ -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) {
+25 -84
View File
@@ -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);
}
}
}
+13 -3
View File
@@ -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,
+35
View File
@@ -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, &region, &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, &region, &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
+8
View File
@@ -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);
+5
View File
@@ -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;
+9
View File
@@ -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;
+68 -10
View File
@@ -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();
+18 -2
View File
@@ -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 {
+56 -23
View File
@@ -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]);
+4
View File
@@ -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({
+14 -2
View File
@@ -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++) {
+6 -1
View File
@@ -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++;
+32
View File
@@ -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,
+72
View File
@@ -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"
+25
View File
@@ -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)
+19
View File
@@ -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:
+12
View File
@@ -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,
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+17
View File
@@ -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;
};
+2 -4
View File
@@ -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