Compare commits

...
157 Commits
Author SHA1 Message Date
Hongqiang Wang 63b4b303bd opencl: write KV-cache views in set_tensor so a state restore is not dropped (#30471) 2026-10-26 11:39:22 -07:00
Max Krasnyansky caa8191df1 hexagon: MUL_MAT_ID row skipping optimizations and improvements (#31169)
* hex-mmid: vectorize skipped row zeroing and added zeroing to fused MM_ID_NX

* hex-mmid: remove scalar div
2026-10-26 08:21:14 -07:00
Georgi Gerganov a4699400e3 llama : move no_alloc out of llama_hparams (#31141)
* llama : move no_alloc out of llama_hparams

Assisted-by: dsh:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))

* cont : clean-up

* cont : clean-up

Assisted-by: dsh:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))

* tests : add a no_alloc case to test-llama-context

Assisted-by: dsh:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))
2026-10-26 16:21:25 +02:00
Georgi Gerganov 4b851fbee8 models : replace ggml_tensor** out-params with output structs (#31142)
The hyper-connection and DSA graph builders wrote their extra outputs
(post / comb gates, carried top-k selections, inject gates) through
ggml_tensor** out-params. Return them in a small struct next to the
primary output instead.

Assisted-by: dsh:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-26 16:19:47 +02:00
Pascal 4d71b42fd8 ggml-cpu : rewrite concat as one row kernel for all types (#31179)
Every dst row comes from src0 or src1, or for dim 0 from the row of
src0 followed by the row of src1, so a single kernel covers every
type: a dense source row is one memcpy, quantized types included,
and a strided source is copied element by element with the element
size known at compile time.

Rows are split across threads instead of i2 alone, which is often 1,
and are copied in tiles of 32: for a transposed source the elements
are walked in the outer loop, so the rows of a tile read neighboring
addresses instead of jumping a full row stride per element.

Add transposed b cases to test_concat, the layout of the delta-net
conv input, and a perf case for it.
2026-10-26 16:18:45 +02:00
virajwad 70c5f26324 vulkan: rms norm optimization using subgroup reductions (#29882)
* RMS Norm Subgroup reductions optimizaton

* Fix build error

* Improved optimization

* Make usage of BLOCK_SIZE consistent

* Add fast path to skip 2nd subgroupAdd

* Add AMD Vendor to subgroup reduction opt

* Add comment clarifying rms_dict and merge_maps behavior

* Add unroll

* Rebase

* Fixed syntax issue

* Add 128/256/512 block sizes to non-subgroup path

* Remove trailing whitespace
2026-10-26 16:14:31 +02:00
Titaniumtown 5cd62ffee9 sycl: template the plain MMVQ launch triples (#31013) 2026-10-26 16:13:16 +02:00
Titaniumtown 38645c2ad9 sycl: template the per-quant concat impls (#31008) 2026-10-26 16:12:56 +02:00
Captain-TrippsandJon Staples 02269b4c00 sycl: add set_tensor_2d for faster tensor-split loading (#30358)
* sycl: add set_tensor_2d for faster tensor-split loading

Pack host rows into chunks of up to 64 MiB when destination rows are
contiguous, avoiding a separate upload call for each row. Keep the
existing per-row copy path for non-contiguous destinations.

On two Arc Pro B70s with gpt-oss-120b UD-Q4_K_XL, total llama-perplexity
runtime drops from 263-268 s to 114-115 s. A full-load readback check
verified 26,784,256 rows with no mismatches.

* sycl: batch 2D tensor uploads through shared staging slots

Implement set_tensor_2d to speed up tensor-split model loading by
packing rows into the existing pinned staging slots and uploading
each batch with a single device copy.

Share staging initialization with set_tensor and wait only when
reusing a slot, allowing CPU packing and device copies to overlap.

Fall back to per-row uploads for noncontiguous device rows,
oversized rows, or unavailable staging.

---------

Co-authored-by: Jon Staples <250234834+Captain-Tripps@users.noreply.github.com>
2026-10-26 16:12:25 +02:00
KyleHagy 5320c87ff0 sycl : bound reorder temp memory to 32 MiB (#31062) 2026-10-26 16:11:57 +02:00
Pascal 74b104c9ba common: pin model resolution on real HF repo layouts (#30548)
* tests: pin model resolution on real repo listings

Extend test-model-resolution with 63 repo listings captured verbatim
from the HF API, chosen to cover every file naming pattern found across
1237 GGUF repos of the major publishers and pruned to whole quant groups.
Each repo pins its resolved plans and its cache listing.

The expectations follow what each file really is per its GGUF header,
not what the current resolver picks, so a mismatch is a resolution bug.
Mismatches are collected and reported together before the test fails.

* tests: leave the listing unchecked on paths the filesystem refuses

Some real split names (DevQuasar's Model.Q2_K.gguf-00001-of-00012.gguf
under a quant folder) exceed MAX_PATH once materialized in the temporary
cache on Windows, so the listing of such a repo is skipped with a note
while its plans stay checked.

* tests: pin real listings to master on GGUF convention names only

Replace the real repo table with 171 HF listings whose file names follow
the GGUF naming convention (lowercase sidecar prefix, - delimited
components, quant last), one per layout pattern found across 1174 GGUF
repos and pruned to whole quant groups. Repos outside the convention are
dropped. Each repo pins the plans and the cache listing master gives
today, so the test is a pure harness: any change in resolution shows up
here, and mismatches are reported together.
2026-10-26 14:18:29 +01:00
Xuan-Son Nguyen c6fb3b5be2 mtmd: add mtmd_helper_get_capabilities (#31183)
* mtmd: add mtmd_helper_get_capabilities

* cap audio_stream

* add back old api for compat

* audio_gen_spk_ref, text_gen
2026-10-26 12:45:35 +01:00
Sigbjørn Skjæret 8b28c23943 ggml-cpu : fix unused variables in arm arch (#31180) 2026-10-26 11:38:55 +01:00
Georgi Gerganov fd8f8451a2 tests : refactor test-llama-archs to not link internal libllama symbols (#30880)
test-llama-archs included src/llama-arch.h and src/llama-model-saver.h,
which prevented building on Windows with shared libraries since the
used symbols are not exported from the DLL.

- declare llama_arch_* wrappers around the internal llm_ arch query
  functions in the llama-ext.h staging header, together with
  llama_arch_kv_name() and llama_arch_supports_save()
- slim llama_model_saver down to the private implementation of
  llama_model_save_to_file(): the fixture-writer half used by the test
  moves into tests/test-llama-archs.cpp as a local kv_writer
- replace the roundtrip saver usage with the public
  llama_model_save_to_file() writing to a temp path (also removes the
  tmpfile() fd leak and its Windows workaround)
- move test-llama-archs, test-generate-models, test-llama-context and
  test-fusion out of the NOT WIN32 OR NOT BUILD_SHARED_LIBS guard

Assisted-by: pi:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))
2026-10-26 12:16:02 +02:00
Evan Huus 9125a2ebe8 ci : fix race in webgpu memset (#31162) 2026-10-26 18:02:12 +09:00
feni6andfeni6 9b8446b914 metal : stop overflow from happening in large matrix output offsets (#30072)
* Metal : stop overflow from happening in large matrix output offsets

Assisted-by: Codex

* metal : widen batch offsets and MUL_MAT_ID token indices

Assisted-by: Codex

---------

Co-authored-by: feni6 <270037094+feni6@users.noreply.github.com>
2026-10-26 10:13:39 +02:00
Aleksander Grygier dc9e3bed42 ui: UX improvements (#31030)
* ui : land opened conversations at their bottom before the first paint

Pin the page to its bottom with a ResizeObserver until it stops resizing or the reader interacts, and mount the last rows eagerly so the landing frame shows real content.

Assisted by: Claude (Opus 5.5 Medium)

* ui : make conversation tabs browser-style

A plain navigation shows the chat in the current tab instead of adding one, and the new-chat tab turns into the conversation it starts. New tabs come only from explicit actions: the + button, Cmd/Ctrl-click or middle-click on a sidebar chat, and its Open in new tab menu item, which open in the background. Closing a tab moves to its neighbour among the tabs the view shows.

Assisted by: Claude (Opus 5.5 Medium)

* ui : share the tab surface and tab bar fade styles

Move the tab item surface (shape, resting and active looks) to TAB_SURFACE_* class constants and the fade under the tab bar to a global chat-top-fade class, so other elements can match the tabs.

Assisted by: Claude (Opus 5.5 Medium)

* ui : fit the chat column below the tab bar

The tab bar reported 1.25rem of its height while it takes 3.3125rem (pt-3.25 + h-10), so a short conversation still left a few pixels to scroll. Use the bar's full height and drop the extra 1rem from the column's minimum height, so it fills the viewport exactly; the centered new-chat form moves up by the same 1rem to keep its place.

Assisted by: Claude (Opus 5.5 Medium)

* ui : stop the page from bouncing past its ends

Elastic overscroll on the root scroller dragged the sticky and fixed bars (tab bar, chat form) along at the top and bottom of a chat, and they jumped back on release. Set overscroll-behavior: none on html and body.

Assisted by: Claude (Opus 5.5 Medium)

* ui : stop scrolled dialogs from bouncing past their ends

Dialogs scroll on their own (the settings panel, the search results), so the page rule did not reach them and their sticky navigation still jumped on overscroll. Turn off overscroll on dialog panels and on the scroll containers inside them. Only scroll containers: Firefox counts an overflow-hidden box such as truncated text as one, and `none` there kept the wheel from scrolling the list around it.

Assisted by: Claude (Opus 5.5 Medium)

* ui : fit a first exchange opened by the working directory row

The last answer's minimum height leaves room for the last question only, so the synthetic row that a new chat starts with (Set working directory to ...) made a short first exchange overflow by that row. When everything above the last question is synthetic, count those rows as part of it.

Assisted by: Claude (Opus 5.5 Medium)

* ui : keep following a streaming response until the reader scrolls up

Auto-scroll stopped whenever the page moved up, including moves the
reader did not make: scroll anchoring and blocks re-rendering shorter
while a response streams. Only the reader's wheel, touch, scroll keys
or scrollbar drag now count as scrolling up.

The send scroll also looked up the new user bubble by position, which
picked an older message when the answer row had not rendered yet.

Assisted by: Claude (Opus 5.5 Medium)

* ui : fade the chat form between its centered and bottom positions

The form no longer slides across the column between the new-chat center
and the bottom. It fades out drifting toward where it goes and fades
back in where it lands, quicker when opening a chat than when going back
to a new one. Its wrapper keeps the same height in both positions, so
the messages never shift while it moves.

The new-chat greeting fades in as the form settles, also on a refresh or
a first visit. Reduced motion keeps only the fades.

Assisted by: Claude (Opus 5.5 Medium)
2026-10-26 10:13:08 +02:00
Aleksander Grygier 69b3cc406e ui: Sidebar resizing improvements (#31119)
* ui : let the expanded sidebar be resized by dragging its edge

Bring back the desktop sidebar resize handle that was dropped with the
shadcn sidebar primitive. The width is clamped between 18rem and 32rem,
persists across reloads, and resets on a double-click of the handle. The
tab bar follows the same width so a wider sidebar never covers it.

Assisted by: Claude (Opus 5.5 Medium)

* ui : rubber-band the sidebar edge past its bounds and drag it shut

Past the max width the edge resists harder the further it is pulled, and
the same happens below the min width until the pull passes 9rem, where it
snaps to the pointer and letting go hides the sidebar. A release outside
the bounds springs the width back to the nearest one.

Assisted by: Claude (Opus 5.5 Medium)

* ui : show the sidebar edge strip faintly on hover and solid while dragging

The strip is clipped to the sidebar's rounded corners through its own
overlay, so the sidebar itself is never clipped.

Assisted by: Claude (Opus 5.5 Medium)

* ui : remember whether the desktop sidebar is open across reloads

A phone opens the sidebar as an overlay, so it always starts closed there
and its toggling never overwrites the desktop preference.

Assisted by: Claude (Opus 5.5 Medium)

* ui : fade the sidebar labels and conversation list with its width

The fade follows the live width, so it tracks both the toggle's width
transition and a drag below the minimum width.

Assisted by: Claude (Opus 5.5 Medium)

* ui : persist the sidebar width on release and clamp a restored one

A drag now updates only the live width and writes it to localStorage once
when released. A width restored from localStorage is clamped to the
current bounds, so a value saved under older bounds does not stick.

Assisted-by: Claude (Opus 5.5 Medium)
2026-10-26 10:07:25 +02:00
Georgi Gerganov 224d94c02b ci : bump server timeout during startup (#31174) 2026-10-26 09:59:15 +02:00
Georgi Gerganov 48c36110ab ci : drop the macos-latest-x64 job (#31175) 2026-10-26 09:56:19 +02:00
Aman GuptaandGeorgi Gerganov a074215352 deepseekv4.1: follow-up review fixes (#31137)
* kv-cache : use ggml_backend_tensor_copy to refill the DSV4 K mirrors

* llama : move the Engram pad id to llama_hparams and the token map to llama_model

The saver no longer needs the deepseek41 model class.

* deepseek41 : drop the F32 src1 request on the HC mixes matmul

* ggml : add softplus TODOs in the backends that still use log(1 + exp(x))

* convert : make the DSV4/DSV4.1 modify_tensors generators, drop the redundant vision filter

* deepseek41 : clean up comments

* deepseek41 : drop the F32 src1 requests on the router and indexer matmuls

* convert : use format_tensor_name directly in DeepseekV4Model

* s/comp_kv_src/comp_il_src/g

* cont : fix TODOs for log1p

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-26 15:52:02 +08:00
Adrien Gallouët d854d98bae cuda : use std::string instead of VLA for archName (#30463)
* cuda : use std::string instead of VLA for archName

Modernize the code (old C style) and fix the following warnings:

```
ggml/src/ggml-cuda/ggml-cuda.cu:180:19: warning: variable length arrays in C++ are a Clang extension [-Wvla-cxx-extension]
  180 |     char archName[archLen + 1];
      |                   ^~~~~~~~~~~
ggml/src/ggml-cuda/ggml-cuda.cu:180:19: note: read of non-const variable 'archLen' is not allowed in a constant expression
ggml/src/ggml-cuda/ggml-cuda.cu:179:9: note: declared here
  179 |     int archLen = strlen(devName);
      |         ^
```

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* Add a comment about archName

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

---------

Signed-off-by: Adrien Gallouët <angt@huggingface.co>
Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>
2026-10-26 00:08:22 +01:00
48c9f08b7b common, grammar: add recursive depth limit, make it a requirement (#31153)
* common, grammar: add recursive depth limit, make it a requirement

* catch more recursive functions

* common : tidy up peg parser changes

* grammar : reject candidates in a loop, without a depth limit

The depth limit in llama_grammar_reject_candidates_for_stack counted
the code points of a vocab token, so any token of 256 chars or more
was always rejected under a grammar, such as the 512-space token of
GLM-4.7-Flash or the long space tokens of Laguna-S-2.1.

The rejection now walks the code points with its own stack of levels
instead of recursing: there is no limit and no stack risk, whatever
the length of a token from the GGUF, and the masks match master.

* grammar : track rejected candidates as indices into the input

At a given code point a candidate only differs from its input by the
position of its code point pointer, so the levels now carry indices
into the input candidates instead of copies. The first level needs no
special case and the rejects map back to the input directly.

Grammar sampling gets faster than master on GLM-4.7, Laguna, Qwen3.6
and gemma-4 with the json, list and spaces grammars, same masks.

* nit : shorten the comment of llama_grammar_reject_step

* common : add depth_guard for schema walks

* grammar : move GRAMMAR_RECURSIVE_LIMIT into the .cpp

* common : reduce recursion limit for peg parser

* improve

---------

Co-authored-by: Alde Rojas <hello@alde.dev>
Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-25 23:53:08 +01:00
Georgi Gerganov 6050b467c0 tests : adjust default threads for test-llama-context (#31157) 2026-10-25 19:46:44 +02:00
Aman Gupta 2280f0fd16 qwen: merge tensors on convert (#31028)
* qwen: merge tensors on convert

* convert: use generator functions

* qwen4exp: remove conts and pre-scale
2026-10-25 15:23:22 +02:00
Masashi Yoshimura 1a0b56cec7 ci : disable test-llama-context with fa off in the webgpu (#30977) 2026-10-25 13:53:55 +02:00
Trunk Trick 63412d8737 llama: fix exhausting CPU and MEM caused by dry sequence breaker (#31035) 2026-10-25 12:31:09 +01:00
Anthony Wang 5c965fd8ca llama-quant : fix 4D tensor quantization using ggml_nrows() and name-based expert detection (#30820)
* llama-quant : fix quantizing 4D tensors

nrows_total was ne[1]*ne[2], so for a 4D tensor only the first ne[2]
slices were quantized and the rest of the rows were dropped. Use
ggml_nrows(tensor) to count all rows, including ne[3].

Fixes #29916

* llama-quant : detect expert tensors by name and fix imatrix size for 4D tensors

The MXFP4_MOE path used ne[2] > 1 to detect MoE expert tensors. This is
wrong for non-expert tensors with 3 or more dims, e.g. MLA projections
(BailingMoE3) and 4D conv kernels. Use the tensor name instead: all
tensors used by GGML_OP_MUL_MAT_ID are named *_exps or *_chexps. This
also removes the BailingMoE3 special case.

The imatrix holds one vector of ne[0] values per 2D slice, so the
expected size is ne[0]*ne[2]*ne[3], not ne[0]*ne[2]. This matches how
the imatrix is indexed per row for 4D tensors in llama_tensor_quantize_impl.

Assisted-by: Claude Opus 5.5
2026-10-25 13:28:38 +02:00
Hrushikesh Ramilla f1168ac417 grammar: limit recursion depth to prevent stack overflow (#29495) (#29755) 2026-10-25 12:10:10 +01:00
Georgi Gerganov fa3645feb5 llama : respect no_alloc in all memory caches (#31087)
* llama : respect no_alloc in memory caches

Assisted-by: dsh:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* llama : TODO: move no_alloc out of llama_hparams

Assisted-by: dsh:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* llama : TODO: move no_alloc out of llama_hparams

Assisted-by: dsh:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)
2026-10-25 11:56:04 +02:00
ShreyaandShreya Agrawal 7790a9231d tools : reject truncated GGUF tensor data in merge (#31044)
Co-authored-by: Shreya Agrawal <shreyaagrawal@Shreyas-MacBook-Air.local>
2026-10-25 11:25:10 +02:00
anujj c49edf0963 CUDA: fuse FFN gate/up matmuls and GLU in MMQ with interleaved gate/up rows (#29948)
* cuda: fuse FFN gate/up and GLU in MMQ with interleaved gate/up rows

For prompt processing, run the gate and up matmuls of a dense FFN as one
mul_mat_q launch and apply the GLU in the write-back.

- The loaders take the gate weights as a second pointer and fill the weight
  tile with up and gate rows interleaved in chunks of 8, so each thread holds
  an up value and the matching gate value (rows r and r + 8 of an MMA tile).
- The MMA loop is unchanged: same tile, warp layout, vec_dot and one sum[]
  per thread. The fused write-back computes the GLU from registers and writes
  half the rows.
- The fused path uses the regular tiling (no stream-k) with the column tiles
  innermost so that neighbouring blocks share weight tiles.
- Enabled for Q4_K, Q5_K, Q6_K, Q8_0 and IQ4_XS on NVIDIA Turing and newer.

* cuda: assert the fused MMQ gate argument and trim redundant comments

* cuda: move the fused MMQ hint check to should_fuse and add TODOs

* cuda: pass prec_src1 to the fused MMQ write-back

* cuda: fuse only with efficient tiling and no fallback, merge the GLU write-back

* cuda: require has_fusion explicitly, drop the fusion-only src1 precision check

* cuda: launch the fused MMQ kernel through the stream-k kernel with one block per tile

* cuda: inline the MMQ tiling-efficiency check

* tests: one fused gate/up MMQ case per type and GLU op

* cuda: always launch the fused MMQ kernel with one block per tile

* tests: shorten the fused gate/up MMQ test comment

* cuda: select the fused MMQ loader and write-back in ggml_cuda_mmq_get_util_funcs

* cuda: run the fused MMQ kernel on the plain tiling path

* cuda: put the column tiles innermost in the MMQ plain tiling path
2026-10-25 11:24:38 +02:00
Aman Gupta a55c16e4ee ggml-cuda: fuse qwen MRoPE + set-rows (#31025) 2026-10-25 11:15:45 +02:00
Jeff Bolz e321bdad7e vulkan: Add a larger tile size for conv3d (coopmat2 only) (#29920)
* vulkan: Add a larger tile size for conv3d (coopmat2 only)

This adds a larger tile size, in practice it is only for coopmat2 where
we don't need shared memory to store the output (would use more than 64KB).

Add some additional representative backend tests. At least, this is what codex
came up with from looking at sd.cpp Wan VAE.

* only apply large tile size for newer driver versions
2026-10-25 11:13:48 +02:00
Ian McKellar 459a1ba549 vulkan : one warp tile per subgroup in the small matmul tiles (#30106)
WM of the small tiles was 32, or 8 for 8-wide subgroups, whatever the
tile's BLOCK_SIZE and WARP. With 16-wide subgroups (Mali) the second
subgroup of a workgroup wrote into the next tile's columns. With 8-wide
subgroups some _id tiles left rows uncomputed. Derive WM so there is one
warp tile per subgroup.

Tested on my Pixel 9 Android phone. Before the change there were 20
MUL_MAT and 6 MUL_MAT_ID failures. After the change they all pass.

Fixes #28637

Assisted-by: Claude Opus 5.5
2026-10-25 11:13:14 +02:00
Max Krasnyansky f0fb5984b0 hexagon: generic dims collapse/reshape for unary, binary and matmul ops (#31071)
* hex-tools: support for --compare option in the profile.py

* hex-unary: improve collapse logic to also do splits if nrows is small

* hex-colapse: handle all colapsing at the host and make it generic

* hex-colapse: remove leftover unary helper
2026-10-25 11:11:48 +02:00
lhez 9a4580b61c opencl: add topk and lightning indexer (#31026)
* opencl: add topk

* opencl: optimize topk

* opencl: add fp lightning indexer

* opencl: optimize lightning indexer

* opencl: refactor

* opencl: refactor tiled topk kernel

* opencl: adjust format
2026-10-25 11:11:15 +02:00
lhezandHongqiang Wang b083c384f7 opencl: add initial q2_k support (#30999)
* opencl: add `mul_mv_q2_k_f32`

* opencl: add q2_k SOA mv and tiled mm

* opencl: add q2_k noshuffle gemm/gemv for adreno

* opencl: add q2_k dp4a gemm for adreno

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: tune q2_k kernels for adreno

---------

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>
2026-10-25 11:09:56 +02:00
Georgi Gerganov 344be0a600 ci : update CPU job assignments (#31136) 2026-10-25 10:35:11 +02:00
Yuri KhrustalevandXuan Son Nguyen b3fa69955e chat : reject JSON nested more than 128 levels before walking it (#30242)
* chat : reject JSON nested more than 128 levels before walking it

The schema builder, the jinja engine and the decision code walk request
JSON with recursion. A request nested deep enough overflows the stack of
the HTTP thread and llama-server dies (SIGBUS on macOS, SIGSEGV on Linux):
for a tool schema from a few hundred levels on macOS and about 10000 on
Linux.

Check the depth first and answer 400 past 128 levels: the chat inputs in
common_chat_templates_apply (tools, tool-call arguments,
chat_template_kwargs, json_schema), the json_schema of /completion, and the
whole /v1/systemone body. A gemma4 tool result that is too deep stays text.

* fix the root, not the call side

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-25 06:46:35 +01:00
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
SreeramandJohannes Gäßler 9ff0d7e90f CUDA: fix build after chunked CUDA graphs (#30804) (#31053)
* CUDA: fix build after chunked CUDA graphs (#30804)

* Apply suggestion from @JohannesGaessler

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* Fix newlines in common.cuh

---------

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
2026-10-23 20:47:20 +02:00
Christian Loehnert 06230cb877 allows the usage of 4-10 gpus for stepfun and laguna in tensor parallel (#24554) 2026-10-23 19:56:38 +02:00
Alexey Kopytko 5c31142682 qwen4exp : build the QSA selection without the kq_mask (#30965)
* qwen4exp : build the QSA selection without the kq_mask

* clean

* lang

* qwen4exp : reshape the fused QSA selection to the kq_mask streams

ggml_top_k_mask with a single keep row returns [n_kv, n_tokens], while
build_attn_qsa expects the kq_mask layout [n_kv, n_tokens/n_stream, 1, n_stream].
With several streams the shape assert failed at graph reserve.

* test the QSA optimization

* reword

* update build_fused_top_k_mask instead of handwiring

* simplify

* keep il at the end for consistency
2026-10-23 19:49:08 +02:00
Adrien Gallouët da2ee1e7a6 vendor : update LibreSSL to 4.4.0 [no ci] (#31049)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
2026-10-23 19:12:35 +02:00
Aman Gupta 280141e6d4 CUDA: chunk graphs to amortize graph launch (#30804) 2026-10-24 00:26:16 +08:00
Max Krasnyansky 36e36e63a5 hexagon: add CEIL op (#31006)
* hex-ceil: add CEIL(f32) op

* hex-ceil: add f16 version

* hex-ops: update supported ops

* hex-ops: update docs with supported ops
2026-10-23 07:46:53 -07:00
Aleksander Grygier e1e3b2c409 ui : bump packages to clear npm audit (#31038)
Run npm audit fix, then bump the remaining vulnerable packages within
their current majors: storybook 10.6.2, vitest 4.1.12, vite 7.3.7,
@modelcontextprotocol/sdk 1.32.1, dompurify 3.4.17, mermaid 11.17.2,
@vite-pwa/assets-generator 1.0.4 and @tailwindcss/typography 0.5.20.

Override katex to 0.18.11 and postcss-selector-parser to 7.1.6 since
their dependents still pin vulnerable ranges, bump the sharp override
to 0.35.5, and pin @storybook/svelte so npm does not resolve the 11.0
alpha through the addon-svelte-csf peer range.

Storybook stays on 10.6.2 as 10.6.3 is newer than min-release-age.

Assisted by: Claude (Opus 5.5 Medium)
2026-10-23 16:33:13 +02:00
Kartik Gulia 4231b23dc2 ggml : fix time overflow in ggml_time_us (#30969)
* fix time overflow

* use double
2026-10-23 15:56:59 +03:00
Sigbjørn Skjæret f00c9083b2 ci : add behavior check workflow (#31036)
* remove python from tests

* add behavior and backend ctest labels

* add behavior check workflow

[no ci]
2026-10-23 14:53:44 +02:00
Raul Torres f5027fb213 CANN: missing const qualifiers added for consistency (#30933) 2026-10-23 15:34:20 +08:00
Titaniumtown f64a49819c sycl : pass device into mul_mat_uses_onednn (#31014) 2026-10-23 08:37:53 +02:00
Pranesh GonegandlaandPranesh Gonegandla 1ab09d632e Extend probabilistic sampling for DFlash2 drafts (#30663)
* spec : add probabilistic sampling for DFlash2 drafts

* spec : apply review suggestions

---------

Co-authored-by: Pranesh Gonegandla <pgonegandla@nvidia.com>
2026-10-23 11:15:36 +05:30
f9599d0cc8 vocab : fix Nemotron-3.5-Embed tokenization (#30974)
* vocab : fix Nemotron-3.5-Embed tokenization

* Update src/llama-vocab.h

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

* rename

* Update src/llama-vocab.cpp

Co-authored-by: Daniel Bevenius <daniel.bevenius@gmail.com>

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
Co-authored-by: Daniel Bevenius <daniel.bevenius@gmail.com>
2026-10-23 07:31:30 +02:00
Max Krasnyansky 643b0cdacb hexagon: Op Fusion refactoring, optimizations and new fusions like BINARY_NX (back-to-back ADDs, etc) (#30843)
* hex-fusion: support for GELU+MUL, REPEAT+GET_ROWS and BINARY_NX (back-to-back ADD/MUL/...)

* hex-fusion: factor out fusion logic into separate .h and .cpp and update all related code

* hex-fusion: allow for RMS_NORM+MUL fusions for non-contig tensors

* hex-fusion: streamline fusion eligibility checks and cover missing corner cases

* hex-fusion: streamline fusion flow

* hex-opbatch: make n_tensors limit more scalable

* hex-bufs: adjust the trace buff sizing

* hex-glu: cleanup glu-ops interfaces and add kernel_params

* hex-kparams: clean up kparams handling in unary, ssm-conv, and other ops

* hex-kparams: update the rest of the ops PAD,DIAG,... to use kparams

* hex-unary: add support for collapsing dims

* hex-ops: cleanup redundant runtime checks

* hex-ops: consistent use of kernel_type in all kparams

* hex-fusion: remove redundant check in gdn+cpy

* hex-fusion: further streamlining of fusion rules and code

* hex-fusion: cleanup naming

* hex-opfmt: cleanup op formatting

* hex-fusion: bump fusion lookahead to 64

* hex-fusion: fuse SSM_CONV+SILU

* hex-fusion: fix hvx register spills in fused ssm_conv+silu

* hex-fusion: add more GLU fusions

* hex-fusion: remove duplicate checks and use ggml-common helpers

* hex-kparams: refactor kparams into htp-kparams.h/cpp

* hex-fusion: fix binary-nx fusion regression

* hex-kparams: another pass at cleaning up kparams (removing unused, etc)

* hex-kparams: streamling nameing and move _check_ wrappers into .h

* hex-kparams: templetize htp_kparams_check_<op> to reduce code bloat

* hex-ops: de-duplicate op dispatch switches

* hex-kparams: trim down exposed kparams calls

* hex-kparams: simplify type casting of kparams structs

* hex-fence: de-dup fence tensor construction

* hex-opbatch: simplify tensor shape init/checks

* hex-mm: cleanup leftovers of the original QK defines

* hex-fa: fix stack overflow on older npu firmware

* hex-ctx: move dma_caches into ops_ctx
2026-10-22 21:05:44 -07:00
Todd Malsbary 0a9ef00678 sycl: Replace host waits in fattn-mkl with explicit dependencies (#30684)
Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>
2026-10-22 22:43:12 -04:00
Todd Malsbary 7377bada8c sycl: Add graph record and replay (#28725)
Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>
2026-10-22 22:39:52 -04:00
pzhu 184f653602 server: Substitute rerank placeholder from the original template only. (#30864) 2026-10-23 00:38:03 +02:00
Xuan-Son NguyenandPascal d814cbb88c server: realtime API: send complete after committed audio drains (#30994)
* server: realtime API: send complete after committed audio drains

* server: realtime API: complete a commit at its end row

Completion is triggered when the stream runs dry, which is a timing
proxy: audio appended while the tail drains leaks into the committed
item, a final commit before the drain merges both items, and back to
back commits collapse into one.

mtmd_audio_stream_flush() records the row count at the end of the
flushed audio (mtmd_audio_stream_get_n_rows_flushed), next to the rows
produced so far (mtmd_audio_stream_get_n_rows). The slot keeps a FIFO
of these end rows and completes each commit once every row up to it is
decoded.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-23 00:37:47 +02:00
kurquharandLi He 970b51ce7c opencl: add depthwise conv2d (#30900)
* opencl: add Adreno depthwise conv2d

Assisted-by: OpenCode

* opencl: use local program for depthwise conv2d

Assisted-by: OpenCode

* opencl: use singed type for args

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
2026-10-22 10:54:24 -07:00
tc-mb a08ab75ae7 mtmd : add MiniCPM-OCR 1.0 support (#30979)
Signed-off-by: tc-mb <tianchi_cai@icloud.com>
2026-10-22 19:22:15 +02:00
Martin Kleinberger 19ab5a5e65 clef : include <algorithm> for std::find and std::copy (#30981)
[no ci]
2026-10-22 18:14:47 +02:00
Gaurav Garg 36cbf415f1 Fix !30793 2026-10-22 19:30:57 +05:30
Georgi Gerganov 5c19a5a756 glm5 : optimize inputs (#30932)
* glm5 : avoid sel_mask_2d input

* cont : avoid new_pool_flat input
2026-10-22 16:59:29 +03:00
Georgi Gerganov d9ee007a06 model : fix qwen4exp layer inputs for DFlash drafters (#30927)
qwen4exp published the hc-wide residual [n_embd, hc, n_tokens] as
t_layer_inp, but the drafter reads layer inputs as [n_embd, n_tokens]
rows: llama_context sizes each embd_layer_inp buffer as n_embd * n_batch
and the DFlash encoder input is n_extract * n_embd wide. Evaluating a
DFlash draft over a qwen4exp target therefore aborts on the first request
in llama_context::extract_layer_inputs.

Collapse the streams with a plain mean before exposing them, as deepseek4
already does for its hyper-connection residual, and build the mean only
for the layers a drafter actually requested.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-22 16:49:38 +03:00
budiony d81d0d8fb9 Multi-column on Intel XMX matrix engines refactored (#29864)
* Multi-column on Intel XMX matrix engines refactored

Multi-column (2 to 80 activation columns) mul_mat_vec_q for reordered K-quant and Q8_0 weights on the Intel XMX matrix engines (int8 DPAS, ESIMD), for the SYCL backend. It targets speculative decoding verification (2 to 8 tokens per step) and short prompt batches (9 to 80 tokens). Three types also use it for a single column (Q2_K, Q3_K, Q8_0).

* Fixed regression bug

real bug found: the first version produced NaN whenever the row count wasn't a multiple of 4

* Removed GGML_TYPE_Q8_0 activation

* rewritten xmx_mul_mat

64-byte alignment and 16-byte pitch check on the activations

* Implemented fused gate/up kernel

One launch computes up * act(gate) for both FFN weights, reading the activations once and applying SWIGLU or GEGLU in the epilogue. It covers up to 16 columns and works for all six XMX types.

* Removed the temporary XMX_NOGLU environment switch

* Q2_K min-sum optimization

Q2_K weight is d·scl·q − dmin·mc, where scl (the 4-bit scale) and mc (the 4-bit min code) apply to each half of a 32-weight group

* Added dispatch check and GRF sweep

tg128 on Q4_K_M goes from 26.67 to 28.23 tok/s on the B70 (+5.9%).
Q6_K is unchanged.
MUL_MAT passes 1727/1727 and MUL_MAT_VEC_FUSION 1361/1361.
Perplexity at batch 1 is 5.2064 with XMX and 5.2058 without.

* Renamed variables

* Added 2 environment variables

GGML_SYCL_XMX_MAX_COLS (default 80, any value 0 or higher) and GGML_SYCL_XMX_GLU_MAX_COLS (default 16, range 0 to 16):  the largest batch for the fused gate/up kernel. It is clamped to 16 because that is the most the fused kernel is compiled for, so the variable can only lower it.

* Update esimd.hpp

1. All five quant structs were repeating the same boilerplate, so I moved it into shared pieces.

2. A new helper xmx_term does one DPAS, converts it to float and scales it per row. The four branches are now one or two lines each, and the shared Cb += z * d8r is written once.

The refactor passes all three op tests. MUL_MAT_VEC_FUSION is 1369/1369, MUL_MAT is 2281/2281 (the count includes your uncommitted f32 tests) and MUL_MAT_ID is 937/937.
2026-10-22 21:39:01 +08:00
Bill Sideris d77256999f vulkan: replace std::aligned_alloc with posix_memalign for termux compatibility (#30828)
std::aligned_alloc is unavailable on Android bionic below API 28 (Termux targets API 24); use posix_memalign like ggml.c does.
2026-10-22 14:40:13 +02:00
martin-cimminoandMartin Cimmino 0693dbfb91 nemotron : support non-standard GQA, BPE tokenizer and domyn-small pre-type (#23343)
* nemotron : support non-standard GQA, BPE tokenizer and domyn-small pre-type

* gguf-py : write Metaspace prepend_scheme as GGUF metadata

* vocab : parse metaspace prepend_scheme into enum, rename key

* vocab : use metaspace prepend_scheme in detokenize, add TODOs for other tokenizers

* vocab : use constant for metaspace escaped space

---------

Co-authored-by: Martin Cimmino <martin.cimmino@domyn.com>
2026-10-22 13:34:47 +02:00
Tim Wangandtimothywang21 a5d22276be server : support typed content input for RERANK endpoint (#29625)
* server : support typed content input for rerank endpoint

Assisted-by: Opencode/Qwen3.8 27B

* server : address rerank multimodal review findings

- reject multimodal rerank input when the model has no rerank chat template (images were silently dropped)
- translate handle_media download/base64 failures to 400 instead of 500
- reject negative top_n with 400 instead of letting resize throw
- fix comment typos, redundant comments, indentation and trailing whitespace
- README: document that TEI input accepts the same shape as Jina; drop stray blank line

Assisted-by: opencode

* Trimmed README

---------

Co-authored-by: timothywang21 <timothywang21@users.noreply.github.com>
2026-10-22 13:27:24 +02:00
Georgi Gerganov 5f5acfe250 ci : disable slow WebGPU tests (#30963) 2026-10-22 13:56:20 +03:00
Georgi Gerganov 1f4c2eeaa5 ci : be able to reconfigure HTTP port for server pytest workers (#30972) 2026-10-22 13:56:03 +03:00
Michael Taylor 7170cad4d0 sycl: fix CONV_2D wrong results for non-contiguous input (#30363)
* sycl: fix CONV_2D wrong results for non-contiguous input

The kernel indexes the `input` as a contiguous [W, H, C, N] tensor, but
supports_op claimed the op for any layout. A non-contiguous (e.g. `CWHN`
permuted) input was read with the wrong element offsets, silently
producing wrong results.

Gate the CONV_2D op on `input` and `kernel` contiguity, matching the
CUDA backend contract, and assert input contiguity in the op.

`test-backend-ops -b SYCL0 -o CONV_2D`: goes from 16 failures to 0,
none.

```text
  2006/2006 tests passed
  Backend SYCL0: OK
Backend 2/2: CPU
  Skipping
2/2 backends passed
OK
```

Assisted-by: pi (unsloth/Qwen3.8-27B-GGUF:Q4_K_M via llama.cpp)

* sycl : CONV_2D cwhn=1 support

Make the SYCL backend compute `GGML_OP_CONV_2D` correctly for `cwhn=1` (CWHN
channel-order) inputs and kernels, so the previously failing test-
backend-ops CONV_2D cases pass on SYCL instead of being skipped or
miscomputed.

- Implement CWHN layout support in `ggml/src/ggml-sycl/conv2d.cpp`
  (`ggml_sycl_op_conv2d`).
- Update SYCL `supports_op` for `GGML_OP_CONV_2D` in
  `ggml/src/ggml-sycl/ggml-sycl.cpp` to accept both layouts.

Assisted-by: pi (Greatjedi/Qwen3.8-27B-Intel-Arc-Tuned-GGUF via llama.cpp)

* sycl docs : update ops for SYCL
2026-10-22 16:19:58 +08:00
tkran2 d88040c704 vocab : add clean_spaces GGUF key (#30940) 2026-10-22 09:58:08 +02:00
tkran2 e197cef455 vocab : use Tekken pre-tokenizer for pixtral rather than the Llama 3 one (#30960) 2026-10-22 09:06:47 +02:00
Gabe Goodhart bf039c8496 models : Record layer inputs for all granite models (#30957)
Branch: GraniteSpeculatorSupport
AI-usage: full (Claude + Sonnet 5.5)
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>
2026-10-22 10:02:41 +03:00
Charles Xu dfeea52005 kleidiai : add Q8_0 SVE dotprod GEMV and SVE i8mm GEMM kernels (#30808) 2026-10-22 09:56:21 +03:00
Titaniumtown e3797b184d sycl: merge the fp16 and fp32 dequant switches (#30740) 2026-10-22 08:30:21 +03:00
Chanho Lee 0b1281aeef webgpu : add Q2_0 support (#30914)
* webgpu : add Q2_0 row dequantization

* webgpu : add Q2_0 matrix multiplication
2026-10-22 08:30:04 +03:00
Hrishith Thadicherla 4912ac24f1 dflash : load optional FC input scale (#30945)
Assisted-by: Codex
2026-10-22 08:24:12 +03:00
Aleksander Grygier 84746c5c94 ui: Add "Show tool call in progress" setting (#30681)
* feat(ui): add Show tool call in progress display setting

Add the showToolCallInProgress key and its checkbox to Settings > Display,
right next to its "always show" counterpart. Upstream dropped a setting of
this exact name in 43b5e63589, folding it into alwaysShowToolCallContent
because the old name promised expansion that outlives the call; this brings
back the narrower, in-progress-only meaning.

The entry is registry-driven, so SETTING_CONFIG_DEFAULT, the dialog field and
the ui_settings admin baseline pick it up without extra wiring. It ships
enabled, mirroring Show thought in progress, and the always-show help text is
reworded so the two toggles read as a pair rather than contradicting each
other.

Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)

* feat(ui): expand tool call blocks while they are in progress

Drive the default expansion of TOOL_CALL_STREAMING and TOOL_CALL_PENDING from
showToolCallInProgress and leave TOOL_CALL, the terminal state, to
alwaysShowToolCallContent. A finished call now folds back on its own once its
result lands, which is exactly how the reasoning block behaves when the
thought completes.

Manual toggling keeps precedence over both defaults in either direction:
isExpanded() only consults the settings while expandedStates holds no entry
for the section.

Client tests pin the truth table for the two toggles, including the automatic
fold-back and both manual overrides.

Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)

* fix(ui): keep running tool calls expanded

A call the agentic loop reports as running now outranks its section type.
exec_shell_command persists an empty result before reading any output, so a
live call is already TOOL_CALL and folded back, which hid the streaming
output the setting is meant to show.

Move the expansion default to isSectionExpandedByDefault so the running-id
rule is covered by a truth table, and test the pending -> finished transition
instead of only the finished state: a block that only ever got mounted
finished cannot prove it folds back on its own.

Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)

* feat(ui): stream the exec command from the first character

The exec parser read its args strictly, so while the model was still
typing the call the block title stayed empty and the body claimed
"Running...". Parse the args partially (the read_file / edit_file /
write_file pattern), keep a terminal cursor while the args JSON is
incomplete, and stand the conversation cwd in for the x-tool-cwd that
only arrives with the result message, so the prompt is there from the
first character. The body stays empty while the args stream, shows
"Executing command..." once the call is executing, and the wrapper
drops its padding for empty phases so the title sits flush with the
frame.

Assisted by: Delta (zai-org/GLM-5.3)

* fix(ui): stream file tool bodies even before a path arrives

write_file and edit_file refused to render until their args carried a
path, so a call that streams its body first (or a same-named tool with
other arg names) showed an empty block. Meta now carries optional
fileName/filePath, path alias reading moves to shared helpers, and the
blocks fall back to the raw args JSON when no known content key is
present.

Assisted by: Delta (zai-org/GLM-5.3)

* fix(ui): keep an empty write_file body instead of the raw args

A call carrying {"content":""} fell through to the raw-args fallback, so
a finished write of an empty file showed the args JSON. The parser now
leaves content undefined when the key is missing and the block tests
!== undefined, so an empty write renders as an empty code block with its
byte count.

Assisted by: DeepSeek-V4.1-Flash (deepseek-ai)
2026-10-22 08:14:47 +03:00
Hoang Vien Duy 0682a576a7 lora : support prism.hadamard folded weights (#30910)
* lora : support prism.hadamard folded weights

* tests : remove LoRA test from test-llama-archs
2026-10-22 08:12:17 +03:00
Aleksander Grygier a6b83d426f ui : key draft sidecar badges by model instead of repo (#30919)
Two quants of one repo pulled each other's draft: the badge read every
--model-draft arg of the repo, so each row showed both drafts.

Assisted-by: DeepSeek-V4.1-Flash-1017
2026-10-22 08:11:45 +03:00
kurquharandLi He c7fc509417 opencl: add F32-to-I32 ARGMAX (#30897)
* opencl: add F32-to-I32 ARGMAX

Assisted-by: OpenCode

* opencl: init to 0 to be consistent with cpu

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
2026-10-21 18:02:50 -07:00
alexey-rsiandSigbjørn Skjæret a37cc46104 models : add Flint model (FlintForCausalLM) (#30928)
* Add Flint model (FlintForCausalLM). Assisted by Codex

* cleanup

* one more

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
2026-10-21 23:26:22 +02:00
Tarek Dakhran 99fb997184 jinja : fix namespace attributes shadowed by dict methods (#30941) 2026-10-21 23:06:56 +02:00
Maphist0andSigbjørn Skjæret 7775ffb7e4 model: Add support for SenseNova U1 model (#28919)
* model : add SenseNova U1 understanding support

Add support for the dense SenseNova U1 understanding branch, including
the U1.5-8B-MoT model.

- Add HF-to-GGUF conversion for NEOChatModel, excluding MoT generation
  tensors and concatenating temporal and spatial Q/K normalization
  weights.
- Register the sensenova_u1 GGUF architecture and custom spatial RoPE
  metadata.
- Implement temporal/spatial Q/K normalization with 4-axis RoPE.
- Keep U1-specific text position handling in the model implementation.
- Add synthetic architecture coverage for sensenova_u1.

Only the dense understanding branch is supported; generation
branches are intentionally rejected during conversion.

* conversion : treat one-expert SenseNova U1 configs as dense

* mtmd : add SenseNova U1 vision support

Add conversion support for the SenseNova U1 vision encoder and implement
its CPU mtmd projector graph.

The graph applies patch embedding, GELU, adjacent-pair 2D RoPE, spatial
downsampling, and dense projection for dynamic-resolution image inputs.

* mtmd : fix SenseNova U1 vision token layout

* model : clean up SenseNova U1 RoPE metadata

Replace the hard-coded spatial RoPE key with shared GGUF and LLM_KV
definitions used by conversion, model loading, and architecture tests.

Clarify why SenseNova U1 retains its custom text-position input: U1 uses
[t, 0, 0, 0], while the generic M-RoPE input uses [t, t, t, 0].

* mtmd : simplify SenseNova U1 projector integration

Map the SenseNova U1 dense projection to the standard mm.0 weight and
bias tensors and reuse the shared projector fields in the loader and graph.

Read vision parameters from the checkpoint configuration and remove the
U1-specific tensor names and model fields while preserving its strided
projection, bias, and row-major token layout.

* mtmd : fix SenseNova U1 image positions and attention

* fix spacing

* mtmd : restore generic test input chunks

* mtmd : restore SenseNova U1 integration after upstream merge

* Update conversion/sensenova_u1.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

* Update src/models/sensenova-u1.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

* model : remove SenseNova U1 BF16 activation casts

Use standard backend precision handling for QKV, FFN, and output
projections instead of casting inputs based on the output tensor type.

Assisted-by: Codex

* Update src/models/sensenova-u1.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
2026-10-21 21:54:16 +02:00
Aman GuptaandPascal d454a204ef llama: create new fused op for QSA mask (#30640)
* llama: create new fused op for QSA scoring

* enroll glm5.3-next too

* tests : initialize the remaining tensors in test_kpool_mask

The harness sentinels were left uninitialized, so leftover memory could read as NaN and fail the NaN check.

* ggml: generalize the k-pool mask op into ggml_top_k_mask

The k-pool mask op only covered the pooled QSA selection of qwen4exp
and glm5-next, while the DSA models and the DeepSeek V4 CSA build the
same top-k mask by hand with fill, set_rows and add.

ggml_top_k_mask(mask, top_k, score, blocks, extra) keeps the mask
cells of the live picks and the extra cells, and sets the rest to
-inf. Every row tensor is 4D in the layout of top_k, and the mask is
broadcast to those rows, so a selection can also be made per head.
score, blocks and extra are optional: without score every pick is
live, without blocks a pick is a cell, and extra holds the cells a
row keeps without a pick.

llm_graph_context::build_fused_top_k_mask views the row tensors in
the layout of kq_mask and registers the fused node. The k-pool
models, the DSA attention of build_attn and the DeepSeek V4 CSA mask
use it when the probe enables the op, and keep their current path on
backends without it.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-22 01:02:22 +08:00
92es 0756e83fc1 sycl: q8 fast dequant (#30775) 2026-10-21 10:01:03 -07:00
alexey-rsi 9febe63d5a jinja: support hexadecimal string escapes (#30929) 2026-10-21 18:04:36 +02:00
kurquharandLi He 9b56ccf00d ggml-opencl : add IM2COL_3D support (#30895)
* ggml-opencl : add IM2COL_3D support

Assisted-by: OpenCode

* opencl: use same type for kernel and host

* opencl: fix for older adreno

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
2026-10-21 07:59:55 -07:00
Xuan-Son Nguyen 0a1ef309cd mtmd: add state object for mtmd_gen (#30902) 2026-10-21 12:59:54 +02:00
David Garcia 261700b896 server : forward llama.cpp sampling params on /v1/messages (#30856)
* server : forward llama.cpp sampling params on /v1/messages

* server : pass the request through on /v1/messages instead of copying listed keys

* NIT: Change comment from multiline to single line

* server : accept llama.cpp params under extra_params on /v1/messages

* server : remove test case for trivial functionality

* server : show extra_params in the /v1/messages README example
2026-10-21 12:58:52 +02:00
Alessandro de Oliveira Faria (A.K.A.CABELO) f849095365 vendor: update BoringSSL to 0.20261020.0 (#30889)
[no ci]
2026-10-21 11:15:54 +02:00
Evgen MykhailenkoandGeorgi Gerganov 824eaecf48 llama : optimize vocabulary loading (#30679)
* llama : optimize vocabulary loading

* Update src/unicode.h

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* CR : added reserve in gguf_kv_to_str

* CR : trailing space fix

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-21 12:03:27 +03:00
Aman Gupta eb182d85c0 llama : place d2t by its set_rows use so it can stay on the GPU (#30912) 2026-10-21 16:32:29 +08:00
Niklas Wenzel ea9e3446b5 metal : fix memory leaks and cleanup logic in GPU capture path (#30890) 2026-10-21 09:54:33 +02:00
Max Krasnyanskyandhubezhou 0efe8e4c6b hexagon: neon-vectorized and threaded repack (tiling) (#30901)
* add vectorized q4_0 repack

* apply to more types

* hex-repack: neon-vectorize the rest of the types q1/q2/f16/...

* hex-repack: add simple chunking / threading

---------

Co-authored-by: hubezhou <hubezhou@qti.qualcomm.com>
2026-10-21 10:48:39 +03:00
lhez b531e4c749 opencl: add dp4a GEMM variants to vector load activation (#30753)
* opencl: add dp4a GEMM variants with vector activation loads

* opencl: rename kernels and env vars
2026-10-21 10:48:13 +03:00
Neo Zhang 74b115ca6e sycl : fix fa crash for Query head dimension mismatch (neq0 != HSK / D=2048/4096) (#30286) 2026-10-21 10:46:43 +03:00
Titaniumtown 96caa80059 sycl: collapse duplicate need_check launch bodies in mmq (#30739) 2026-10-21 10:46:00 +03:00
bosh 479a52779f ggml-cpu : compute f32 gelu_quick and geglu_quick without the fp16 table (#30737)
* ggml-cpu : compute f32 gelu_quick and geglu_quick without the fp16 table

* ggml-cpu : keep the GGML_GELU_QUICK_FP16 table path

* ggml-cpu : add the VXE gelu_quick path

* tests : add test-gelu-quick for the f32 CPU kernels

* tests : drop inf/nan inputs from test-gelu-quick
2026-10-21 09:34:37 +02:00
Georgi Gerganov 8baabd53b7 llama : scale moe down projection per expert token (#30861)
* llama : scale moe down projection per expert token

Replace the global amax rescaling for the MoE down projection with a per-expert-token factor computed by a POOL_1D max reduction along the intermediate dimension. Undo the scale on the output with the same exact power-of-two factor. Handle POOL_1D in the Meta split-state logic so a split along axis 0 becomes a replicated per-device partial result, enabling tensor parallelism.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* cont : simplify comments

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* llama : refactor amax src1 scaling result

Return the rescaled activation and the optional scale tensor through a small struct instead of an output pointer.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* cont : add TODO + clean-up
2026-10-21 09:01:00 +03:00
Anant Shrivastava 493253fe5a Sycl iq3 crash fix (#30604) 2026-10-21 01:04:13 -04:00
Ben Ashbaugh 339f3242c7 opencl: fix rope program build error with newer Clang (#30893)
Newer versions of Clang diagnose an incompatible pointer type
error when assigning a pointer to float to a pointer to half.
2026-10-20 17:19:08 -07:00
Unbug Lee 9a3b91c680 server : fix usage.completion_tokens undercount with n > 1 (#29486)
Assisted-by: Copilot
2026-10-21 01:19:54 +02:00
Xuan-Son NguyenandPascal 5c0b49a23b server : add POST /tts endpoint for TTS models (#26603)
* mtmd: add audio out stream api

* add /tts endpoint

* add docs

* wire up mtmd_helper_model_can_chat

* upload speaker_ref via form-data

* expose seed to api

* fix header for pcm

* wip

* fix penalty_last_n

* fix server_output_limits

* fix pocket-tts stream

* wip

* add server_schema for tts

* add server routing

* server: coerce seed and stream from multipart form fields

Form fields arrive as strings, so -F seed=42 and -F stream=true were
rejected by the schema. seed joins the integer fields, parsed as a long
long so a full uint32 seed fits, and stream is read as a boolean when
it comes as a string.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-21 01:19:42 +02:00
Xuan-Son NguyenandPascal 1242be66f6 server: add realtime transcription API (#30636)
* server: add realtime transcription API

* drop transcription_sessions

* proxy ws

* add docs

* server: let the API key middleware guard the websocket

httplib runs the pre-routing handler before the websocket upgrade, so
the API key middleware answers 401 at handshake like any other route.
This covers the router proxy, whose children run keyless, and accepts
the same key forms as HTTP (Bearer, raw key, X-Api-Key).

* server: keep realtime slots out of the prompt loop

A realtime slot waiting for audio is GENERATING with nothing to decode,
so it fell through the prompt loop and took slot_batched. A request on
a higher slot then failed can_batch_with on the task type and starved
for the whole session. Realtime slots have no prompt: the loop sets
them up once and always leaves them.

* server: start the realtime session once its slot is live

The audio stream was created in the prompt loop, which skips a slot
that cannot batch with the one generating, and session.created went out
before the task was posted: early appends failed and the reader quit,
leaving the socket open and the slot held until the HTTP timeout.

The slot now sets up its stream at launch and signals it with the begin
result, session.created waits for it, and a reader that stops closes
the socket so the session ends with it.

* server: name the committed item in input_audio_buffer.committed

Per the Realtime spec, item_id is the item made from the committed audio
and previous_item_id the one committed before it, null for the first.
committed now comes before the transcript that completes that item, and
both go out with the item switch under one lock, so a delta cannot land
between them.

* server: use the GA transcription session shape

The session object follows the GA Realtime schema: type transcription,
with the input format, transcription model, turn detection and noise
reduction under audio.input. The rate stays the model sample rate, which
is what the client must send for now.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-10-21 00:04:15 +02:00
Tarek DakhranandGeorgi Gerganov 84e66d0321 refactor : move LFM2 shortconv and FFN blocks into a shared LFM base (#30874)
* llama : move LFM2 shortconv and FFN blocks into a shared LFM base

* Update src/models/lfm-base.cpp

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-10-20 23:17:13 +02:00
Takamichi Ohara 52ffa02745 model : support Nemotron-3(.5)-Embed (#30877) 2026-10-20 22:48:45 +02:00
Aman Gupta 839a572a4c qwen: reshape output to 2d instead of 3d (#30823)
* qwen: reshape output to 2d instead of 3d

* remove final reshape, fix comments
2026-10-20 22:09:56 +03:00
ead9f32058 server: Support logprobs with speculative decoding (#27196)
* return logprobs in spec path

* server: add regression test for logprobs with speculative decoding

Asserts accepted draft tokens report the target model's logprobs,
comparing a draft-enabled run against a no-draft baseline (#24271).
Fails on master (top_logprobs empty, logprob 0.0), passes with the
fix from #27196.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KARBWpPcijcBXzJEvttuwk

---------

Co-authored-by: Jeonghyun Lee <sig.jhl@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-10-20 19:36:14 +02:00
Tarek Dakhran b684b498b6 hexagon: fix VTCM size query on images that need layout out-params (#30655) 2026-10-20 10:22:08 -07:00
Yuri KhrustalevandXuan Son Nguyen 4a45de4f2c mtmd : lfm2 tiles and layout match transformers (#30261)
* mtmd : lfm2 tiles stretch the image to the grid

Lfm2VlImageProcessor.crop_image_to_patches resizes the image straight to
(512 * rows, 512 * cols) before it cuts the tiles. llama.cpp kept the aspect
ratio and added black bars for LFM2 (only d1-omni stretched), so the tiles of
d1-3B and every LFM2-VL / LFM2.5-VL model differed from transformers unless
the image had the aspect ratio of the grid. With PAD_NONE the tiles are the
same as in transformers.

* mtmd : lfm2 layout with Python rounding in double precision

Lfm2VlImageProcessor.smart_resize rounds with Python's round (half to even)
and computes beta and the tile grid aspect ratio in double. llama.cpp used
std::round and float, so 31926 of the 16.7M sizes in [1,4096]^2 got another
thumbnail size or grid than transformers (and d1-omni vision.py), e.g.
400x600 -> 416x608 instead of 384x608 and 720x1600 -> 2x5 tiles instead of
2x4. The lfm2 smart resize and grid choice now follow the Python code and
match it on every size of that range.

* fix

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-20 18:54:46 +02:00
7e3eeee048 model : add H2O-Lightning-4B decision model (#30361)
* model : add H2O-Lightning-4B decision model

Adds the h2o-lightning decision type for h2oai/h2o-lightning-4b
(Qwen3.5-4B with a merged decision LoRA).

None of the existing types match this model. It answers with " A"
rather than "A", uses up to 255 labels, and was trained on Python
json.dumps text, so floats like 12.0 must render as 12.0.

The converter writes the decision metadata and the systemone template
from the model's serve_config.json and refuses a config that doesn't
match. Tests skip unless the tiny test model is available locally.

Assisted-by: Claude Opus 5.5

* rm redundant code

* put labels to gguf

---------

Co-authored-by: Jonathan C. McKinney <jon.mckinney@h2o.ai>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-20 18:54:27 +02:00
Xuan-Son Nguyen 5ba3b9fd3d mtmd: fix gemma 4 ffn act (#30882) 2026-10-20 18:33:32 +02:00
TANGBUDUandTANGBUDU 5a9a34d888 server : avoid repeated mmproj memory margin addition on sleep/resume (#28233)
Co-authored-by: TANGBUDU <TANGBUDU@users.noreply.github.com>
2026-10-20 18:32:12 +02:00
Tarek Dakhran 39da130fcd ggml-cuda : support ssm_conv kernel size 7 (#30873) 2026-10-20 17:47:44 +02:00
Me42019 4e201388c3 server: Router wasn't propagating the client disconnects fully to the child, the response wait could cause a delayed cancellation (#29707)
* Router wasn't propagating the client disconnects fully to the child, the response wait could cause a delayed cancellation

* tests: satisfy router disconnect type check

* server : remove timing-sensitive router cancellation test

Assisted-by: Codex
2026-10-20 17:43:45 +02:00
Renzis 1ba568c5ad server: fix missing output_index in Responses API SSE events for compatibility (#30863)
* server: include output_index in Responses API SSE events

* server: reuse Anthropic streaming code for fix output_index

* server: remove unnecessary generated_tool_call_ids and oai_resp_tool_calls_started
2026-10-20 16:17:06 +02:00
Tarek DakhranandMichael DM 36140dbee2 mtmd : read LFM2 tiling params from GGUF metadata (#30840)
* mtmd : read LFM2 tiling params from GGUF metadata

Write min_tiles, max_tiles, tile_size and max_pixels_tolerance from
processor_config.json when converting LFM2-VL mmproj, read them in clip.cpp
with the previous constants as defaults. do_image_splitting=false maps to
min_tiles=max_tiles=1 (no tiling).

Rebased version of #25524, original implementation by Michael DM (@michael-dm).

Co-authored-by: Michael DM <usable_siren180@simplelogin.com>

* fix test

---------

Co-authored-by: Michael DM <usable_siren180@simplelogin.com>
2026-10-20 16:05:30 +02:00
Georgi Gerganov df11702136 metal : optimize argmax and pool_1d (#30862)
* metal : optimize argmax and pool_1d

Add a two-pass partial reduction for large-row argmax and use cooperative threadgroup reductions for pool_1d. Allocate scratch space for the argmax partials through the Metal buffer allocator.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* tests : add pool_1d perf cases

Add representative POOL_1D max cases for the MoE down-projection amax shapes, reducing the whole intermediate dim.

Assisted-by: pi:llama.cpp/ggml-org/DeepSeek-V4-Flash-Vision-Exp-GGUF (Mac Studio, M2 Ultra, 192GB)

* cont : minor fix
2026-10-20 16:46:00 +03:00
Georgi GerganovandSigbjørn Skjæret 9df0b73c70 model : drop the redundant precision overrides in the Kolibri-1 router (#30865)
* model : drop the redundant precision overrides in the Kolibri-1 router

Follow-up to the review in #30673: the router matmul works with the
default precision, so both ggml_prec_set_* calls can go away.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)

* cont : fix comment

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
2026-10-20 15:58:09 +03:00
Sigbjørn Skjæret b38960f520 ci : remove superfluous webgpu jobs (#30868) 2026-10-20 14:27:55 +02:00
lhezandHongqiang Wang a01ce9ffc6 opencl: add several gemm variants for small batches and some fixes (#30850)
* opencl: fix OOB read in `mul_mv_mxfp4_f32_flat`

* opencl: remove large-N guard that forces CPU weight placement

* this condition claims that large batch produces incorrect results on
  adreno and cannot be reproduced
* model loader uses N512 to probe op support, resulting in cpu fallback
* thus this condition does not seem necessary

* opencl: cap num of subgroups by workgroup size for q4_0 mc3

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: add small batch variant for q4_k dp4a GEMM

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: add a narrow variant of mul_mm_f16_f32_l4_lm

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

* opencl: add a one subgroup variant for gemv_noshuffle_q1_0_f32 for big M

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>

---------

Co-authored-by: Hongqiang Wang <wangh@qti.qualcomm.com>
2026-10-20 14:37:28 +03:00
Neo Zhang 8115b4c31f sycl : replace memset due to not always ordered (#30812) 2026-10-20 14:35:48 +03:00
Bernard Ladenthin 119f68d7d4 ggml-cpu : select the NEON fp32->fp16 conversion (#30505)
The __ARM_NEON branch defines both GGML_CPU_COMPUTE_* macros but selects
only FP16_TO_FP32, so FP32_TO_FP16 falls through to the portable
software conversion while neon_compute_fp32_to_fp16, a single fcvt,
stays defined and referenced by nothing. __POWER9_VECTOR__ and
__riscv_zfhmin select both.

Add an #error pair that refuses a branch defining a COMPUTE macro
without selecting it. That is how this stayed unnoticed: the fallback
silently supplies a correct software implementation, so the only
symptom is a hardware instruction nothing ever reaches. The
FP16_TO_FP32 half carries a !defined(__F16C__) carve-out, because PR
30343 keeps that direction on the lookup table on purpose.

Replace the comment above the fallback: it singled out NEON and POWER9
and pointed at a selection that now happens in the branch itself.
2026-10-20 14:35:06 +03:00
uvos b684498579 HIP/CUDA: Fix reduction in mmf getting tile widths not devisiable by 64 (#30836) 2026-10-20 14:32:38 +03:00
Aaron Teo 522f2b6eed ggml-zdnn: introduce flash attention op (#30512)
* ggml-zdnn: add scratch buffer pool

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ggml-zdnn: implement fattn, ditch scratch slots in favour of scratch buffer pool

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* docs: add note about fattn

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-20 14:31:35 +03:00
Peter SiderisandPetros Sideris 83c2d012a2 spec : save the dynamic ngram cache file (#22055)
* spec : save the dynamic ngram cache file

* save the cache at the common_speculative_impl_ngram_cache destruction
* remove save_static/save_dynamic flags as well as the relevant TODO
* if the dynamic cache file does not exist, start with an empty cache
  that is written on exit. if it exists but cannot be read, abort
  instead of overwriting it.

* spec : fix draft_one not updating the ngram cache history correctly

* common_ngram_cache_update received only the new tokens with none of
  the tokens before them causing ngrams ending at the new token to not
  being formed, leading the context cache to only ever hold the prompt

* fix: pass the full history to common_ngram_cache_update with nnew set
  to the number of new tokens the way llama-lookup does and also reset
  cache_size to 0 on each new request

---------

Co-authored-by: Petros Sideris <petros.sideris@nokia.com>
2026-10-20 14:31:10 +03:00
Bálint Bokros 13786fbb3a CUDA: Clear CUDA graphs after legacy memory pool flush (#26771)
Assisted-by: Claude Opus 5.5
2026-10-20 14:30:50 +03:00
Peter van VelzenandXuan Son Nguyen d80619397c server : pass id_slot through the Anthropic /v1/messages conversion (#28554)
* server : pass id_slot through the Anthropic /v1/messages conversion

server_chat_convert_anthropic_to_oai copies a small whitelist of common
params from the Anthropic request into the internal OAI-style body and
left id_slot out. Every other completion endpoint honours id_slot; a
proxy that pins a session to the slot holding its KV cache needs the
same on /v1/messages, or it silently falls back to automatic slot
selection and, on hybrid/recurrent models, pays a full re-prefill.

Adds a test asserting a request with id_slot set is served by that
slot, observed via GET /slots.

* server : shorten comments

* rm test

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-10-20 13:23:41 +02:00
Caladrel 704366d76f metal : fix missing MoE kernel variant for 9 experts per token (#30610) (#30699) 2026-10-20 13:02:05 +02:00
Christian Kastner 707616feb5 ci: Fix --strict mode for API check (#30623)
abi-compliance-checker was running in strict mode unconditionally,
rather than honoring the CLI flag.
2026-10-20 13:38:45 +03:00
497 changed files with 49734 additions and 25980 deletions
+109
View File
@@ -0,0 +1,109 @@
name: Behavior Check
on:
workflow_dispatch: # allows manual triggering
push:
branches:
- master
paths: [
'.github/workflows/behavior-check.yml',
'**/CMakeLists.txt',
'**/.cmake',
'**/*.h',
'**/*.hpp',
'**/*.c',
'**/*.cpp',
]
pull_request:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/behavior-check.yml',
'**/CMakeLists.txt',
'**/.cmake',
'**/*.h',
'**/*.hpp',
'**/*.c',
'**/*.cpp'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
env:
GGML_NLOOP: 3
GGML_N_THREADS: 1
GGML_SCHED_DEBUG_REALLOC: 1
LLAMA_ARG_LOG_COLORS: 1
LLAMA_ARG_LOG_PREFIX: 1
LLAMA_ARG_LOG_TIMESTAMPS: 1
jobs:
ubuntu:
runs-on: ubuntu-26.04-arm
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: Build Dependencies
id: build_depends
run: |
sudo apt update
sudo apt install -y build-essential libssl-dev git-lfs libjpeg-dev python3 python3-venv python3-pip
- name: Python Dependencies
id: python_depends
run: |
python3 -m venv .venv
source .venv/bin/activate
pip install jinja2==3.1.6
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: behavior-check-ubuntu-26.04-arm
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
cmake -B build \
-DGGML_NATIVE=OFF \
-DGGML_BACKEND_DL=OFF \
-DLLAMA_FATAL_WARNINGS=ON
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: behavior-check-ubuntu-26.04-arm
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
source .venv/bin/activate
cd build
ctest -L behavior -LE 'model|backend' --verbose --timeout 900
+1 -57
View File
@@ -105,63 +105,7 @@ jobs:
cd build
# Metal Paravirtual devices are difficult to support -> disable
# ref: https://github.com/ggml-org/llama.cpp/pull/19802#issuecomment-4013704023
ctest -L main -E "test-llama-archs|test-llama-context" --verbose --timeout 900
macos-latest-x64:
runs-on: macos-15-intel
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: apple-x64
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
sysctl -a
# Metal is disabled due to intermittent failures with Github runners not having a GPU:
# https://github.com/ggml-org/llama.cpp/actions/runs/8635935781/job/23674807267#step:5:2313
cmake -B build \
-DCMAKE_BUILD_RPATH="@loader_path" \
-DLLAMA_FATAL_WARNINGS=ON \
-DLLAMA_BUILD_BORINGSSL=ON \
-DGGML_METAL=OFF \
-DGGML_RPC=ON \
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.3
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: apple-x64
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
cd build
ctest -L main --verbose --timeout 900
ctest -L main -E "test-llama-archs|test-llama-context|test-speculative-rejection-sampling" --verbose --timeout 900
macos-latest-ios-xcode:
runs-on: macos-latest
+1 -1
View File
@@ -9,7 +9,7 @@ permissions:
jobs:
linux:
runs-on: [self-hosted, Linux, CPU]
runs-on: [self-hosted, Linux, fast]
steps:
- uses: actions/checkout@v6
with:
+1 -1
View File
@@ -129,7 +129,7 @@ jobs:
id: cmake_test
run: |
cd build
ctest -L 'main|python' --verbose --timeout 900
ctest -L main --verbose --timeout 900
- name: Test llama2c conversion
id: llama2c_test
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
- sanitizer: THREAD
machine: ubuntu-24.04
- sanitizer: UNDEFINED
machine: [self-hosted, X64, Linux]
machine: [self-hosted, X64, Linux, CPU]
runs-on: ${{ matrix.machine }}
+1 -141
View File
@@ -7,15 +7,7 @@ on:
- master
paths: [
'.github/workflows/build-webgpu.yml',
'**/CMakeLists.txt',
'**/.cmake',
'**/*.h',
'**/*.hpp',
'**/*.c',
'**/*.cpp',
'**/*.wgsl',
'**/*.tmpl',
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
'ggml/src/ggml-webgpu/**'
]
pull_request:
@@ -63,135 +55,3 @@ jobs:
-type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' \) \
-print0 |
xargs -0 clang-format-22 --dry-run --Werror
macos:
runs-on: macos-latest
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: Dawn Dependency
id: dawn-depends
run: |
DAWN_VERSION="v20260908.214631"
DAWN_OWNER="google"
DAWN_REPO="dawn"
DAWN_ASSET_NAME="Dawn-94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655-macos-latest-Release"
echo "Fetching release asset from https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
curl -L -o artifact.tar.gz \
"https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
mkdir dawn
tar -xvf artifact.tar.gz -C dawn --strip-components=1
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-macos-latest
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
export CMAKE_PREFIX_PATH=dawn
cmake -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DGGML_WEBGPU=ON -DGGML_METAL=OFF -DGGML_BLAS=OFF
time cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-macos-latest
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
cd build
ctest -L main --verbose --timeout 900
ubuntu:
runs-on: ubuntu-24.04
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: Dependencies
id: depends
run: |
sudo add-apt-repository -y ppa:kisak/kisak-mesa
sudo apt-get update -y
sudo apt-get install -y build-essential mesa-vulkan-drivers \
libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev libssl-dev
- name: Dawn Dependency
id: dawn-depends
run: |
sudo apt-get install -y libxrandr-dev libxinerama-dev libxcursor-dev mesa-common-dev libx11-xcb-dev libxi-dev
DAWN_VERSION="v20260908.214631"
DAWN_OWNER="google"
DAWN_REPO="dawn"
DAWN_ASSET_NAME="Dawn-94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655-ubuntu-latest-Release"
echo "Fetching release asset from https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
curl -L -o artifact.tar.gz \
"https://github.com/google/dawn/releases/download/${DAWN_VERSION}/${DAWN_ASSET_NAME}.tar.gz"
mkdir dawn
tar -xvf artifact.tar.gz -C dawn --strip-components=1
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-ubuntu-24.04
folder: llama.cpp
hf_bucket: ggml-org/cache
- name: Build
id: cmake_build
run: |
export Dawn_DIR=dawn/lib64/cmake/Dawn
cmake -B build \
-DGGML_WEBGPU=ON
time cmake --build build --config Release -j $(nproc)
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-ubuntu-24.04
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
- name: Test
id: cmake_test
run: |
cd build
ctest -L main --verbose --timeout 900
+2 -2
View File
@@ -46,7 +46,7 @@ env:
jobs:
cpu-x64-high-perf:
runs-on: [self-hosted, Linux, X64]
runs-on: [self-hosted, Linux, X64, CPU]
steps:
- name: Clone
@@ -56,7 +56,7 @@ jobs:
- name: Test
id: ggml-ci
run: |
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp ~/mnt/llama.cpp
LLAMA_ARG_THREADS=$(nproc) GG_BUILD_HIGH_PERF=1 GG_BUILD_EXTRA_TESTS_0=1 bash ./ci/run.sh ~/results/llama.cpp/$RUNNER_NAME ~/mnt/llama.cpp
cpu-arm64-high-perf-graviton4:
runs-on: ah-ubuntu_24_04-c8g_8x
+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
+89 -88
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
}
@@ -254,7 +264,7 @@ function gg_run_ctest_release {
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
if [ -z ${GG_BUILD_LOW_PERF} ]; then
(time ctest -C Release --output-on-failure -L 'main|python' ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
(time ctest -C Release --output-on-failure -L main ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
else
(time ctest -C Release --output-on-failure -L main -E test-opt ${CTEST_EXTRA}) 2>&1 | tee -a $OUT/${ci}-ctest.log
fi
@@ -312,8 +322,8 @@ function gg_run_test_scripts {
set -e
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$MNT/models") 2>&1 | tee -a $OUT/${ci}-scripts.log
(cd ./tools/gguf-split && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
(cd ./tools/quantize && time bash tests.sh "$SRC/build-ci-release/bin" "$OUT/scratch") 2>&1 | tee -a $OUT/${ci}-scripts.log
set +e
}
@@ -374,10 +384,14 @@ function gg_run_qwen3_0_6b {
gg_wget models-mnt/wikitext/ https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d models-mnt/wikitext/
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
mkdir -p $OUT/scratch/qwen3-0.6B
unzip -o models-mnt/wikitext/wikitext-2-raw-v1.zip -d $OUT/scratch/wikitext/
path_models="../models-mnt/qwen3/0.6B"
path_wiki="../models-mnt/wikitext/wikitext-2-raw"
path_local="$OUT/scratch/qwen3-0.6B"
path_wiki="$OUT/scratch/wikitext/wikitext-2-raw"
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
@@ -386,72 +400,15 @@ function gg_run_qwen3_0_6b {
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf --outtype f16
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-bf16.gguf --outtype bf16
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf --outtype f16
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-bf16.gguf --outtype bf16
model_f16="${path_models}/ggml-model-f16.gguf"
model_bf16="${path_models}/ggml-model-bf16.gguf"
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
model_q4_0="${path_models}/ggml-model-q4_0.gguf"
model_q4_1="${path_models}/ggml-model-q4_1.gguf"
model_q5_0="${path_models}/ggml-model-q5_0.gguf"
model_q5_1="${path_models}/ggml-model-q5_1.gguf"
model_q2_k="${path_models}/ggml-model-q2_k.gguf"
model_q3_k="${path_models}/ggml-model-q3_k.gguf"
model_q4_k="${path_models}/ggml-model-q4_k.gguf"
model_q5_k="${path_models}/ggml-model-q5_k.gguf"
model_q6_k="${path_models}/ggml-model-q6_k.gguf"
model_f16="${path_local}/ggml-model-f16.gguf"
model_bf16="${path_local}/ggml-model-bf16.gguf"
model_q4_0="${path_local}/ggml-model-q4_0.gguf"
wiki_test="${path_wiki}/wiki.test.raw"
./bin/llama-quantize ${model_bf16} ${model_q8_0} q8_0 $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q4_0} q4_0 $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q4_1} q4_1 $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q5_0} q5_0 $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q5_1} q5_1 $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q2_k} q2_k $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q3_k} q3_k $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q4_k} q4_k $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q5_k} q5_k $(nproc)
./bin/llama-quantize ${model_bf16} ${model_q6_k} q6_k $(nproc)
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
(time ./bin/llama-completion -no-cnv --model ${model_q8_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
(time ./bin/llama-completion -no-cnv --model ${model_q4_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
(time ./bin/llama-completion -no-cnv --model ${model_q4_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
(time ./bin/llama-completion -no-cnv --model ${model_q5_0} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
(time ./bin/llama-completion -no-cnv --model ${model_q5_1} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
(time ./bin/llama-completion -no-cnv --model ${model_q2_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
(time ./bin/llama-completion -no-cnv --model ${model_q3_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
(time ./bin/llama-completion -no-cnv --model ${model_q4_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
(time ./bin/llama-completion -no-cnv --model ${model_q5_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
(time ./bin/llama-completion -no-cnv --model ${model_q6_k} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
if [ -z ${GG_BUILD_NO_BF16} ]; then
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
fi
(time ./bin/llama-perplexity --model ${model_q8_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
(time ./bin/llama-perplexity --model ${model_q4_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_0.log
(time ./bin/llama-perplexity --model ${model_q4_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_1.log
(time ./bin/llama-perplexity --model ${model_q5_0} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_0.log
(time ./bin/llama-perplexity --model ${model_q5_1} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_1.log
(time ./bin/llama-perplexity --model ${model_q2_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q2_k.log
(time ./bin/llama-perplexity --model ${model_q3_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q3_k.log
(time ./bin/llama-perplexity --model ${model_q4_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q4_k.log
(time ./bin/llama-perplexity --model ${model_q5_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q5_k.log
(time ./bin/llama-perplexity --model ${model_q6_k} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-q6_k.log
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
function check_ppl {
qnt="$1"
ppl=$(echo "$2" | grep -oE "[0-9]+\.[0-9]+" | tail -n 1)
@@ -465,20 +422,56 @@ function gg_run_qwen3_0_6b {
return 0
}
for qnt in q8_0 q4_0 q4_1 q5_0 q5_1 q2_k q3_k q4_k q5_k q6_k; do
model="${path_local}/ggml-model-${qnt}.gguf"
./bin/llama-quantize ${model_bf16} ${model} ${qnt} $(nproc)
(time ./bin/llama-completion -no-cnv --model ${model} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
(time ./bin/llama-perplexity --model ${model} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-${qnt}.log
# note: ppl > 20.0 for q2_k with this model
if [ "${qnt}" != "q2_k" ]; then
check_ppl "${qnt}" "$(cat $OUT/${ci}-tg-${qnt}.log | grep "^\[1\]")"
fi
if [ "${qnt}" = "q4_0" ]; then
# q4_0 is kept for other tests
q4_0="${path_models}/ggml-model-q4_0.gguf"
ln ${model} "$q4_0" 2>/dev/null || { cp ${model} $q4_0.tmp.$$ && mv -f $q4_0.tmp.$$ $q4_0; }
else
rm -f ${model}
fi
done
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
(time ./bin/llama-completion -no-cnv --model ${model_f16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
(time ./bin/llama-completion -no-cnv --model ${model_bf16} -ngl 99 -c 1024 -s 1234 -n 64 --ignore-eos -p "I believe the meaning of life is" ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
(time ./bin/llama-perplexity --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
if [ -z ${GG_BUILD_NO_BF16} ]; then
(time ./bin/llama-perplexity --model ${model_bf16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-tg-bf16.log
fi
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
# TODO: these tests are extremely slow on WebGPU - re-enable when fixed
# https://github.com/ggml-org/llama.cpp/actions/runs/39639985732
if [ -z ${GG_BUILD_WEBGPU} ]; then
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
fi
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
check_ppl "f16" "$(cat $OUT/${ci}-tg-f16.log | grep "^\[1\]")"
if [ -z ${GG_BUILD_NO_BF16} ]; then
check_ppl "bf16" "$(cat $OUT/${ci}-tg-bf16.log | grep "^\[1\]")"
fi
check_ppl "q8_0" "$(cat $OUT/${ci}-tg-q8_0.log | grep "^\[1\]")"
check_ppl "q4_0" "$(cat $OUT/${ci}-tg-q4_0.log | grep "^\[1\]")"
check_ppl "q4_1" "$(cat $OUT/${ci}-tg-q4_1.log | grep "^\[1\]")"
check_ppl "q5_0" "$(cat $OUT/${ci}-tg-q5_0.log | grep "^\[1\]")"
check_ppl "q5_1" "$(cat $OUT/${ci}-tg-q5_1.log | grep "^\[1\]")"
#check_ppl "q2_k" "$(cat $OUT/${ci}-tg-q2_k.log | grep "^\[1\]")" # note: ppl > 20.0 for this quant and model
check_ppl "q3_k" "$(cat $OUT/${ci}-tg-q3_k.log | grep "^\[1\]")"
check_ppl "q4_k" "$(cat $OUT/${ci}-tg-q4_k.log | grep "^\[1\]")"
check_ppl "q5_k" "$(cat $OUT/${ci}-tg-q5_k.log | grep "^\[1\]")"
check_ppl "q6_k" "$(cat $OUT/${ci}-tg-q6_k.log | grep "^\[1\]")"
# scratch is rebuilt on every run; q4_0 is already published to $MNT
rm -rf $OUT/scratch/qwen3-0.6B $OUT/scratch/wikitext
set +e
}
@@ -501,6 +494,8 @@ function gg_run_embd_bge_small {
gg_wget models-mnt/bge-small/1_Pooling https://huggingface.co/BAAI/bge-small-en-v1.5/raw/main/1_Pooling/config.json
path_models="../models-mnt/bge-small"
path_local="$OUT/scratch/bge-small"
rm -rf $path_local && mkdir -p $path_local
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
@@ -509,10 +504,10 @@ function gg_run_embd_bge_small {
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
model_f16="${path_models}/ggml-model-f16.gguf"
model_q8_0="${path_models}/ggml-model-q8_0.gguf"
model_f16="${path_local}/ggml-model-f16.gguf"
model_q8_0="${path_local}/ggml-model-q8_0.gguf"
./bin/llama-quantize ${model_f16} ${model_q8_0} q8_0
@@ -521,6 +516,8 @@ function gg_run_embd_bge_small {
(time ./bin/llama-embedding --model ${model_f16} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-f16.log
(time ./bin/llama-embedding --model ${model_q8_0} -p "I believe the meaning of life is" -ngl 99 -c 0 --no-op-offload) 2>&1 | tee -a $OUT/${ci}-tg-q8_0.log
rm -rf $path_local
set +e
}
@@ -537,6 +534,8 @@ function gg_run_rerank_tiny {
gg_wget models-mnt/rerank-tiny/ https://huggingface.co/jinaai/jina-reranker-v1-tiny-en/raw/main/vocab.json
path_models="../models-mnt/rerank-tiny"
path_local="$OUT/scratch/rerank-tiny"
rm -rf $path_local && mkdir -p $path_local
rm -rf build-ci-release && mkdir build-ci-release && cd build-ci-release
@@ -545,9 +544,9 @@ function gg_run_rerank_tiny {
(cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
(time cmake --build . --config Release -j$(nproc)) 2>&1 | tee -a $OUT/${ci}-make.log
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_models}/ggml-model-f16.gguf
python3 ../convert_hf_to_gguf.py ${path_models} --outfile ${path_local}/ggml-model-f16.gguf
model_f16="${path_models}/ggml-model-f16.gguf"
model_f16="${path_local}/ggml-model-f16.gguf"
(time ./bin/llama-fit-params --model ${model_f16} 2>&1 | tee -a $OUT/${ci}-fp-f16.log)
@@ -577,6 +576,8 @@ function gg_run_rerank_tiny {
check_score "rerank score 1" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 1")" "0.00" "0.05" | tee -a $OUT/${ci}-rk-f16.log
check_score "rerank score 2" "$(cat $OUT/${ci}-rk-f16.log | grep "rerank score 2")" "0.10" "0.30" | tee -a $OUT/${ci}-rk-f16.log
rm -rf $path_local
set +e
}
@@ -683,11 +684,11 @@ if [ -z ${GG_BUILD_LOW_PERF} ]; then
ln -sfn ${mnt_models} ${SRC}/models-mnt
# Create a fresh python3 venv and enter it
if ! python3 -m venv "$MNT/venv"; then
echo "Error: Failed to create Python virtual environment at $MNT/venv."
if ! python3 -m venv "$OUT/venv"; then
echo "Error: Failed to create Python virtual environment at $OUT/venv."
exit 1
fi
source "$MNT/venv/bin/activate"
source "$OUT/venv/bin/activate"
pip install -r ${SRC}/requirements.txt --disable-pip-version-check
pip install --editable gguf-py --disable-pip-version-check
+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;
+55 -40
View File
@@ -26,6 +26,7 @@
#include <fstream>
#include <iostream>
#include <iterator>
#include <random>
#include <regex>
#include <sstream>
#include <string>
@@ -741,50 +742,56 @@ static inline bool glob_class_match(const char c, const char * pattern, const ch
}
// simple glob: * matches non-/ chars, ** matches anything including /, [] matches character class
static inline bool glob_match(const char * pattern, const char * str) {
if (*pattern == '\0') {
return *str == '\0';
}
if (pattern[0] == '*' && pattern[1] == '*') {
const char * p = pattern + 2;
if (glob_match(p, str)) return true;
if (*str != '\0') return glob_match(pattern, str + 1);
// only wildcards recurse, too many wildcards: no match
static bool glob_match(const char * pattern, const char * str, int depth = 0) {
if (depth >= COMMON_RECURSIVE_LIMIT) {
return false;
}
if (*pattern == '*') {
const char * p = pattern + 1;
for (; *str != '\0' && *str != '/'; str++) {
if (glob_match(p, str)) return true;
}
return glob_match(p, str);
}
if (*pattern == '?' && *str != '\0' && *str != '/') {
return glob_match(pattern + 1, str + 1);
}
if (*pattern == '[') {
const char * class_end = pattern + 1;
// If first character after '[' is ']' or '-', treat it as literal
if (*class_end == ']' || *class_end == '-') {
class_end++;
}
while (*class_end != '\0' && *class_end != ']') {
class_end++;
}
if (*class_end == ']') {
if (*str == '\0') return false;
bool matched = glob_class_match(*str, pattern + 1, class_end);
return matched && glob_match(class_end + 1, str + 1);
} else {
if (*str == '[') {
return glob_match(pattern + 1, str + 1);
while (*pattern != '\0') {
if (pattern[0] == '*' && pattern[1] == '*') {
const char * p = pattern + 2;
for (;; str++) {
if (glob_match(p, str, depth + 1)) return true;
if (*str == '\0') return false;
}
return false;
}
if (*pattern == '*') {
const char * p = pattern + 1;
for (; *str != '\0' && *str != '/'; str++) {
if (glob_match(p, str, depth + 1)) return true;
}
return glob_match(p, str, depth + 1);
}
if (*pattern == '?' && *str != '\0' && *str != '/') {
pattern++;
str++;
continue;
}
if (*pattern == '[') {
const char * class_end = pattern + 1;
// If first character after '[' is ']' or '-', treat it as literal
if (*class_end == ']' || *class_end == '-') {
class_end++;
}
while (*class_end != '\0' && *class_end != ']') {
class_end++;
}
if (*class_end == ']') {
if (*str == '\0' || !glob_class_match(*str, pattern + 1, class_end)) return false;
pattern = class_end + 1;
str++;
continue;
}
if (*str != '[') return false;
pattern++;
str++;
continue;
}
if (*pattern != *str) return false;
pattern++;
str++;
}
if (*pattern == *str) {
return glob_match(pattern + 1, str + 1);
}
return false;
return *str == '\0';
}
bool glob_match(const std::string & pattern, const std::string & str) {
@@ -917,9 +924,13 @@ std::string fs_path_to_utf8(const std::filesystem::path & path) {
}
void fs_write_atomic(const std::filesystem::path & path, const std::string & data) {
// a random suffix gives every writer its own temporary file, across processes and containers
std::random_device rd;
const uint64_t id = ((uint64_t) rd() << 32) | rd();
std::error_code ec;
std::filesystem::path path_tmp = path;
path_tmp += ".tmp";
path_tmp += "." + std::to_string(id) + ".tmp";
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path(), ec);
@@ -1175,6 +1186,7 @@ static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NA
{ COMMON_DECISION_TYPE_LFM2_D1, "lfm2-d1" },
{ COMMON_DECISION_TYPE_LFM2_D1_OMNI, "lfm2-d1-omni" },
{ COMMON_DECISION_TYPE_GRANITE_SWITCH, "granite-switch"},
{ COMMON_DECISION_TYPE_H2O_LIGHTNING, "h2o-lightning" },
// for future models, ALWAYS add org name prefix to avoid name collisions, e.g. "orgname-modelname"
};
@@ -1234,6 +1246,9 @@ common_gguf_info common_get_gguf_info(const std::string & fname) {
info.n_ctx_train = gguf_get_val_u32(gguf_ctx.get(), ctx_id);
}
// same key as llama_model_chat_template(model, nullptr)
info.has_chat_tmpl = gguf_find_key(gguf_ctx.get(), "tokenizer.chat_template") >= 0;
return info;
}
+4
View File
@@ -31,6 +31,8 @@
#define DIRECTORY_SEPARATOR '/'
#endif // _WIN32
#define COMMON_RECURSIVE_LIMIT 128
#define COM_DBG(fmt, ...) LOG_DBG("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
#define COM_TRC(fmt, ...) LOG_TRC("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
#define COM_INF(fmt, ...) LOG_INF("cmn %12.*s: " fmt, 12, __func__, __VA_ARGS__)
@@ -975,6 +977,7 @@ enum common_decision_type {
COMMON_DECISION_TYPE_LFM2_D1, // same as openjev, the labels depend on the question type
COMMON_DECISION_TYPE_LFM2_D1_OMNI, // same as laya, other prompt layout
COMMON_DECISION_TYPE_GRANITE_SWITCH, // same as openjev, run by the decision aLoRA of granite switch
COMMON_DECISION_TYPE_H2O_LIGHTNING, // same as openjev, the labels are read after an answer prefill
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
};
@@ -984,6 +987,7 @@ common_decision_type common_get_decision_type(const struct llama_model * model);
struct common_gguf_info {
common_decision_type decision_type = COMMON_DECISION_TYPE_UNKNOWN; // UNKNOWN if the file is missing, unreadable, or invalid
uint32_t n_ctx_train = 0; // 0 if unknown
bool has_chat_tmpl = false; // has the default tokenizer.chat_template
};
common_gguf_info common_get_gguf_info(const std::string & fname);
+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);
+20
View File
@@ -1,7 +1,9 @@
#include "lexer.h"
#include "runtime.h"
#include "unicode.h"
#include <cctype>
#include <charconv>
#include <functional>
#include <map>
#include <string>
@@ -75,6 +77,24 @@ lexer_result lexer::tokenize(const std::string & source) {
}
// add escaped char
char escaped_char = src[pos++];
if (escaped_char == 'x') {
if (pos + 1 >= src.size()) {
throw lexer_exception("incomplete hexadecimal escape", source, pos);
}
unsigned int value = 0;
const char * first = &src[pos];
const char * last = first + 2;
auto [ptr, ec] = std::from_chars(first, last, value, 16);
if (ec != std::errc{} || ptr != last) {
throw lexer_exception("invalid hexadecimal escape", source, pos);
}
str += common_unicode_cpt_to_utf8(value);
pos += 2;
continue;
}
if (escape_chars.find(escaped_char) == escape_chars.end()) {
throw lexer_exception(std::string("unknown escape character \\") + escaped_char, source, pos);
}
+21 -3
View File
@@ -21,6 +21,17 @@ static bool is_type(const statement_ptr & ptr) {
class parser {
const std::vector<token> & tokens;
size_t current = 0;
int depth = 0;
struct depth_guard {
parser & p;
depth_guard(parser & p) : p(p) {
if (++p.depth > JINJA_RECURSIVE_LIMIT) {
throw parser_exception("Parser Error: template is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep", p.source, p.peek().pos);
}
}
~depth_guard() { p.depth--; }
};
std::string source; // for error reporting
@@ -94,6 +105,7 @@ private:
}
statement_ptr parse_any() {
depth_guard guard(*this);
size_t start_pos = current;
switch (peek().t) {
case token::comment:
@@ -254,6 +266,7 @@ private:
size_t pos0 = current;
++current; // consume {%
++current; // consume 'elif'
depth_guard guard(*this);
alternate.push_back(parse_if_statement(pos0)); // nested If
} else if (is_statement({"else"})) {
++current; // consume {%
@@ -330,6 +343,7 @@ private:
}
statement_ptr parse_if_expression() {
depth_guard guard(*this);
auto a = parse_logical_or_expression();
if (is_identifier("if")) {
// Ternary expression
@@ -373,6 +387,7 @@ private:
statement_ptr parse_logical_negation_expression() {
// Try parse unary operators
if (is_identifier("not")) {
depth_guard guard(*this);
size_t start_pos = current;
auto op = next();
return mk_stmt<unary_expression>(start_pos, op, parse_logical_negation_expression());
@@ -458,6 +473,7 @@ private:
statement_ptr parse_unary_expression() {
if (is(token::unary_operator)) {
depth_guard guard(*this);
size_t start_pos = current;
auto op = next();
return mk_stmt<unary_expression>(start_pos, op, parse_unary_expression());
@@ -477,9 +493,11 @@ private:
size_t start_pos = current;
auto expr = mk_stmt<call_expression>(start_pos, std::move(callee), parse_args());
auto member = parse_member_expression(std::move(expr)); // foo.x().y
return is(token::open_paren)
? parse_call_expression(std::move(member)) // foo.x()()
: std::move(member);
if (!is(token::open_paren)) {
return member;
}
depth_guard guard(*this);
return parse_call_expression(std::move(member)); // foo.x()()
}
statements parse_args() {
+23 -7
View File
@@ -3,6 +3,7 @@
#include "value.h"
#include "utils.h"
#include <exception>
#include <string>
#include <vector>
#include <memory>
@@ -52,32 +53,39 @@ static void ensure_key_type_allowed(const value & val) {
// execute with error handling
value statement::execute(context & ctx) const {
std::exception_ptr pending;
std::string message;
try {
return execute_impl(ctx);
} catch (const continue_statement::signal & /* ex */) {
throw;
pending = std::current_exception();
} catch (const break_statement::signal & /* ex */) {
throw;
pending = std::current_exception();
} catch (const rethrown_exception & /* ex */) {
throw;
pending = std::current_exception();
} catch (const not_implemented_exception & /* ex */) {
throw;
pending = std::current_exception();
} catch (const std::exception & e) {
const std::string & source = *ctx.src;
if (source.empty()) {
std::ostringstream oss;
oss << "\nError executing " << type() << " at position " << pos << ": " << e.what();
throw rethrown_exception(oss.str());
message = oss.str();
} else {
std::ostringstream oss;
oss << "\n------------\n";
oss << "While executing " << type() << " at " << get_line_col(source, pos) << " in source:\n";
oss << peak_source(source, pos) << "\n";
oss << "Error: " << e.what();
// throw as another exception to avoid repeated formatting
throw rethrown_exception(oss.str());
message = oss.str();
}
}
// throw outside the catch: MSVC keeps the unwound frames on the stack while a catch block runs
if (pending) {
std::rethrow_exception(pending);
}
// throw as another exception to avoid repeated formatting
throw rethrown_exception(message);
}
value identifier::execute_impl(context & ctx) const {
@@ -747,7 +755,11 @@ value macro_statement::execute_impl(context & ctx) const {
std::string name = cast_stmt<identifier>(this->name)->val;
const func_handler func = [this, name](const func_args & args) -> value {
if (args.ctx.macro_depth >= JINJA_RECURSIVE_LIMIT) {
throw std::runtime_error("macro calls are nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
}
context macro_ctx(args.ctx); // new scope for macro execution
macro_ctx.macro_depth = args.ctx.macro_depth + 1;
bind_parameters(name, this->args, args, macro_ctx);
@@ -778,7 +790,11 @@ value call_statement::execute_impl(context & ctx) const {
context caller_ctx(ctx); // new scope for caller execution
const func_handler func = [this, caller_ctx = std::move(caller_ctx)](const func_args & args) -> value {
if (args.ctx.macro_depth >= JINJA_RECURSIVE_LIMIT) {
throw std::runtime_error("macro calls are nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
}
context block_ctx(caller_ctx); // new scope for block execution
block_ctx.macro_depth = args.ctx.macro_depth + 1;
bind_parameters("caller", this->caller_args, args, block_ctx);
+7 -2
View File
@@ -58,6 +58,8 @@ struct context {
bool is_get_stats = false; // whether to collect stats
int macro_depth = 0;
visitor_fn visitor;
// src is optional, used for error reporting
@@ -78,6 +80,7 @@ struct context {
src(parent.src),
current_time(parent.current_time),
is_get_stats(parent.is_get_stats),
macro_depth(parent.macro_depth),
// inherit variables (for example, when entering a new scope)
env(mk_val<value_object>(*parent.env)) {}
@@ -710,7 +713,7 @@ struct rethrown_exception : public std::exception {
//////////////////////
static void gather_string_parts_recursive(const value & val, value_string & parts) {
static void gather_string_parts_recursive(const value & val, value_string & parts, int depth = 0) {
// TODO: probably allow print value_none as "None" string? currently this breaks some templates
if (is_val<value_string>(val)) {
const auto & str_val = cast_val<value_string>(val)->val_str;
@@ -718,10 +721,12 @@ static void gather_string_parts_recursive(const value & val, value_string & part
} else if (is_val<value_int>(val) || is_val<value_float>(val) || is_val<value_bool>(val)) {
std::string str_val = val->as_string().str();
parts->val_str.append(str_val);
} else if (is_val<value_array>(val) && depth >= JINJA_RECURSIVE_LIMIT) {
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
} else if (is_val<value_array>(val)) {
auto items = cast_val<value_array>(val)->as_array();
for (const auto & item : items) {
gather_string_parts_recursive(item, parts);
gather_string_parts_recursive(item, parts, depth + 1);
}
}
}
+2
View File
@@ -6,6 +6,8 @@
#include <cstdint>
#include <cstring>
#define JINJA_RECURSIVE_LIMIT 128
namespace jinja {
static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
+64 -71
View File
@@ -6,6 +6,7 @@
#include "json.h"
#include <sstream>
#include <stdexcept>
#include <string>
#include <cctype>
#include <cfloat>
@@ -299,74 +300,53 @@ static value & get_attribute(const value & val, const value & attr, value & defa
return default_val;
}
template<bool is_reject>
static value selectattr(const func_args & args) {
args.ensure_count(2, 4);
args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false);
template<bool is_reject, bool lookup_attr>
static value select_or_reject(const func_args & args) {
// example: array | select("odd") -> test_is_odd(item)
// example: array | selectattr("status", "equalto", "active") -> test_is_equalto(item.status, "active")
constexpr size_t test_pos = lookup_attr ? 2 : 1;
args.ensure_count(test_pos);
args.ensure_val<value_array>(args.get_pos(0));
value attr;
if constexpr (lookup_attr) {
attr = args.get_pos(1);
args.ensure_val<value_string>(attr);
}
auto & builtins = global_builtins();
auto test_it = builtins.end();
if (args.count() > test_pos) {
args.ensure_val<value_string>(args.get_pos(test_pos));
std::string test_name = args.get_pos(test_pos)->as_string().str();
test_it = builtins.find("test_is_" + test_name);
if (test_it == builtins.end()) {
throw raised_exception(args.func_name + ": unknown test '" + test_name + "'");
}
}
auto arr = args.get_pos(0)->as_array();
auto attribute = args.get_pos(1);
auto out = mk_val<value_array>();
value val_default = mk_val<value_undefined>();
if (args.count() == 2) {
// example: array | selectattr("active")
for (const auto & item : arr) {
value attr_val = get_attribute(item, attribute, val_default);
bool is_selected = attr_val->as_bool();
if constexpr (is_reject) is_selected = !is_selected;
if (is_selected) out->push_back(item);
for (const auto & item : arr) {
value val = item;
if constexpr (lookup_attr) {
val = get_attribute(item, attr, val_default);
}
return out;
} else if (args.count() == 3) {
// example: array | selectattr("equalto", "text")
// translated to: test_is_equalto(item, "text")
std::string test_name = args.get_pos(1)->as_string().str();
value test_val = args.get_pos(2);
auto & builtins = global_builtins();
auto it = builtins.find("test_is_" + test_name);
if (it == builtins.end()) {
throw raised_exception("selectattr: unknown test '" + test_name + "'");
}
auto test_fn = it->second;
for (const auto & item : arr) {
bool is_selected;
if (test_it == builtins.end()) {
is_selected = val->as_bool();
} else {
func_args test_args(args.ctx);
test_args.push_back(item); // current object
test_args.push_back(test_val); // extra argument
value test_result = test_fn(test_args);
bool is_selected = test_result->as_bool();
if constexpr (is_reject) is_selected = !is_selected;
if (is_selected) out->push_back(item);
test_args.push_back(val);
for (size_t i = test_pos + 1; i < args.count(); i++) {
test_args.push_back(args.get_pos(i));
}
is_selected = test_it->second(test_args)->as_bool();
}
return out;
} else if (args.count() == 4) {
// example: array | selectattr("status", "equalto", "active")
// translated to: test_is_equalto(item.status, "active")
std::string test_name = args.get_pos(2)->as_string().str();
auto extra_arg = args.get_pos(3);
auto & builtins = global_builtins();
auto it = builtins.find("test_is_" + test_name);
if (it == builtins.end()) {
throw raised_exception("selectattr: unknown test '" + test_name + "'");
}
auto test_fn = it->second;
for (const auto & item : arr) {
value attr_val = get_attribute(item, attribute, val_default);
func_args test_args(args.ctx);
test_args.push_back(attr_val); // attribute value
test_args.push_back(extra_arg); // extra argument
value test_result = test_fn(test_args);
bool is_selected = test_result->as_bool();
if constexpr (is_reject) is_selected = !is_selected;
if (is_selected) out->push_back(item);
}
return out;
} else {
throw raised_exception("selectattr: invalid number of arguments");
if constexpr (is_reject) is_selected = !is_selected;
if (is_selected) out->push_back(item);
}
return out;
}
@@ -425,7 +405,12 @@ const func_builtins & global_builtins() {
throw raised_exception("Jinja Exception: " + msg);
}},
{"dict", toobject},
{"namespace", toobject},
{"namespace", [](const func_args & args) -> value {
// unlike a dict, a namespace has no methods: ns.items is the attribute, as in jinja2
value out = toobject(args);
cast_val<value_object>(out)->has_builtins = false;
return out;
}},
{"strftime_now", [](const func_args & args) -> value {
args.ensure_vals<value_string>();
std::string format = args.get_pos(0)->as_string().str();
@@ -1090,10 +1075,10 @@ const func_builtins & value_array_t::get_builtins() const {
auto arr = slice(val->as_array(), start, stop, step);
return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr));
}},
{"selectattr", selectattr<false>},
{"select", selectattr<false>},
{"rejectattr", selectattr<true>},
{"reject", selectattr<true>},
{"selectattr", select_or_reject<false, true>},
{"select", select_or_reject<false, false>},
{"rejectattr", select_or_reject<true, true>},
{"reject", select_or_reject<true, false>},
{"join", [](const func_args & args) -> value {
args.ensure_count(1, 3);
if (!is_val<value_array>(args.get_pos(0))) {
@@ -1455,7 +1440,8 @@ const func_builtins & value_undefined_t::get_builtins() const {
//////////////////////////////////
static value from_json(const common_json & j, bool mark_input) {
// the runtime walks values with recursion, a deeper input overflows the stack
static value from_json(const common_json & j, bool mark_input, int depth = 0) {
if (j.is_null()) {
return mk_val<value_none>();
} else if (j.is_boolean()) {
@@ -1470,16 +1456,18 @@ static value from_json(const common_json & j, bool mark_input) {
str->mark_input();
}
return str;
} else if ((j.is_array() || j.is_object()) && depth >= JINJA_RECURSIVE_LIMIT) {
throw std::invalid_argument("JSON is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
} else if (j.is_array()) {
auto arr = mk_val<value_array>();
for (const auto & item : j) {
arr->push_back(from_json(item, mark_input));
arr->push_back(from_json(item, mark_input, depth + 1));
}
return arr;
} else if (j.is_object()) {
auto obj = mk_val<value_object>();
for (auto it = j.begin(); it != j.end(); ++it) {
obj->insert(it.key(), from_json(it.value(), mark_input));
obj->insert(it.key(), from_json(it.value(), mark_input, depth + 1));
}
return obj;
} else {
@@ -1674,6 +1662,8 @@ static void value_to_json_internal(std::ostringstream & oss, const value & val,
}
}
oss << "\"";
} else if ((is_val<value_array>(val) || is_val<value_object>(val)) && curr_lvl >= JINJA_RECURSIVE_LIMIT) {
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
} else if (is_val<value_array>(val)) {
const auto & arr = val->as_array();
oss << "[";
@@ -1739,17 +1729,20 @@ std::string value_to_string_repr(const value & val) {
}
// stats utility
void value_t::stats_t::mark_used(value & val, bool deep) {
void value_t::stats_t::mark_used(value & val, bool deep, int depth) {
val->stats.used = true;
if (deep && (is_val<value_array>(val) || is_val<value_object>(val)) && depth >= JINJA_RECURSIVE_LIMIT) {
throw std::runtime_error("value is nested more than " + std::to_string(JINJA_RECURSIVE_LIMIT) + " levels deep");
}
if (deep) {
if (is_val<value_array>(val)) {
for (auto & item : val->val_arr) {
mark_used(item, deep);
mark_used(item, deep, depth + 1);
}
} else if (is_val<value_object>(val)) {
for (auto & pair : val->val_obj) {
mark_used(pair.first, deep);
mark_used(pair.second, deep);
mark_used(pair.first, deep, depth + 1);
mark_used(pair.second, deep, depth + 1);
}
}
}
+1 -1
View File
@@ -119,7 +119,7 @@ struct value_t {
// ops can be builtin calls or operators: "array_access", "object_access"
std::set<std::string> ops;
// utility to recursively mark value and its children as used
static void mark_used(value & val, bool deep = false);
static void mark_used(value & val, bool deep = false, int depth = 0);
} stats;
value_t() = default;
+46 -14
View File
@@ -272,15 +272,12 @@ static bool is_reserved_name(const std::string & name) {
return RESERVED_NAMES.find(name) != RESERVED_NAMES.end();
}
static std::regex INVALID_RULE_CHARS_RE("[^a-zA-Z0-9-]+");
static std::regex GRAMMAR_LITERAL_ESCAPE_RE("[\r\n\"\\\\]");
static std::regex GRAMMAR_RANGE_LITERAL_ESCAPE_RE("[\r\n\"\\]\\-\\\\]");
static std::unordered_map<char, std::string> GRAMMAR_LITERAL_ESCAPES = {
{'\r', "\\r"}, {'\n', "\\n"}, {'"', "\\\""}, {'-', "\\-"}, {']', "\\]"}, {'\\', "\\\\"}
};
static const int MAX_PATTERN_DEPTH = 100;
static std::unordered_set<char> NON_LITERAL_SET = {'|', '.', '(', ')', '[', ']', '{', '}', '*', '+', '?', '^', '$'};
static std::unordered_set<char> ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = {'^', '$', '.', '[', ']', '(', ')', '|', '{', '}', '*', '+', '?'};
@@ -312,6 +309,22 @@ static std::string format_literal(const std::string & literal) {
std::string gbnf_format_literal(const std::string & literal) { return format_literal(literal); }
// same as std::regex_replace with [^a-zA-Z0-9-]+ -> "-", no std::regex: libstdc++ recurses per char
std::string gbnf_format_rule_name(const std::string & name) {
std::string out;
bool in_invalid = false;
for (char c : name) {
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') {
out += c;
in_invalid = false;
} else if (!in_invalid) {
out += '-';
in_invalid = true;
}
}
return out;
}
static size_t gbnf_escape_length(const std::string & pattern, size_t pos) {
if (pos + 1 >= pattern.length() || pattern[pos] != '\\') {
return 0;
@@ -347,6 +360,17 @@ private:
std::unordered_set<std::string> _refs_being_resolved;
std::vector<std::string> _errors;
std::vector<std::string> _warnings;
int _depth = 0;
struct depth_guard {
int & depth;
depth_guard(int & depth) : depth(depth) {
if (++depth > COMMON_RECURSIVE_LIMIT) {
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
}
}
~depth_guard() { depth--; }
};
template <typename T>
static const T & as(const common_chat_schema & node) {
@@ -354,7 +378,7 @@ private:
}
std::string _add_rule(const std::string & name, const std::string & rule) {
std::string esc_name = regex_replace(name, INVALID_RULE_CHARS_RE, "-");
std::string esc_name = gbnf_format_rule_name(name);
if (_rules.find(esc_name) == _rules.end() || _rules[esc_name] == rule) {
_rules[esc_name] = rule;
return esc_name;
@@ -482,7 +506,7 @@ private:
}
}
paren_depth++;
if (paren_depth > MAX_PATTERN_DEPTH) {
if (paren_depth > COMMON_RECURSIVE_LIMIT) {
throw unsupported_pattern("pattern nesting too deep");
}
seq.emplace_back("(" + to_rule(transform()) + ")", false);
@@ -644,7 +668,11 @@ private:
std::string char_rule = _add_primitive("char", PRIMITIVE_RULES.at("char"));
std::ostringstream out;
out << "[\"] ( ";
std::function<void(size_t)> visit = [&](size_t idx) {
// one level per char of the longest name
std::function<void(size_t, int)> visit = [&](size_t idx, int depth) {
if (depth >= COMMON_RECURSIVE_LIMIT) {
throw std::invalid_argument("JSON schema property name is longer than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " chars");
}
const auto & node = trie.nodes[idx];
std::string rejects;
auto first = true;
@@ -659,7 +687,7 @@ private:
out << "[" << c << "]";
if (!trie.nodes[child].children.empty()) {
out << " (";
visit(child);
visit(child, depth + 1);
out << ")";
} else {
out << " " << char_rule << "+";
@@ -669,7 +697,7 @@ private:
out << " | [^\"" << rejects << "] " << char_rule << "*";
}
};
visit(0);
visit(0, 0);
out << " )";
if (trie.nodes[0].pattern < 0) {
@@ -682,8 +710,7 @@ private:
std::string _resolve_ref(const common_chat_schema_ref & schema) {
auto it = schema.ref.find('#');
std::string ref_fragment = it != std::string::npos ? schema.ref.substr(it + 1) : schema.ref;
static const std::regex nonalphanumeric_regex(R"([^a-zA-Z0-9-]+)");
std::string ref_name = "ref" + std::regex_replace(ref_fragment, nonalphanumeric_regex, "-");
std::string ref_name = "ref" + gbnf_format_rule_name(ref_fragment);
if (_rules.find(ref_name) == _rules.end() && _refs_being_resolved.find(schema.ref) == _refs_being_resolved.end()) {
if (!schema.target) {
_errors.push_back("Unresolved $ref " + schema.ref);
@@ -833,10 +860,13 @@ public:
std::unordered_set<std::string> required;
std::vector<std::pair<std::string, const common_chat_schema *>> properties;
std::map<std::string, size_t> enum_values;
std::function<void(const common_chat_schema &, bool)> add_component = [&](const common_chat_schema & comp, bool is_required) {
std::function<void(const common_chat_schema &, bool, int)> add_component = [&](const common_chat_schema & comp, bool is_required, int depth) {
if (depth >= COMMON_RECURSIVE_LIMIT) {
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
}
if (comp.kind() == common_chat_schema::KIND_REF) {
if (const auto * target = as<common_chat_schema_ref>(comp).target) {
add_component(*target, is_required);
add_component(*target, is_required, depth + 1);
}
} else if (comp.kind() == common_chat_schema::KIND_OBJECT) {
for (const auto & prop : as<common_chat_schema_object>(comp).properties) {
@@ -854,10 +884,10 @@ public:
for (const auto & child : schema.children) {
if (child->kind() == common_chat_schema::KIND_ANY_OF) {
for (const auto & alt : as<common_chat_schema_any_of>(*child).children) {
add_component(*alt, false);
add_component(*alt, false, 0);
}
} else {
add_component(*child, true);
add_component(*child, true, 0);
}
}
if (!enum_values.empty()) {
@@ -875,6 +905,8 @@ public:
}
std::string visit(const common_chat_schema & schema, const std::string & name) {
// the $ref targets are visited too, a $ref chain is not bounded by the builder
depth_guard guard(_depth);
std::string rule_name = is_reserved_name(name) ? name + "-" : name.empty() ? "root" : name;
std::string sub_name = name + (name.empty() ? "" : "-");
+1
View File
@@ -19,5 +19,6 @@ struct common_grammar_options {
};
std::string gbnf_format_literal(const std::string & literal);
std::string gbnf_format_rule_name(const std::string & name);
std::string build_grammar(const std::function<void(const common_grammar_builder &)> & cb, const common_grammar_options & options = {});
+34 -10
View File
@@ -9,6 +9,18 @@
#include <utility>
#include <vector>
namespace {
struct depth_guard {
int & depth;
depth_guard(int & depth) : depth(depth) {
if (++depth > COMMON_RECURSIVE_LIMIT) {
throw std::invalid_argument("JSON schema is nested more than " + std::to_string(COMMON_RECURSIVE_LIMIT) + " levels deep");
}
}
~depth_guard() { depth--; }
};
} // namespace
class common_chat_schema_builder {
const common_json & root_;
common_chat_schema_document & doc_;
@@ -224,7 +236,14 @@ class common_chat_schema_builder {
return node;
}
int depth_ = 0;
common_chat_schema_ptr build_node(const common_json & schema, const std::string & path) {
depth_guard guard(depth_);
return build_node_impl(schema, path);
}
common_chat_schema_ptr build_node_impl(const common_json & schema, const std::string & path) {
if (!schema.is_object()) {
fail(path, "schema must be an object");
}
@@ -365,7 +384,9 @@ static common_chat_schema::value_type json_type(const common_json & value) {
return common_chat_schema::TYPE_OBJECT;
}
static common_chat_schema::type_set value_types_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {
// depth counts the $ref, anyOf and allOf hops, a $ref chain is not bounded by the builder
static common_chat_schema::type_set value_types_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited, int & depth) {
depth_guard guard(depth);
switch (s.kind()) {
case common_chat_schema::KIND_ANY:
return common_chat_schema::type_set::all();
@@ -399,21 +420,21 @@ static common_chat_schema::type_set value_types_impl(const common_chat_schema &
// a cycle contributes no type, to be safe
return {};
}
auto types = value_types_impl(*target, visited);
auto types = value_types_impl(*target, visited, depth);
visited.erase(target);
return types;
}
case common_chat_schema::KIND_ANY_OF: {
common_chat_schema::type_set types;
for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {
types |= value_types_impl(*child, visited);
types |= value_types_impl(*child, visited, depth);
}
return types;
}
case common_chat_schema::KIND_ALL_OF: {
auto types = common_chat_schema::type_set::all();
for (const auto & child : static_cast<const common_chat_schema_all_of &>(s).children) {
types &= value_types_impl(*child, visited);
types &= value_types_impl(*child, visited, depth);
}
return types;
}
@@ -423,10 +444,12 @@ static common_chat_schema::type_set value_types_impl(const common_chat_schema &
common_chat_schema::type_set common_chat_schema::value_types() const {
std::unordered_set<const common_chat_schema *> visited;
return value_types_impl(*this, visited);
int depth = 0;
return value_types_impl(*this, visited, depth);
}
static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {
static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited, int & depth) {
depth_guard guard(depth);
switch (s.kind()) {
case common_chat_schema::KIND_STRING:
return true;
@@ -445,13 +468,13 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
if (!target || !visited.insert(target).second) {
return false;
}
bool result = may_be_string_impl(*target, visited);
bool result = may_be_string_impl(*target, visited, depth);
visited.erase(target);
return result;
}
case common_chat_schema::KIND_ANY_OF:
for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {
if (may_be_string_impl(*child, visited)) {
if (may_be_string_impl(*child, visited, depth)) {
return true;
}
}
@@ -463,7 +486,7 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
if (child->kind() == common_chat_schema::KIND_ANY) {
continue;
}
if (!may_be_string_impl(*child, visited)) {
if (!may_be_string_impl(*child, visited, depth)) {
return false;
}
any_string = true;
@@ -477,7 +500,8 @@ static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<
bool common_chat_schema::may_be_string() const {
std::unordered_set<const common_chat_schema *> visited;
return may_be_string_impl(*this, visited);
int depth = 0;
return may_be_string_impl(*this, visited, depth);
}
const char * common_chat_schema::kind_name(node_kind kind) {
+53 -10
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;
+27 -15
View File
@@ -10,10 +10,12 @@
#include <initializer_list>
#include <map>
#include <memory>
#include <regex>
#include <set>
#include <stdexcept>
// The parser can run with a lower limit than COMMON_RECURSIVE_LIMIT
static constexpr int RULE_RECURSIVE_LIMIT = 64;
// Trick to catch missing branches
template <typename T>
inline constexpr bool is_always_false_v = false;
@@ -186,13 +188,17 @@ std::string common_peg_ast_node::sanitized_text() const {
}
void common_peg_ast_arena::visit(common_peg_ast_id id, const common_peg_ast_visitor & visitor) const {
if (id == COMMON_PEG_INVALID_AST_ID) {
return;
}
const auto & node = get(id);
visitor(node);
for (const auto & child : node.children) {
visit(child, visitor);
// no recursion: the AST is several nodes deep per level of the input
std::vector<common_peg_ast_id> stack = { id };
while (!stack.empty()) {
const auto cur = stack.back();
stack.pop_back();
if (cur == COMMON_PEG_INVALID_AST_ID) {
continue;
}
const auto & node = get(cur);
visitor(node);
stack.insert(stack.end(), node.children.rbegin(), node.children.rend());
}
}
@@ -736,8 +742,15 @@ struct parser_executor {
}
common_peg_parse_result operator()(const common_peg_rule_parser & p) {
// only rules recurse, a recursive rule is entered once per input level
if (ctx.rule_depth[p.child] >= RULE_RECURSIVE_LIMIT) {
throw common_peg_recursion_error("input is nested more than " + std::to_string(RULE_RECURSIVE_LIMIT) + " levels deep");
}
// Parse the child
ctx.rule_depth[p.child]++;
auto result = arena.parse(p.child, ctx, start_pos);
ctx.rule_depth[p.child]--;
if (!result.fail()) {
std::string_view text;
@@ -819,6 +832,10 @@ common_peg_parse_result common_peg_arena::parse(common_peg_parse_context & ctx,
if (root_ == COMMON_PEG_INVALID_PARSER_ID) {
throw std::runtime_error("No root parser set");
}
// Initialize rule depth for all parsers
ctx.rule_depth.assign(parsers_.size(), 0);
return parse(root_, ctx, start);
}
@@ -1069,11 +1086,6 @@ common_peg_parser operator|(const std::string & str, const common_peg_parser & p
return operator|(str.c_str(), p);
}
static std::string rule_name(const std::string & name) {
static const std::regex invalid_rule_chars_re("[^a-zA-Z0-9-]+");
return std::regex_replace(name, invalid_rule_chars_re, "-");
}
common_peg_parser_builder::common_peg_parser_builder() {}
common_peg_parser common_peg_parser_builder::sequence(const std::vector<common_peg_parser_id> & parsers) {
@@ -1155,14 +1167,14 @@ common_peg_parser common_peg_parser_builder::schema(const common_peg_parser & p,
}
common_peg_parser common_peg_parser_builder::rule(const std::string & name, const common_peg_parser & p, bool trigger) {
auto clean_name = rule_name(name);
auto clean_name = gbnf_format_rule_name(name);
auto rule_id = arena_.add_parser(common_peg_rule_parser{clean_name, p.id(), trigger});
arena_.add_rule(clean_name, rule_id);
return ref(clean_name);
}
common_peg_parser common_peg_parser_builder::rule(const std::string & name, const std::function<common_peg_parser()> & builder_fn, bool trigger) {
auto clean_name = rule_name(name);
auto clean_name = gbnf_format_rule_name(name);
if (arena_.has_rule(clean_name)) {
return ref(clean_name);
}
+6
View File
@@ -6,6 +6,7 @@
#include <memory>
#include <set>
#include <stdexcept>
#include <unordered_map>
#include <string>
#include <string_view>
@@ -182,6 +183,10 @@ inline common_peg_parse_flags operator~(common_peg_parse_flags a) {
return static_cast<common_peg_parse_flags>(~int(a));
}
struct common_peg_recursion_error : std::invalid_argument {
using std::invalid_argument::invalid_argument;
};
struct common_peg_parse_context {
std::string input; // [h, e, l, l, o, _, w, o, r, l, d]
std::vector<llama_token> tokens; // [id, -1, -1, -1, -1, id, -1, -1, -1, -1, -1]
@@ -189,6 +194,7 @@ struct common_peg_parse_context {
common_peg_ast_arena ast;
int parse_depth;
std::vector<int> rule_depth; // index: rule child id
common_peg_parse_context(common_peg_parse_flags flags = COMMON_PEG_PARSE_FLAG_NONE)
: flags(flags), parse_depth(0) {}
+113 -58
View File
@@ -16,8 +16,10 @@
#include <cassert>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <map>
#include <random>
#include <cinttypes>
#define SPC_DBG(fmt, ...) LOG_DBG("spec %12.*s: " fmt, 12, __func__, __VA_ARGS__)
@@ -1009,6 +1011,10 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
bool is_mrope = false;
int32_t selector_top_k = 0;
// DFlash2 probabilistic drafting: one generator per sequence and the seed it was last seeded with (-1: reseed)
std::vector<std::mt19937> dflash2_rng;
std::vector<int64_t> dflash2_seed;
// draft-dspark: the draft carries a Markov head and uses an anchor-first block layout
const bool is_dspark;
@@ -1059,6 +1065,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
selector_top_k = llama_model_dflash_selector_top_k(model_dft);
is_dflash2 = selector_top_k > 0;
dflash2_rng.resize(n_seq);
dflash2_seed.assign(n_seq, -1);
char lilicorr_meta[32] = {};
lilicorr_top_k = llama_model_meta_val_str(model_dft, "lilicorr.candidate_topk", lilicorr_meta, sizeof(lilicorr_meta)) >= 0
? std::atoi(lilicorr_meta) : 0;
@@ -1157,6 +1165,9 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
return;
}
// reseed per request rather than per round, or two identical requests differ
dflash2_seed[seq_id] = -1;
const int32_t N = (int32_t) prompt.size();
if (N <= 0) {
return;
@@ -1337,6 +1348,21 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
const float * lattice = llama_get_embeddings_nextn(ctx_dft);
GGML_ASSERT(lattice && "DFlash2 selector produced no lattice");
// DFlash2 can instead sample each step and hand q to the verifier for rejection sampling
const bool sample = is_dflash2 && params.probabilistic && dp.result_q;
// greedy keeps temperature 1, sampling uses the target's
const float temp = sample ? dp.temp : 1.0f;
if (sample && dflash2_seed[seq_id] != dp.seed) {
dflash2_seed[seq_id] = dp.seed;
// the default seed means random; mixed so it differs from the target's, as in spec_retune
dflash2_rng[seq_id].seed(dp.seed == LLAMA_DEFAULT_SEED ? std::random_device{}() : dp.seed ^ 0x85ebca6bu);
}
std::vector<float> probs;
std::vector<int32_t> sorted_idx;
int32_t predecessor = 0;
for (int32_t i = 1; i < (is_dflash2 ? n_block_tokens : params.n_max + 1); ++i) {
const float * row = lattice + (size_t) (beg + i) * n_embd_dec;
@@ -1345,21 +1371,53 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
predecessor = (int32_t) std::distance(scores,
std::max_element(scores, scores + top_k));
if (params.p_min > 0.0f) {
// softmax(scores) at the argmax, i.e. 1 / sum(exp(s_k - s_max))
if (params.p_min > 0.0f || sample) {
// softmax(scores / temp) at the argmax, i.e. 1 / sum(exp((s_k - s_max) / temp))
probs.resize(top_k);
float sum = 0.0f;
for (int32_t k = 0; k < top_k; ++k) {
sum += std::exp(scores[k] - scores[predecessor]);
probs[k] = std::exp((scores[k] - scores[predecessor]) / temp);
sum += probs[k];
}
if (1.0f / sum < params.p_min) {
break;
}
for (int32_t k = 0; k < top_k; ++k) {
probs[k] /= sum;
}
}
if (sample) {
// ggml_top_k leaves candidates unordered, so walk them by token id to keep a seed's draft stable
sorted_idx.resize(top_k);
for (int32_t k = 0; k < top_k; ++k) {
sorted_idx[k] = k;
}
std::sort(sorted_idx.begin(), sorted_idx.end(), [row](int32_t a, int32_t b) { return row[a] < row[b]; });
// a round-off overshoot keeps the argmax, which has q > 0
double u = std::uniform_real_distribution<double>(0.0, 1.0)(dflash2_rng[seq_id]);
for (const int32_t k : sorted_idx) {
u -= probs[k];
if (u < 0.0) {
predecessor = k;
break;
}
}
auto & q = dp.result_q->emplace_back();
q.reserve(top_k);
for (int32_t k = 0; k < top_k; ++k) {
q.push_back({ (llama_token) row[k], scores[k], probs[k] });
}
}
result.push_back((llama_token) row[predecessor]);
}
if (result.size() < (size_t) params.n_min) {
result.clear();
if (sample) {
dp.result_q->clear();
}
}
continue;
}
@@ -2152,9 +2210,6 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
uint16_t n_draft;
bool save_dynamic;
bool save_static;
struct seq_info {
size_t cache_size = 0; // number of tokens in n-gram cache
@@ -2168,54 +2223,72 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
common_speculative_impl_ngram_cache(
const common_params_speculative & params,
uint32_t n_seq,
uint16_t n_draft,
const std::string & path_static,
const std::string & path_dynamic,
bool save_dynamic,
bool save_static)
uint16_t n_draft)
: common_speculative_impl(COMMON_SPECULATIVE_TYPE_NGRAM_CACHE, n_seq, n_draft)
, params(params.ngram_cache)
, n_draft(n_draft)
, save_dynamic(save_dynamic)
, save_static(save_static)
{
const auto & ngram = params.ngram_cache;
SPC_TRC("%s", "adding speculative implementation 'ngram-cache'\n");
SPC_TRC("- n_draft=%d, cache_static=%s, cache_dynamic=%s\n",
n_draft,
path_static.empty() ? "none" : path_static.c_str(),
path_dynamic.empty() ? "none" : path_dynamic.c_str());
ngram.lookup_cache_static.empty() ? "none" : ngram.lookup_cache_static.c_str(),
ngram.lookup_cache_dynamic.empty() ? "none" : ngram.lookup_cache_dynamic.c_str());
sinfos.resize(n_seq);
if (!path_static.empty()) {
if (!ngram.lookup_cache_static.empty()) {
try {
auto ngram_cache_static = common_ngram_cache_load(path_static);
auto ngram_cache_static = common_ngram_cache_load(ngram.lookup_cache_static);
for (auto & sinfo : sinfos) {
sinfo.ngram_cache_static = ngram_cache_static;
}
} catch (...) {
SPC_ERR("failed to open static lookup cache: %s", path_static.c_str());
} catch (const std::ifstream::failure &) {
SPC_ERR("failed to open static lookup cache: %s\n", ngram.lookup_cache_static.c_str());
GGML_ABORT("Couldn't read static lookup cache");
}
}
if (!path_dynamic.empty()) {
if (!ngram.lookup_cache_dynamic.empty()) {
try {
auto ngram_cache_dynamic = common_ngram_cache_load(path_dynamic);
auto ngram_cache_dynamic = common_ngram_cache_load(ngram.lookup_cache_dynamic);
for (auto & sinfo : sinfos) {
sinfo.ngram_cache_dynamic = ngram_cache_dynamic;
}
} catch (...) {
SPC_ERR("failed to open dynamic lookup cache: %s", path_dynamic.c_str());
GGML_ABORT("Couldn't read dynamic lookup cache");
} catch (const std::ifstream::failure &) {
std::error_code ec;
const bool exists = std::filesystem::exists(ngram.lookup_cache_dynamic, ec);
if (!exists && !ec) {
// missing file is created on destruction
SPC_WRN("dynamic lookup cache file does not exist: %s, starting with empty cache\n",
ngram.lookup_cache_dynamic.c_str());
} else {
SPC_ERR("failed to open dynamic lookup cache: %s\n", ngram.lookup_cache_dynamic.c_str());
GGML_ABORT("Couldn't read dynamic lookup cache");
}
}
}
}
void begin(llama_seq_id /*seq_id*/, const llama_tokens & /*prompt*/) override {
// noop
~common_speculative_impl_ngram_cache() override {
if (params.lookup_cache_dynamic.empty() || sinfos.empty()) {
return;
}
auto & dynamic_cache = sinfos.front().ngram_cache_dynamic;
for (auto & sinfo : sinfos) {
common_ngram_cache_merge(dynamic_cache, sinfo.ngram_cache_context);
}
SPC_INF("saving dynamic lookup cache at %s\n", params.lookup_cache_dynamic.c_str());
common_ngram_cache_save(dynamic_cache, params.lookup_cache_dynamic);
}
void begin(llama_seq_id seq_id, const llama_tokens & /*prompt*/) override {
sinfos[seq_id].cache_size = 0;
}
void draft_one(
@@ -2226,22 +2299,6 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
const auto & prompt = *dparams.prompt;
if (sinfo.cache_size < prompt.size() + 1) {
llama_tokens tokens_new;
tokens_new.reserve(prompt.size() + 1 - sinfo.cache_size);
for (size_t j = sinfo.cache_size; j < prompt.size(); ++j) {
tokens_new.push_back(prompt[j]);
}
tokens_new.push_back(dparams.id_last); // add the last token
// Update context ngram cache with new dparams.prompt:
common_ngram_cache_update(
sinfo.ngram_cache_context,
LLAMA_NGRAM_MIN, LLAMA_NGRAM_MAX,
tokens_new, tokens_new.size(), false);
sinfo.cache_size = prompt.size() + 1;
}
llama_tokens inp;
inp.reserve(prompt.size() + 1);
for (size_t j = 0; j < prompt.size(); ++j) {
@@ -2249,6 +2306,16 @@ struct common_speculative_impl_ngram_cache : public common_speculative_impl {
}
inp.push_back(dparams.id_last);
if (sinfo.cache_size < inp.size()) {
// Update context ngram cache with the full history so n-grams
// ending at the new tokens have their preceding context
common_ngram_cache_update(
sinfo.ngram_cache_context,
LLAMA_NGRAM_MIN, LLAMA_NGRAM_MAX,
inp, inp.size() - sinfo.cache_size, false);
sinfo.cache_size = inp.size();
}
result.push_back(dparams.id_last);
common_ngram_cache_draft(
@@ -2309,20 +2376,12 @@ static common_ngram_map get_common_ngram_map(
return common_ngram_map(size_key, size_value, key_only, min_hits);
}
static common_speculative_impl_ngram_cache create_state_ngram_cache(
static std::unique_ptr<common_speculative_impl_ngram_cache> create_state_ngram_cache(
const common_speculative_config & config,
uint32_t n_seq,
const std::string & path_static,
const std::string & path_dynamic) {
uint32_t n_seq) {
uint16_t n_draft = 8; // TODO get from config?
// TODO bool param in common/common.h to set save_static/save_dynamic?
bool save_static = false;
bool save_dynamic = false;
common_speculative_impl_ngram_cache state(config.params, n_seq, n_draft, path_static, path_dynamic, save_static, save_dynamic);
return state;
return std::make_unique<common_speculative_impl_ngram_cache>(config.params, n_seq, n_draft);
}
std::string common_speculative_type_name_str(const std::vector<common_speculative_type> & types) {
@@ -2822,11 +2881,7 @@ common_speculative * common_speculative_init(common_params_speculative & params,
break;
}
case COMMON_SPECULATIVE_TYPE_NGRAM_CACHE: {
auto state = create_state_ngram_cache(
config, n_seq,
params.ngram_cache.lookup_cache_static,
params.ngram_cache.lookup_cache_dynamic);
impls.push_back(std::make_unique<common_speculative_impl_ngram_cache>(state));
impls.push_back(create_state_ngram_cache(config, n_seq));
break;
}
default:
+1 -1
View File
@@ -73,7 +73,7 @@ struct common_speculative_draft_params {
// the generated draft from the last _draft() call
llama_tokens * result;
// candidate distribution per drafted token; set it to make draft-simple and draft-mtp sample
// candidate distribution per drafted token; set it to make draft-simple, draft-mtp and draft-dflash (DFlash2 only) sample
std::vector<std::vector<llama_token_data>> * result_q = nullptr;
// the target's temp and seed, read only when the drafter samples probabilistically
+14
View File
@@ -17,8 +17,10 @@ __all__ = [
TEXT_MODEL_MAP: dict[str, str] = {
"NEOChatModel": "sensenova_u1",
"AfmoeForCausalLM": "afmoe",
"LagunaForCausalLM": "laguna",
"FlintForCausalLM": "flint",
"ApertusForCausalLM": "llama",
"ArceeForCausalLM": "llama",
"ArcticForCausalLM": "arctic",
@@ -68,6 +70,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"LingDSparkModel": "qwen",
"DeepseekV4ForCausalLM": "deepseek",
"DeepseekV4DSparkModel": "deepseek",
"DeepseekV41ForCausalLM": "deepseek41",
"DistilBertForMaskedLM": "bert",
"DistilBertForSequenceClassification": "bert",
"DistilBertModel": "bert",
@@ -194,13 +197,16 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MiniCPMForCausalLM": "minicpm",
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
"MiniCPMOcrForConditionalGeneration": "minicpm",
"MiniMaxText01ForCausalLM": "minimax",
"MiniMaxM1ForCausalLM": "minimax",
"MiniMaxM2ForCausalLM": "minimax",
"MiniMaxM3SparseForCausalLM": "minimax",
"MiniMaxM3SparseForConditionalGeneration": "minimax",
"Ministral3ForCausalLM": "mistral3",
"Ministral3Model": "mistral3",
"Mistral3ForConditionalGeneration": "mistral3",
"Mistral3Model": "mistral3",
"VoxtralRealtimeForConditionalGeneration": "mistral3",
"MistralForCausalLM": "llama",
"MixtralForCausalLM": "llama",
@@ -226,6 +232,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MuseSparkForConditionalGeneration": "muse_spark",
"OpenELMForCausalLM": "openelm",
"OpenJevModel": "qwen",
"H2OLightningModel": "h2o_lightning",
"OrionForCausalLM": "orion",
"PLMForCausalLM": "plm",
"PLaMo2ForCausalLM": "plamo",
@@ -280,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",
@@ -319,6 +327,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"VllmD3Model": "vllm_d3",
"DeepseekOCR2ForCausalLM": "deepseek",
"DeepseekOCRForCausalLM": "deepseek",
"DeepseekV41ForCausalLM": "deepseek41",
"DeepseekV4ForCausalLM": "deepseek",
"Dots3NoteForCausalLM": "dots3",
"Dots3NoteForConditionalGeneration": "dots3",
@@ -356,13 +365,16 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"MiniMaxM3SparseForConditionalGeneration": "minimax",
"MiniCPMV4_6ForConditionalGeneration": "minicpm",
"MiniCPMV4_7ForConditionalGeneration": "minicpm",
"MiniCPMOcrForConditionalGeneration": "minicpm",
"Mistral3ForConditionalGeneration": "llava",
"Mistral3Model": "llava",
"NemotronH_Omni_Reasoning_V3": "nemotron",
"NemotronH_Nano_VL_V2": "nemotron",
"MuseGlimmerForConditionalGeneration": "muse_glimmer",
"MuseSparkForConditionalGeneration": "muse_spark",
"PaddleOCRVisionModel": "ernie",
"Phi4ForCausalLMV": "phi",
"NEOVisionModel": "sensenova_u1",
"Qwen2AudioForConditionalGeneration": "ultravox",
"Qwen2VLForConditionalGeneration": "qwenvl",
"Qwen2VLModel": "qwenvl",
@@ -371,10 +383,12 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Qwen3ASRForConditionalGeneration": "qwen3vl",
"Qwen3OmniMoeForConditionalGeneration": "qwen3vl",
"PocketTTSModel": "pockettts",
"SopranoModel": "soprano",
"Qwen3TTSForConditionalGeneration": "qwen3tts",
"Qwen3VLForConditionalGeneration": "qwen3vl",
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
"OpenJevModel": "qwen3vl",
"H2OLightningModel": "h2o_lightning",
"Qwen3_5ForConditionalGeneration": "qwen3vl",
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
+8 -2
View File
@@ -1701,7 +1701,7 @@ class TextModel(ModelBase):
self.gguf_writer.add_expert_group_used_count(n_group_used)
logger.info(f"gguf: expert groups used count = {n_group_used}")
if (score_func := self.find_hparam(["score_function", "scoring_func", "score_func", "moe_router_activation", "moe_router_activation_func", "expert_selection_fn", "router_score_func"], optional=True)) is not None:
if (score_func := self.find_hparam(["score_function", "scoring_func", "score_func", "moe_router_activation", "moe_router_activation_func", "expert_selection_fn", "router_score_func", "router_score_function"], optional=True)) is not None:
if score_func == "sigmoid":
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
elif score_func == "softmax":
@@ -1837,6 +1837,9 @@ class TextModel(ModelBase):
if chkhsh == "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35":
# ref: https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0
res = "minerva-7b"
if chkhsh == "6e4d72c6e5260b90606e26a0931fa15e4b42ec6e97719d12919f2b1f93478262":
# ref: https://huggingface.co/domyn/Domyn-Small-v1.0
res = "domyn-small"
if chkhsh == "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664":
# ref: https://huggingface.co/tencent/Hunyuan-A13B-Instruct
res = "hunyuan"
@@ -1939,6 +1942,9 @@ class TextModel(ModelBase):
if chkhsh == "52df12b4c8d4176e7481aab4b6e8454d1fd0a210a04a574f6d4e067d10e23c3e":
# ref: https://huggingface.co/CohereLabs/North-Mini-Code-1.0
res = "cohere2moe"
if chkhsh == "d5420e601e979078c670bc503d9ac411c311fa76b76a76267c31e0d6ba816637":
# ref: https://huggingface.co/RecursiveSI/flint
res = "flint"
if chkhsh == "e636dc30a262dcc0d8c323492e32ae2b70728f4df7dfe9737d9f920a282b8aea":
# ref: https://huggingface.co/Qwen/Qwen1.5-7B
res = "qwen2"
@@ -3143,7 +3149,7 @@ def get_model_architecture(hparams: dict[str, Any], model_type: ModelType) -> st
# Kimi-K3's text_config reports "KimiLinearForCausalLM", which is the older
# Kimi-Linear-48B architecture and cannot load K3 (no attention residuals,
# latent MoE, situ, ...). Route on the top-level architecture instead.
if model_type == ModelType.TEXT and arch in ("StepVLForConditionalGeneration", "Sarashina2VisionForCausalLM", "Exaone4_5_ForConditionalGeneration", "Step3p7ForConditionalGeneration", "KimiK3ForConditionalGeneration"):
if model_type == ModelType.TEXT and arch in ("NEOChatModel", "StepVLForConditionalGeneration", "Sarashina2VisionForCausalLM", "Exaone4_5_ForConditionalGeneration", "Step3p7ForConditionalGeneration", "KimiK3ForConditionalGeneration"):
return arch
# if "architectures" is found in the sub-config, use that instead
+4 -7
View File
@@ -803,9 +803,6 @@ class DeepseekV4Model(TextModel):
self._dsv4_mxfp4_generated = True
return ()
def _format_dsv4_tensor_name(self, key: gguf.MODEL_TENSOR, bid: int | None, suffix: str = ".weight") -> str:
return self.format_tensor_name(key, bid, suffix)
def _map_dsv4_tensor_name(self, name: str, bid: int | None) -> tuple[gguf.MODEL_TENSOR, str]:
root_map: dict[str, tuple[gguf.MODEL_TENSOR, str]] = {
"embed.weight": (gguf.MODEL_TENSOR.TOKEN_EMBD, ".weight"),
@@ -879,17 +876,17 @@ class DeepseekV4Model(TextModel):
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if re.match(r"layers\.\d+\.ffn\.experts\.\d+\.w[123]\.(weight|scale)$", name):
return []
return
# hash layers route text tokens via tid2eid and image tokens via bias_vl; gate.bias is unused
if name.endswith(".ffn.gate.bias") and bid is not None and bid < self.hparams["num_hash_layers"]:
return []
return
tensor_key, suffix = self._map_dsv4_tensor_name(name, bid)
if tensor_key == gguf.MODEL_TENSOR.FFN_GATE_TID2EID:
return []
return
return [(self._format_dsv4_tensor_name(tensor_key, bid, suffix), data_torch)]
yield self.format_tensor_name(tensor_key, bid, suffix), data_torch
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
del bid # unused
+464
View File
@@ -0,0 +1,464 @@
from __future__ import annotations
import json
import math
import re
from pathlib import Path
from typing import Any, Callable, Iterable, TYPE_CHECKING, cast
import numpy as np
import torch
if TYPE_CHECKING:
from torch import Tensor
from .base import LazyTorchTensor, ModelBase, TextModel, gguf
from .deepseek import DeepseekV4FlashVisionModel, DeepseekV4Model
# Engram hash constants from inference/engram.py, stored in the GGUF: the token map needs the HF tokenizers
# normalizers and the multipliers numpy's PCG64 stream, neither of which the runtime has.
def _is_prime(n: int) -> bool:
if n < 2 or n % 2 == 0:
return n == 2
return all(n % d for d in range(3, math.isqrt(n) + 1, 2))
def engram_primes(n_layers: int, max_ngram_size: int, n_heads: int, vocab_size: int) -> list[list[list[int]]]:
"""EngramLayout.from_args: [layer][ngram order 2..max][head] bucket moduli, never reused across layers."""
seen: set[int] = set()
out: list[list[list[int]]] = []
for _ in range(n_layers):
per_ngram = []
for _ in range(max_ngram_size - 1):
sizes, cur = [], vocab_size - 1
for _ in range(n_heads):
cand = cur + 1
while not _is_prime(cand) or cand in seen:
cand += 1
cur = cand
seen.add(cur)
sizes.append(cur)
per_ngram.append(sizes)
out.append(per_ngram)
return out
def engram_multipliers(layer_ids: list[int], max_ngram_size: int, compressed_vocab_size: int) -> list[list[int]]:
"""compute_hash_multipliers: odd, bounded so that token_id * multiplier cannot overflow int64."""
bound = max(1, (np.iinfo(np.int64).max // compressed_vocab_size) // 2)
rows = []
for layer_id in layer_ids:
rng = np.random.default_rng(10007 * layer_id)
values = rng.integers(low=0, high=bound, size=(max_ngram_size,), dtype=np.int64)
rows.append([int(v) * 2 + 1 for v in values])
return rows
def engram_compressed_token_map(tokenizer_dir: Path) -> tuple[list[int], int]:
"""build_compressed_token_map: token id -> compressed id (tokens that normalize alike collapse)."""
from tokenizers import Regex, normalizers
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(tokenizer_dir)
sentinel = "\ue000"
normalizer = normalizers.Sequence([
normalizers.NFKC(),
normalizers.NFD(),
normalizers.StripAccents(),
normalizers.Lowercase(),
normalizers.Replace(Regex(r"[ \t\r\n]+"), " "),
normalizers.Replace(Regex(r"^ $"), sentinel),
normalizers.Strip(),
normalizers.Replace(sentinel, " "),
])
backend = tokenizer.backend_tokenizer
key_to_new: dict[str, int] = {}
lookup = [0] * len(tokenizer)
for token_id in range(len(tokenizer)):
text = backend.decode([token_id], skip_special_tokens=False)
if "\ufffd" in text:
key = backend.id_to_token(token_id)
else:
normalized = normalizer.normalize_str(text)
key = normalized if normalized else text
lookup[token_id] = key_to_new.setdefault(key, len(key_to_new))
return lookup, len(key_to_new)
class _StreamedBytes:
"""Raw tensor bytes produced chunk by chunk at write time (an Engram table is ~200 GB in BF16)."""
def __init__(self, shape: tuple[int, ...], chunks: Callable[[], Iterable[np.ndarray]]):
self.shape = shape
self.dtype = np.dtype(np.uint8)
self.nbytes = int(np.prod(shape))
self._chunks = chunks
def tofile(self, fout) -> None:
written = 0
for chunk in self._chunks():
chunk = np.ascontiguousarray(chunk, dtype=np.uint8)
chunk.tofile(fout)
written += chunk.nbytes
if written != self.nbytes:
raise ValueError(f"streamed {written} bytes, expected {self.nbytes}")
def byteswap(self, inplace: bool = False):
raise NotImplementedError("streamed tensors are written little-endian only")
def _e8m0_check(scale_u8: np.ndarray, name: str) -> None:
# e4m3 (|x| in [2^-9, 448]) * 2^(e - 127) is exact in BF16 for e in [3, 246]
if scale_u8.size and (int(scale_u8.min()) < 3 or int(scale_u8.max()) > 246):
raise ValueError(f"{name}: E8M0 scale exponents outside [3, 246], the dequantized values would not be exact")
@ModelBase.register("DeepseekV41ForCausalLM")
class DeepseekV41Model(DeepseekV4Model):
"""DeepSeek-V4.1 text model, lossless: FP8 (e4m3, 32x32 ue8m0 blocks) linears dequantized to BF16 (F32 with
--outtype f32), routed experts copied bit-exact as MXFP4, Engram tables (e4m3 rows, one e8m0 scale per 32) as the
exact BF16 lookup (F32 with --outtype f32), router and Engram gate F32. DSpark (mtp.*) is not converted here
(--dspark, DeepseekV41DSparkModel); the vision tower goes to an mmproj (--mmproj, DeepseekV41VisionModel).
The Engram tables of all layers are written as one per_layer_token_embd (rows concatenated in layer order) with
the PLE keys of qwen4exp: absolute head offsets and bucket counts, so the runtime can read it lazily."""
model_arch = gguf.MODEL_ARCH.DEEPSEEK41
supports_mtp_export = False
_ENGRAM_EMBED_RE = re.compile(r"layers\.(\d+)\.engram\.embed\.(weight|scale)$")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hparams.setdefault("num_hash_layers", 0) # no hash-routed layers in V4.1
self._f32 = self.ftype == gguf.LlamaFileType.ALL_F32
qc = self.hparams.get("quantization_config") or {}
self._fp8_block = tuple(qc.get("weight_block_size") or (32, 32))
if qc.get("scale_fmt", "ue8m0") != "ue8m0":
raise ValueError(f"unsupported FP8 scale format {qc.get('scale_fmt')!r}")
if self.hparams.get("gate_temp", 1.0) != 1.0:
raise ValueError("gate_temp != 1 is not supported (the router logits would need dividing by it)")
# V4.1 does not use the DeepSeek-V4 template; a template shipped with the model is still added by set_vocab
self.gguf_writer.remove_key(gguf.Keys.Tokenizer.CHAT_TEMPLATE)
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
# num_hidden_layers lives in text_config, which is merged only after indexing; MTP stages are skipped anyway
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
def set_gguf_parameters(self):
super().set_gguf_parameters()
hp = self.hparams
w = self.gguf_writer
w.add_attention_kv_source_layers(hp["kv_source_layer_ids"])
w.add_indexer_source_layers(hp["index_source_layer_ids"])
if hp.get("candidate_source_layer_id", -1) >= 0:
w.add_indexer_candidate_source_layer(hp["candidate_source_layer_id"])
w.add_indexer_candidate_top_k_blocks(hp["candidate_topk_blocks"])
w.add_indexer_candidate_block_size(hp["candidate_block_size"])
# GGUF cannot hold empty arrays: the Engram keys are omitted when the model has no Engram layers
layer_ids = list(hp.get("engram_layer_ids") or [])
if layer_ids:
max_ngram, n_heads = hp["engram_max_ngram_size"], hp["engram_n_heads"]
primes = engram_primes(len(layer_ids), max_ngram, n_heads, hp["engram_vocab_size"])
for li, layer_primes in enumerate(primes):
if sum(p for per in layer_primes for p in per) != hp["engram_num_embeddings"][li]:
raise ValueError(f"engram layer {layer_ids[li]}: prime sum != num_embeddings")
token_map, cvocab = engram_compressed_token_map(self.dir_model)
if cvocab != hp["engram_compressed_vocab_size"] or len(token_map) != hp["vocab_size"]:
raise ValueError(f"compressed token map ({len(token_map)} -> {cvocab}) does not match the config")
# absolute row offsets into the concatenated table
sizes = [p for layer in primes for per in layer for p in per]
offsets = [sum(sizes[:i]) for i in range(len(sizes))]
w.add_key_value(gguf.Keys.PerLayerEmbedding.LAYERS.format(arch=w.arch), layer_ids,
gguf.GGUFValueType.ARRAY, gguf.GGUFValueType.UINT32)
w.add_ple_ngram_size(max_ngram)
w.add_ple_heads_per_ngram(n_heads)
w.add_embedding_length_per_layer_input(hp["engram_head_dim"])
w.add_ple_layer_multipliers([m for row in engram_multipliers(layer_ids, max_ngram, cvocab) for m in row])
w.add_ple_head_offsets(offsets)
w.add_ple_head_vocab_sizes(sizes)
w.add_engram_pad_token_id(hp["engram_pad_token_id"])
w.add_engram_token_map(token_map)
def dequant_model(self):
bo, bi = self._fp8_block
def dequant(weight: Tensor, scale: Tensor) -> Tensor:
out_f, in_f = weight.shape
s = self._e8m0_to_float(scale).repeat_interleave(bo, 0)[:out_f].repeat_interleave(bi, 1)[:, :in_f]
return weight.float() * s
for name in [n for n in self.model_tensors if n.endswith(".scale") and not self._ENGRAM_EMBED_RE.match(n)]:
weight_name = name.removesuffix(".scale") + ".weight"
if weight_name not in self.model_tensors or self.model_tensors[weight_name]().dtype not in self._float8_dtypes():
continue
w, s = self.model_tensors[weight_name], self.model_tensors.pop(name)
out_f, in_f = w().shape
if tuple(s().shape) != (-(-out_f // bo), -(-in_f // bi)):
raise ValueError(f"{weight_name}: scale shape {tuple(s().shape)} does not match {(bo, bi)} blocks")
_e8m0_check(LazyTorchTensor.to_eager(s()).view(torch.uint8).numpy(), name)
self.model_tensors[weight_name] = lambda w=w, s=s: dequant(w(), s())
self._dsv4_fp8_dequantized.add(weight_name)
def _tensor_part(self, name: str) -> Path:
index = self.dir_model / "model.safetensors.index.json"
if not index.is_file():
return self.dir_model / "model.safetensors"
with open(index, "r", encoding="utf-8") as f:
return self.dir_model / json.load(f)["weight_map"][name]
def _write_mxfp4_experts(self, bid: int, proj: str, key: gguf.MODEL_TENSOR, n_expert: int | None = None) -> None:
n_expert = self.hparams["n_routed_experts"] if n_expert is None else n_expert
names = [f"layers.{bid}.ffn.experts.{e}.{proj}" for e in range(n_expert)]
pairs = [(self.model_tensors.pop(n + ".weight"), self.model_tensors.pop(n + ".scale")) for n in names]
rows, packed_cols = pairs[0][0]().shape
def chunks() -> Iterable[np.ndarray]:
for w, s in pairs:
yield self.repack_mxfp4_blocks(LazyTorchTensor.to_eager(w()), LazyTorchTensor.to_eager(s()))
data = cast(np.ndarray, _StreamedBytes((len(pairs), rows, packed_cols * 2 // 32 * 17), chunks))
self.gguf_writer.add_tensor(self.format_tensor_name(key, bid), data, raw_dtype=gguf.GGMLQuantizationType.MXFP4)
def _write_engram_tables(self, layer_ids: list[int]) -> None:
# official lookup e4m3 * 2^(e8m0 - 127), exact in BF16
parts, dim = [], None
for bid in layer_ids:
wn, sn = f"layers.{bid}.engram.embed.weight", f"layers.{bid}.engram.embed.scale"
rows, d = self.model_tensors.pop(wn)().shape
if d % 32 != 0 or tuple(self.model_tensors.pop(sn)().shape) != (rows, d // 32) or d != (dim or d):
raise ValueError(f"{wn}: expected one e8m0 scale per 32 elements and the same width in every layer")
if rows != self.hparams["engram_num_embeddings"][len(parts)]:
raise ValueError(f"{wn}: {rows} rows, config says {self.hparams['engram_num_embeddings'][len(parts)]}")
dim = d
parts.append((wn, sn, rows, self._tensor_part(wn), self._tensor_part(sn)))
assert dim is not None
name = self.format_tensor_name(gguf.MODEL_TENSOR.PER_LAYER_TOKEN_EMBD)
n_rows = sum(p[2] for p in parts)
def chunks() -> Iterable[np.ndarray]:
from safetensors import safe_open
for wn, sn, rows, wpart, spart in parts:
with safe_open(wpart, framework="pt") as fw, safe_open(spart, framework="pt") as fs:
ws, ss = fw.get_slice(wn), fs.get_slice(sn)
for r0 in range(0, rows, 1 << 18):
q = ws[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 32)
e = ss[r0:r0 + (1 << 18)].view(torch.uint8).reshape(-1, 1)
_e8m0_check(e.numpy(), sn)
v = (q.view(torch.float8_e4m3fn).float() * torch.exp2(e.float() - 127)).reshape(-1, dim)
yield (v if self._f32 else v.bfloat16()).view(torch.uint8).numpy()
qt = gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
data = cast(np.ndarray, _StreamedBytes((n_rows, dim * (4 if self._f32 else 2)), chunks))
self.gguf_writer.add_tensor(name, data, raw_dtype=qt)
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
if not self._dsv4_mxfp4_generated:
for bid in range(self.block_count):
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP)
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP)
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP)
if self.hparams.get("engram_layer_ids"):
self._write_engram_tables(list(self.hparams["engram_layer_ids"]))
self._dsv4_mxfp4_generated = True
return ()
_LAYER_MAP: dict[str, tuple[gguf.MODEL_TENSOR, str]] = {
"attn.indexer.wk.weight": (gguf.MODEL_TENSOR.INDEXER_ATTN_K, ".weight"),
"attn.indexer.k_norm.weight": (gguf.MODEL_TENSOR.INDEXER_K_NORM, ".weight"),
"engram.wkv.weight": (gguf.MODEL_TENSOR.ENGRAM_KV, ".weight"),
}
def _map_dsv4_tensor_name(self, name: str, bid: int | None) -> tuple[gguf.MODEL_TENSOR, str]:
m = re.match(r"layers\.\d+\.(.+)$", name)
if m is not None and m.group(1) in self._LAYER_MAP:
return self._LAYER_MAP[m.group(1)]
return super()._map_dsv4_tensor_name(name, bid)
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.endswith(".engram.k_weight"):
return # folded into engram_gate
if name.endswith(".engram.q_weight"):
# Engram gate weight = q_weight * k_weight (bf16 * bf16 is exact in f32)
k = self.model_tensors[name.removesuffix("q_weight") + "k_weight"]
yield self.format_tensor_name(gguf.MODEL_TENSOR.ENGRAM_GATE, bid), data_torch.float() * k().float()
return
yield from super().modify_tensors(data_torch, name, bid)
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
if new_name.endswith((".engram_gate.weight", ".ffn_gate_inp.weight")) or name in self._dsv4_f32_tensors:
return gguf.GGMLQuantizationType.F32
if n_dims >= 2 and (name in self._dsv4_fp8_dequantized or name in self._dsv4_bf16_tensors):
return gguf.GGMLQuantizationType.F32 if self._f32 else gguf.GGMLQuantizationType.BF16
return False
class DeepseekV41DSparkModel(DeepseekV41Model):
"""DeepSeek-V4.1 DSpark speculative draft (mtp.0..2 of the checkpoint) as a DFLASH GGUF (`--dspark`).
Layout = the V4 DSpark draft (conversion/deepseek.py DeepseekV4DSparkModel) with V4.1's differences flagged by
`dflash.hyper_connection.single_pass`: stages blk.0..2 (MXFP4 experts bit-exact, dense BF16), mtp.0.main_proj /
main_norm -> fc / enc.output_norm, mtp.2.norm -> output_norm, markov_head.embed / head -> markov_w1 / markov_w2,
confidence_head.proj -> conf_proj (F32), no hc_head. Token embeddings and the lm head are the target's (shared at
runtime through ctx_other). target_layers are the V4.1 ids as-is: the draft reads the attention input of those
layers, which is what llama_set_embeddings_layer_inp extracts.
"""
model_arch = gguf.MODEL_ARCH.DFLASH
mtp_only = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
hp = self.hparams
self._n_main = hp["num_hidden_layers"]
self._n_stage = hp["num_nextn_predict_layers"]
if self._n_stage <= 0 or "dspark_block_size" not in hp:
raise ValueError("DeepSeek-V4.1 DSpark export needs num_nextn_predict_layers > 0 and the dspark_* config")
# stages keep main-model block ids (mtp.M -> layers.n_main+M); format_tensor_name() maps them back
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self._n_stage)
def index_tensors(self, remote_hf_model_id: str | None = None) -> dict[str, Callable[[], Tensor]]:
hp = {**self.hparams, **self.hparams.get("text_config", {})} # text_config is merged only after indexing
type(self)._dsv4_main_layers = hp["num_hidden_layers"]
type(self)._dsv4_nextn_layers = hp.get("num_nextn_predict_layers", 0)
return TextModel.index_tensors(self, remote_hf_model_id=remote_hf_model_id)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
name, gen = item
m = re.match(r"mtp\.(\d+)\.(.+)$", name)
if m is None:
return None # shared with the target at runtime
stage, rest = int(m.group(1)), m.group(2)
assert cls._dsv4_main_layers is not None
if stage >= cls._dsv4_nextn_layers:
raise ValueError(f"Unexpected DSpark stage {stage}")
if rest.startswith(("markov_head.", "confidence_head.")):
return f"dspark.{rest}", gen
return f"layers.{cls._dsv4_main_layers + stage}.{rest}", gen
def format_tensor_name(self, key: gguf.MODEL_TENSOR, bid: int | None = None, suffix: str = ".weight") -> str:
if bid is not None and bid >= self._n_main:
bid -= self._n_main
return super().format_tensor_name(key, bid, suffix)
def set_vocab(self):
super().set_vocab()
self.gguf_writer.add_mask_token_id(self.hparams["dspark_noise_token_id"])
def set_gguf_parameters(self):
hp = self.hparams
n = self._n_stage
w = self.gguf_writer
w.add_block_count(n)
w.add_context_length(hp["max_position_embeddings"])
w.add_embedding_length(hp["hidden_size"])
w.add_feed_forward_length(hp["moe_intermediate_size"])
w.add_head_count(hp["num_attention_heads"])
w.add_head_count_kv(1)
w.add_key_length(hp["head_dim"])
w.add_value_length(hp["head_dim"])
w.add_rope_dimension_count(hp["qk_rope_head_dim"])
# compress-ratio-0 layers: plain rope at rope_theta, no YaRN
w.add_rope_freq_base(float(hp["rope_theta"]))
w.add_layer_norm_rms_eps(hp["rms_norm_eps"])
w.add_q_lora_rank(hp["q_lora_rank"])
w.add_sliding_window(hp["sliding_window"])
w.add_expert_count(hp["dspark_n_routed_experts"])
w.add_expert_used_count(hp["dspark_num_experts_per_tok"])
w.add_expert_feed_forward_length(hp["moe_intermediate_size"])
w.add_expert_shared_count(hp["n_shared_experts"])
w.add_expert_weights_scale(hp["routed_scaling_factor"])
w.add_expert_weights_norm(hp["norm_topk_prob"])
w.add_expert_gating_func(gguf.ExpertGatingFuncType.SQRTSOFTPLUS)
w.add_swiglu_clamp_exp([hp["swiglu_limit"]] * n)
w.add_swiglu_clamp_shexp([hp["swiglu_limit"]] * n)
w.add_attention_output_group_count(hp["o_groups"])
w.add_attention_output_lora_rank(hp["o_lora_rank"])
w.add_attention_compress_ratios([0] * n)
w.add_hyper_connection_count(hp["hc_mult"])
w.add_hyper_connection_sinkhorn_iterations(hp["hc_sinkhorn_iters"])
w.add_hyper_connection_epsilon(hp["hc_eps"])
w.add_hyper_connection_single_pass(True)
w.add_block_size(hp["dspark_block_size"])
w.add_target_layers(hp["dspark_target_layer_ids"])
w.add_sample_from_anchor(True)
w.add_has_confidence_head(True)
w.add_file_type(self.ftype)
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
# no Engram tables in the draft
if not self._dsv4_mxfp4_generated:
n_expert = self.hparams["dspark_n_routed_experts"]
for bid in range(self._n_main, self._n_main + self._n_stage):
self._write_mxfp4_experts(bid, "w1", gguf.MODEL_TENSOR.FFN_GATE_EXP, n_expert)
self._write_mxfp4_experts(bid, "w2", gguf.MODEL_TENSOR.FFN_DOWN_EXP, n_expert)
self._write_mxfp4_experts(bid, "w3", gguf.MODEL_TENSOR.FFN_UP_EXP, n_expert)
self._dsv4_mxfp4_generated = True
return ()
_DSPARK_ROOT: dict[str, gguf.MODEL_TENSOR] = {
"main_proj.weight": gguf.MODEL_TENSOR.FC,
"main_norm.weight": gguf.MODEL_TENSOR.ENC_OUTPUT_NORM,
"norm.weight": gguf.MODEL_TENSOR.OUTPUT_NORM,
"dspark.markov_head.embed.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W1,
"dspark.markov_head.head.weight": gguf.MODEL_TENSOR.DSPARK_MARKOV_W2,
"dspark.confidence_head.proj.weight": gguf.MODEL_TENSOR.DSPARK_CONF_PROJ,
}
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if name.endswith(".ffn.gate.bias_vl"):
return
if name in self._DSPARK_ROOT:
yield self.format_tensor_name(self._DSPARK_ROOT[name], None), data_torch
return
m = re.match(r"layers\.\d+\.(main_proj\.weight|main_norm\.weight|norm\.weight)$", name)
if m is not None:
yield self.format_tensor_name(self._DSPARK_ROOT[m.group(1)], None), data_torch
return
yield from super().modify_tensors(data_torch, name, bid)
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
if new_name == "conf_proj.weight":
return gguf.GGMLQuantizationType.F32
return super().tensor_force_quant(name, new_name, bid, n_dims)
@ModelBase.register("DeepseekV41ForCausalLM")
@ModelBase.example("deepseek-ai/DeepSeek-V4.1-Flash-1017")
class DeepseekV41VisionModel(DeepseekV4FlashVisionModel):
"""DeepSeek-V4.1 vision tower (projector deepseek41v): the V4-Flash-Vision ViT + 3x3 aligner, with the V4.1 token
block ([START] ([IMAGE]*w [NEWLINE])*h [END], no pads) and resize plan. ref: inference/vision.py,
inference/image_processor.py of deepseek-ai/DeepSeek-V4.1-Flash-1017. The checkpoint config is HF-style
(vision_config); image_start / image_end / image_newline are written like V4's sentinels, there is no image_pad."""
def get_vision_config(self) -> dict[str, Any] | None:
vc = self.global_config.get("vision_config")
if not vc or vc.get("num_hidden_layers", 0) == 0:
raise ValueError("DeepseekV41VisionModel requires vision_config.num_hidden_layers > 0")
return {
"num_hidden_layers": vc["num_hidden_layers"],
"hidden_size": vc["hidden_size"],
"num_attention_heads": vc["num_attention_heads"],
"intermediate_size": vc["intermediate_size"],
"patch_size": vc["patch_size"],
# dynamic resolution; only used for compat / warmup
"image_size": vc["patch_size"] * vc["downsample_ratio"] * 16,
"rope_theta": vc.get("rope_theta", 10000.0),
"downsample_ratio": vc["downsample_ratio"],
"min_pixels": vc["min_pixels"],
"max_image_tokens": vc["max_image_tokens"],
"max_wh_ratio": vc.get("max_wh_ratio"),
}
def set_gguf_parameters(self):
assert self.hparams_vision is not None
super(DeepseekV4FlashVisionModel, self).set_gguf_parameters()
self.gguf_writer.add_clip_projector_type(gguf.VisionProjectorType.DEEPSEEK41V)
# pytorch default eps (vision.py), not the LLM rms_norm_eps (1e-20)
self.gguf_writer.add_vision_attention_layernorm_eps(1e-6)
self.gguf_writer.add_vision_use_silu(True)
self.gguf_writer.add_vision_projector_scale_factor(self.hparams_vision["downsample_ratio"])
self.gguf_writer.add_vision_min_pixels(self.hparams_vision["min_pixels"])
# hardcoded in clip.cpp (PROJECTOR_TYPE_DEEPSEEK41V); add GGUF keys if a later model differs
assert self.hparams_vision["max_image_tokens"] == 1024, self.hparams_vision["max_image_tokens"]
assert self.hparams_vision["max_wh_ratio"] is None, self.hparams_vision["max_wh_ratio"]
assert self.hparams_vision["rope_theta"] == 10000, self.hparams_vision["rope_theta"]
+38
View File
@@ -0,0 +1,38 @@
from __future__ import annotations
from .base import ModelBase, gguf
from .qwen import Qwen2MoeModel
@ModelBase.register("FlintForCausalLM")
@ModelBase.example("RecursiveSI/flint")
class FlintModel(Qwen2MoeModel):
model_arch = gguf.MODEL_ARCH.FLINT
def set_gguf_parameters(self):
super().set_gguf_parameters()
dense = self.hparams["mlp_only_layers"]
if dense != list(range(len(dense))):
raise ValueError("Flint dense layers must precede the MoE layers")
self.gguf_writer.add_leading_dense_block_count(len(dense))
self.gguf_writer.add_expert_shared_count(
self.hparams["shared_expert_intermediate_size"] // self.hparams["moe_intermediate_size"]
)
self.gguf_writer.add_expert_weights_norm(self.hparams["norm_topk_prob"])
self.gguf_writer.add_expert_weights_scale(1.0)
self.gguf_writer.add_sliding_window(self.hparams["sliding_window"])
self.gguf_writer.add_sliding_window_pattern([
kind == "sliding_attention" for kind in self.hparams["layer_types"]
])
self.gguf_writer.add_rope_pattern([
i not in self.hparams["no_rope_layer_indices"] for i in range(self.block_count)
])
def modify_tensors(self, data_torch, name, bid):
if name.endswith(".softmax_offset"):
name += ".weight"
if name.endswith(".gate.expert_bias"):
if not self.hparams["router_expert_bias"]:
return
name += ".bias"
yield from super().modify_tensors(data_torch, name, bid)
+2
View File
@@ -999,6 +999,8 @@ class Gemma4VisionAudioModel(MmprojModel):
assert self.hparams_vision is not None
self.gguf_writer.add_clip_vision_projector_type(gguf.VisionProjectorType.GEMMA4V)
self.gguf_writer.add_vision_attention_layernorm_eps(self.hparams_vision.get("layer_norm_eps", 1e-6))
assert self.hparams_vision.get("hidden_activation", "gelu_pytorch_tanh") == "gelu_pytorch_tanh"
self.gguf_writer.add_vision_use_gelu(True)
# audio params
if self.has_audio_encoder:
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .base import ModelBase, gguf, logger
from .qwen import Qwen3_5TextModel
from .qwen3vl import Qwen3VLVisionModel
def _is_h2o_lightning_checkpoint(dir_model: Path) -> bool:
return (dir_model / "h2o_lightning_shim.py").is_file() and (dir_model / "serve_config.json").is_file()
@ModelBase.register_hparams_loader(_is_h2o_lightning_checkpoint)
def _load_h2o_lightning_hparams(dir_model: Path) -> dict[str, Any]:
logger.info("gguf: detected H2O-Lightning checkpoint")
hparams = ModelBase.load_hparams(dir_model, False, guess=False)
hparams["architectures"] = ["H2OLightningModel"]
return hparams
@ModelBase.register("H2OLightningModel")
@ModelBase.example("h2oai/h2o-lightning-4b")
class H2OLightningModel(Qwen3_5TextModel):
model_arch = gguf.MODEL_ARCH.QWEN35
# prompt and calibration follow serve_config.json and h2o_lightning_shim.py of the model repo
# the state budget is hard-coded in server-decision.cpp
_MAX_STATE_TOKENS = 32000
_DEFAULT_INSTRUCTIONS = "Answer the question below."
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
with open(self.dir_model / "serve_config.json", "r", encoding="utf-8") as f:
self._serve_config = json.load(f)
prompt = self._serve_config["prompt"]
expected = {
"state_compact": (prompt.get("state_compact", True), True),
"max_state_tokens": (prompt.get("max_state_tokens"), self._MAX_STATE_TOKENS),
"layout": (prompt.get("layout", "plain"), "plain"),
"chat_template": (prompt.get("chat_template", True), True),
"enable_thinking": (prompt.get("enable_thinking", False), False),
}
for name, (got, want) in expected.items():
if got != want:
raise ValueError(f"serve_config.json: {name} is {got!r}, the h2o-lightning decision type expects {want!r}")
def get_vocab_base_pre(self, tokenizer) -> str:
# transformers loads this checkpoint as Qwen2Tokenizer: the model was trained and is served with the Qwen2 split
pre = super().get_vocab_base_pre(tokenizer)
if pre != "qwen35":
raise ValueError(f"expected the Qwen3.5 tokenizer, got pre-tokenizer {pre!r}")
return "qwen2"
def set_vocab(self):
super().set_vocab()
self.gguf_writer.add_chat_template([{"name": "systemone", "template": self._systemone_template()}])
def _systemone_template(self) -> str:
# the server gives the template texts rendered as the shim renders them, it only lays them out
prompt = self._serve_config["prompt"]
nl = "{{ '\\n' }}"
description = (
"{% if type == 'noul' %}"
"{% if o.description %}{{ o.description }}{% elif o.key == 'true' %}the statement holds{% else %}it does not{% endif %}"
"{% elif o.description is none %}{{ o.key }}{% else %}{{ o.description }}{% endif %}"
)
user = (
"{% set user %}record: {{ state }}" + nl
+ "question: {{ instructions if instructions else '" + self._DEFAULT_INSTRUCTIONS + "' }}" + nl
+ "options:{% for o in options %}" + nl + "{{ o.label | trim }}) {{ o.key }}: " + description + "{% endfor %}{% endset %}"
)
return (
"<|im_start|>system" + nl + prompt["system"].strip() + "<|im_end|>" + nl
+ "<|im_start|>user" + nl + "{% for image in images %}{{ image }}{% endfor %}" + user + "{{ user | trim }}<|im_end|>" + nl
+ "<|im_start|>assistant" + nl + "<think>{{ '\\n\\n' }}</think>{{ '\\n\\n' }}" + prompt["prefill"]
)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_decision_type(gguf.DecisionType.H2O_LIGHTNING)
# the answer follows "Answer:", so the token of a label has a leading space
cfg = self._serve_config
self.gguf_writer.add_decision_labels([" " + label for label in cfg["labels"][:cfg.get("max_options", len(cfg["labels"]))]])
for name in ("choice", "noul", "score"):
self.gguf_writer.add_decision_temperature(name, self._serve_config["temperature"][name])
# a request with images has this temperature for all question types
self.gguf_writer.add_decision_temperature("image", self._serve_config["image"]["temperature"])
@ModelBase.register("H2OLightningModel")
class H2OLightningVisionModel(Qwen3VLVisionModel):
pass
+10
View File
@@ -374,6 +374,16 @@ class LFM2VLModel(MmprojModel):
if resize_algo is None:
raise ValueError(f"unsupported resample: {resample}")
self.gguf_writer.add_vision_image_resize_algo(resize_algo)
# tiling params from processor_config.json (do_image_splitting=False -> single tile, i.e. no tiling)
preproc = self.preprocessor_config
if preproc.get("do_image_splitting", True):
self.gguf_writer.add_vision_preproc_min_tiles(preproc.get("min_tiles", 2))
self.gguf_writer.add_vision_preproc_max_tiles(preproc.get("max_tiles", 10))
else:
self.gguf_writer.add_vision_preproc_min_tiles(1)
self.gguf_writer.add_vision_preproc_max_tiles(1)
self.gguf_writer.add_vision_preproc_tile_size(preproc.get("tile_size", 512))
self.gguf_writer.add_vision_preproc_max_pixels_tolerance(preproc.get("max_pixels_tolerance", 2.0))
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
+22 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import json
from typing import Iterable, TYPE_CHECKING
from typing import Callable, Iterable, TYPE_CHECKING
if TYPE_CHECKING:
from torch import Tensor
@@ -128,3 +128,24 @@ class LlavaVisionModel(MmprojModel):
yield from super().modify_tensors(img_break_embd, name, bid)
return # skip other tensors
@ModelBase.register("Mistral3Model")
@ModelBase.example("nvidia/Nemotron-3.5-Embed-1B-BF16")
class Mistral3EmbeddingVisionModel(LlavaVisionModel):
def __init__(self, dir_model, *args, **kwargs):
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
text_config = hparams.get("text_config", hparams)
if text_config.get("is_causal") is not False:
raise NotImplementedError("Expected a Nemotron Embed model for Mistral3Model, but is_causal is not False")
super().__init__(dir_model, *args, **kwargs)
@classmethod
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
if item[0].endswith("embed_tokens.weight"):
return ModelBase.filter_tensors(item)
return super().filter_tensors(item)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_vision_preproc_image_size(self.preprocessor_config["size"]["longest_edge"])
+15
View File
@@ -272,3 +272,18 @@ class MiniCPMV4_7VisionModel(MiniCPMV4_6VisionModel):
yield self.format_tensor_name(gguf.MODEL_TENSOR.V_TOK_EMBD_SEP, suffix=""), data_torch[ids]
return
yield from super().modify_tensors(data_torch, name, bid)
# MiniCPM-OCR 1.0: the 4.6 stack with model_type minicpm_ocr.
@ModelBase.register("MiniCPMOcrForConditionalGeneration")
@ModelBase.example("openbmb/MiniCPM-OCR-1.0")
class MiniCPMOCR1_0TextModel(MiniCPMV4_6TextModel):
model_arch = gguf.MODEL_ARCH.QWEN35
no_mtp = True
@ModelBase.register("MiniCPMOcrForConditionalGeneration")
@ModelBase.example("openbmb/MiniCPM-OCR-1.0")
class MiniCPMOCR1_0VisionModel(MiniCPMV4_6VisionModel):
projector_type = gguf.VisionProjectorType.MINICPM_OCR_1_0
+33 -6
View File
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from torch import Tensor
from .base import ModelBase, TextModel, gguf
from .base import ModelBase, TextModel, get_model_architecture, gguf
from .deepseek import DeepseekV2Model
from .llama import LlamaModel
@@ -13,9 +13,11 @@ from .llama import LlamaModel
@ModelBase.register(
"Mistral3ForConditionalGeneration",
"Mistral3Model",
"Ministral3ForCausalLM",
"Ministral3Model",
)
@ModelBase.example("mistralai/Mistral-Small-3.1-24B-Instruct-2503", "hf-tiny-v2/tiny-random-Ministral3ForCausalLM")
@ModelBase.example("mistralai/Mistral-Small-3.1-24B-Instruct-2503", "hf-tiny-v2/tiny-random-Ministral3ForCausalLM", "nvidia/Nemotron-3-Embed-1B-BF16", "nvidia/Nemotron-3.5-Embed-1B-BF16")
class Mistral3Model(TextModel):
class Ministral3Model(LlamaModel):
model_arch = gguf.MODEL_ARCH.MISTRAL3
@@ -29,6 +31,23 @@ class Mistral3Model(TextModel):
self.gguf_writer.add_rope_scaling_yarn_log_mul(rope_params["mscale_all_dim"])
self.gguf_writer.add_attn_temperature_scale(rope_params["llama_4_scaling_beta"])
class Mistral3EmbedNemotronModel(Ministral3Model):
model_arch = gguf.MODEL_ARCH.MISTRAL3
def set_vocab(self):
super().set_vocab()
self.gguf_writer.add_add_bos_token(False)
self.gguf_writer.add_add_eos_token(False)
def get_vocab_base_pre(self, tokenizer) -> str:
if self.origin_hf_arch == "Mistral3Model":
return "nemotron-3.5-embed"
return super().get_vocab_base_pre(tokenizer)
def set_gguf_parameters(self):
super().set_gguf_parameters()
self._try_set_pooling_type()
class Mistral4Model(DeepseekV2Model):
model_arch = gguf.MODEL_ARCH.MISTRAL4
skip_mtp = False # model contains no MTP layers, so no need to skip
@@ -42,12 +61,20 @@ class Mistral3Model(TextModel):
model_arch = gguf.MODEL_ARCH.MISTRAL3 # unused
impl: TextModel
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, dir_model, *args, **kwargs):
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
hf_arch = get_model_architecture(hparams, self.model_type)
if hf_arch in ("Ministral3Model", "Mistral3Model"):
text_config = hparams.get("text_config", hparams)
if text_config.get("is_causal") is not False:
raise NotImplementedError(f"Expected a Nemotron Embed model for {hf_arch}, but is_causal is not False")
super().__init__(dir_model, *args, **kwargs)
if self.hparams.get("model_type") == "mistral4":
self.impl = Mistral3Model.Mistral4Model(*args, **kwargs)
self.impl = Mistral3Model.Mistral4Model(dir_model, *args, **kwargs)
elif self.hf_arch in ("Ministral3Model", "Mistral3Model"):
self.impl = Mistral3Model.Mistral3EmbedNemotronModel(dir_model, *args, **kwargs)
else:
self.impl = Mistral3Model.Ministral3Model(*args, **kwargs)
self.impl = Mistral3Model.Ministral3Model(dir_model, *args, **kwargs)
def set_vocab(self):
self.impl.set_vocab()
+20 -4
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import re
from typing import Any, Callable, Iterable, Sequence, TYPE_CHECKING
import numpy as np
@@ -367,9 +368,21 @@ class NemotronModel(TextModel):
model_arch = gguf.MODEL_ARCH.NEMOTRON
def set_vocab(self):
self._set_vocab_sentencepiece()
self.gguf_writer.add_pad_token_id(0)
self.gguf_writer.add_unk_token_id(1)
if (self.dir_model / "tokenizer.model").exists():
self._set_vocab_sentencepiece()
self.gguf_writer.add_pad_token_id(0)
self.gguf_writer.add_unk_token_id(1)
else:
# SPM-style BPE (Metaspace + byte fallback): mark <0xXX> tokens as BYTE so they decode to raw bytes
tokens, toktypes, tokpre = self.get_vocab_base()
toktypes = [gguf.TokenType.BYTE if re.fullmatch(r"<0x[0-9A-F]{2}>", tok) else typ for tok, typ in zip(tokens, toktypes)]
self.gguf_writer.add_tokenizer_model("gpt2")
self.gguf_writer.add_tokenizer_pre(tokpre)
self.gguf_writer.add_token_list(tokens)
self.gguf_writer.add_token_types(toktypes)
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
special_vocab.add_to_gguf(self.gguf_writer)
def set_gguf_parameters(self):
super().set_gguf_parameters()
@@ -383,7 +396,10 @@ class NemotronModel(TextModel):
rot_pct = self.rope_parameters["partial_rotary_factor"]
n_embd = self.find_hparam(["hidden_size", "n_embd"])
n_head = self.find_hparam(["num_attention_heads", "n_head"])
self.gguf_writer.add_rope_dimension_count(int(rot_pct * n_embd) // n_head)
# Use explicit head_dim when available; fallback keeps standard Nemotron working.
# Without this, models where n_head * head_dim != n_embd (e.g. 48 heads, head_dim=128) produce the wrong rope dimension count.
head_dim = self.hparams.get("head_dim") or n_embd // n_head
self.gguf_writer.add_rope_dimension_count(int(rot_pct * head_dim))
# * RopeScaling for Nemotron
factor = self.hparams.get("factor") or self.rope_parameters.get("factor")
+54 -15
View File
@@ -41,6 +41,8 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
self._ple_shards: dict[int, str] = {}
self._ple_row_dim: int | None = None
self._mtp_fc: dict[str, Tensor] = {}
# halves of the merged weights, keyed by (bid, merged tensor), see _merge_shared_input
self._merge_parts: dict[tuple[int, gguf.MODEL_TENSOR], dict[int, Tensor]] = {}
@classmethod
def filter_tensors(cls, item):
@@ -131,44 +133,79 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
# int64 hash constants must stay exact; 1-D tensors force F32, so use KV
if name.endswith("ple_embedding.layer_multipliers"):
self._ple_multipliers = [int(x) for x in data_torch.tolist()]
return []
return
if name.endswith("ple_embedding.ngram_heads_offsets"):
self._ple_head_offsets = [int(x) for x in data_torch.tolist()]
return []
return
if name.endswith("ple_embedding.ngram_heads_vocab_sizes"):
self._ple_head_vocab_sizes = [int(x) for x in data_torch.tolist()]
return []
return
if ".ngram_embedding.shard_" in name:
return self._place_ple_shard(data_torch, name)
yield from self._place_ple_shard(data_torch, name)
return
# eh_proj([e ; h_s]) = fc_embedding(e) + fc_hidden(h_s) for every hc stream s
if name.endswith((".nextn_fc_embedding.weight", ".nextn_fc_hidden.weight")):
self._mtp_fc[name.rsplit(".", 2)[1]] = data_torch
if len(self._mtp_fc) < 2:
return []
return
eh = torch.cat([self._mtp_fc.pop("nextn_fc_embedding"), self._mtp_fc.pop("nextn_fc_hidden")], dim=1)
return [(self.format_tensor_name(gguf.MODEL_TENSOR.NEXTN_EH_PROJ, bid, ".weight"), eh)]
yield (self.format_tensor_name(gguf.MODEL_TENSOR.NEXTN_EH_PROJ, bid, ".weight"), eh)
return
# one projection feeds indexer q and k; split it, as minimax-m3 does
if ".indexer.index_qk_proj.weight" in name:
n_q = self.hparams["indexer_n_heads"] * self.hparams["indexer_head_dim"]
q = data_torch[:n_q]
k = data_torch[n_q:]
return [
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), q),
(self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), k),
]
yield (self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_Q_PROJ, bid, ".weight"), data_torch[:n_q])
yield (self.format_tensor_name(gguf.MODEL_TENSOR.INDEXER_K_PROJ, bid, ".weight"), data_torch[n_q:])
return
# Gemma zero-centred gammas the inherited norm.weight rule misses
if name.endswith((".ple.norm_key.weight", ".ple.norm_query.weight", ".ple.norm_conv.weight",
".indexer.q_layernorm.weight", ".indexer.k_layernorm.weight")):
return [(self.map_tensor_name(name), data_torch + 1)]
yield (self.map_tensor_name(name), data_torch + 1)
return
if name.endswith(".ple.conv1d.weight"):
return [(self.map_tensor_name(name), data_torch.squeeze())]
yield (self.map_tensor_name(name), data_torch.squeeze())
return
return super().modify_tensors(data_torch, name, bid)
for new_name, data in super().modify_tensors(data_torch, name, bid):
yield from self._merge_shared_input(new_name, data, bid)
# matmuls that read the same input, written as one weight [a | b] so the runtime does one matmul:
# (merged tensor, (a, b), linear-attention layers only)
_SHARED_INPUT_MERGES = (
(gguf.MODEL_TENSOR.HC_ATTN_DOWN_INJECT, (gguf.MODEL_TENSOR.HC_ATTN_DOWN, gguf.MODEL_TENSOR.HC_ATTN_INJECT), False),
(gguf.MODEL_TENSOR.HC_FFN_DOWN_INJECT, (gguf.MODEL_TENSOR.HC_FFN_DOWN, gguf.MODEL_TENSOR.HC_FFN_INJECT), False),
(gguf.MODEL_TENSOR.ATTN_QKV_GATE, (gguf.MODEL_TENSOR.ATTN_QKV, gguf.MODEL_TENSOR.ATTN_GATE), True),
(gguf.MODEL_TENSOR.SSM_BETA_ALPHA_CAT, (gguf.MODEL_TENSOR.SSM_BETA, gguf.MODEL_TENSOR.SSM_ALPHA), True),
)
# caches one half of a merged weight until the other half arrives, then yields [a | b]
def _merge_shared_input(self, new_name: str, data: Tensor, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# a pre-quantized checkpoint carries per-tensor scales, which one merged weight cannot hold
if bid is None or self.hparams.get("quantization_config"):
yield (new_name, data)
return
layer_types = self.hparams.get("layer_types") or []
is_linear = bid < len(layer_types) and layer_types[bid] == "linear_attention"
match = next(((merged, parts.index(part)) for merged, parts, linear_only in self._SHARED_INPUT_MERGES
for part in parts
if (is_linear or not linear_only) and self.match_model_tensor_name(new_name, part, bid)), None)
if match is None:
yield (new_name, data)
return
merged, idx = match
halves = self._merge_parts.setdefault((bid, merged), {})
halves[idx] = data
if len(halves) == 2:
del self._merge_parts[(bid, merged)]
yield (self.format_tensor_name(merged, bid), torch.cat([halves[0], halves[1]], dim=0))
# the shards concatenate into a tensor of well over 100 GB
# use LazyChunkedTensor here, a single shard resident at a time
@@ -214,6 +251,8 @@ class Qwen4ExpTextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
super().prepare_tensors()
if self._mtp_fc:
raise ValueError(f"MTP projection missing its other half: {sorted(self._mtp_fc)}")
if self._merge_parts:
raise ValueError(f"merged weights missing a half: {sorted((bid, t.name) for bid, t in self._merge_parts)}")
n_parts = self.hparams.get("split_ngram_parts", 0)
if self._ple_shards and len(self._ple_shards) != n_parts:
raise ValueError(
+114
View File
@@ -0,0 +1,114 @@
from __future__ import annotations
from pathlib import Path
import torch
from .base import MmprojModel, ModelBase, gguf
from .qwen import Qwen2Model
@ModelBase.example("sensenova/SenseNova-U1.5-8B-MoT")
@ModelBase.register("NEOChatModel")
class SenseNovaU1Model(Qwen2Model):
model_arch = gguf.MODEL_ARCH.SENSENOVA_U1
@classmethod
def filter_tensors(cls, item):
name, _ = item
if not name.startswith("language_model.") or "_mot_gen" in name:
return None
return super().filter_tensors(item)
def set_gguf_parameters(self):
if int(self.hparams.get("num_experts") or 0) > 1:
raise ValueError("SenseNova U1 conversion currently supports the dense understanding branch")
if self.rope_parameters.get("rope_type", "default") not in ("default", None):
raise ValueError("SenseNova U1 scaled RoPE is not supported")
if self.hparams.get("attention_bias", False):
raise ValueError("SenseNova U1 attention bias is not supported")
if self.hparams.get("use_sliding_window", False):
raise ValueError("SenseNova U1 sliding attention is not supported")
super().set_gguf_parameters()
self.gguf_writer.add_rope_freq_base_spatial(self.hparams["rope_theta_hw"])
def get_tensors(self):
for name, gen in self.model_tensors.items():
if name.endswith((".q_norm_hw.weight", ".k_norm_hw.weight")):
continue
data = gen()
if name.endswith((".q_norm.weight", ".k_norm.weight")):
spatial = self.model_tensors[name.replace("_norm.weight", "_norm_hw.weight")]()
# Keep one weight vector; the graph normalizes its two halves independently.
data = torch.cat((data, spatial), dim=0)
yield name, data
@ModelBase.register("NEOVisionModel")
@ModelBase.example("sensenova/SenseNova-U1.5-8B-MoT")
class SenseNovaU1VisionModel(MmprojModel):
def __init__(self, dir_model: Path, *args, **kwargs):
hparams = kwargs.pop("hparams", None)
if hparams is None:
hparams = ModelBase.load_hparams(dir_model, is_mistral_format=False)
else:
hparams = dict(hparams)
vision_config = dict(hparams["vision_config"])
# NEOVisionModel has no transformer blocks
# these defaults satisfy the generic mmproj metadata contract without replacing checkpoint values
vision_config.setdefault("image_size", 0)
vision_config.setdefault("intermediate_size", 0)
vision_config.setdefault("num_hidden_layers", 0)
vision_config.setdefault("num_attention_heads", 1)
hparams["vision_config"] = vision_config
super().__init__(dir_model, *args, hparams=hparams, **kwargs)
self.preprocessor_config.setdefault("image_mean", [0.485, 0.456, 0.406])
self.preprocessor_config.setdefault("image_std", [0.229, 0.224, 0.225])
@classmethod
def filter_tensors(cls, item):
name, gen = item
if not name.startswith("vision_model."):
return None
return super().filter_tensors((name, gen))
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_clip_projector_type("sensenova_u1")
vision_config = self.hparams["vision_config"]
# These defaults match the known U1.5 checkpoint when older configs omit fields.
hidden_size = int(vision_config.get("hidden_size", 1024))
min_pixels = int(vision_config.get("min_pixels", 65536))
max_pixels = int(vision_config.get("max_pixels", 16777216))
downsample_ratio = float(vision_config.get("downsample_ratio", 0.5))
if downsample_ratio <= 0:
raise ValueError(f"SenseNova U1 downsample_ratio must be positive, got {downsample_ratio}")
merge_size = round(1.0 / downsample_ratio)
if abs((1.0 / downsample_ratio) - merge_size) > 1e-6:
raise ValueError(f"SenseNova U1 downsample_ratio must have an integer reciprocal, got {downsample_ratio}")
norm_eps = float(vision_config.get("layer_norm_eps", vision_config.get("rms_norm_eps", 1e-6)))
self.gguf_writer.add_vision_head_dim(hidden_size)
self.gguf_writer.add_vision_attention_layernorm_eps(norm_eps)
self.gguf_writer.add_vision_min_pixels(min_pixels)
self.gguf_writer.add_vision_max_pixels(max_pixels)
self.gguf_writer.add_vision_spatial_merge_size(merge_size)
def modify_tensors(self, data_torch, name, bid):
tensor_names = {
"vision_model.embeddings.patch_embedding.weight": "v.patch_embd.weight",
"vision_model.embeddings.patch_embedding.bias": "v.patch_embd.bias",
"vision_model.embeddings.dense_embedding.weight": self.format_tensor_name(gguf.MODEL_TENSOR.V_MMPROJ, 0),
"vision_model.embeddings.dense_embedding.bias": self.format_tensor_name(
gguf.MODEL_TENSOR.V_MMPROJ, 0, suffix=".bias"
),
}
if name not in tensor_names:
raise ValueError(f"Unexpected SenseNova U1 vision tensor: {name!r}")
return [(tensor_names[name], data_torch)]
def tensor_force_quant(self, name, new_name, bid, n_dims):
if new_name == self.format_tensor_name(gguf.MODEL_TENSOR.V_MMPROJ, 0):
return gguf.GGMLQuantizationType.F16 if self.ftype == gguf.LlamaFileType.MOSTLY_F16 else gguf.GGMLQuantizationType.F32
return super().tensor_force_quant(name, new_name, bid, n_dims)
+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:
+2
View File
@@ -101,6 +101,7 @@ models = [
{"name": "command-r", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/CohereForAI/c4ai-command-r-v01", },
{"name": "tiny_aya", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/CohereLabs/tiny-aya-base", },
{"name": "cohere2moe", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/CohereLabs/North-Mini-Code-1.0", },
{"name": "flint", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/RecursiveSI/flint", },
{"name": "qwen2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/Qwen/Qwen1.5-7B", },
{"name": "olmo", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/allenai/OLMo-1.7-7B-hf", },
{"name": "dbrx", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/databricks/dbrx-base", },
@@ -177,6 +178,7 @@ pre_computed_hashes = [
{"name": "glm4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/zai-org/GLM-4.5-Air", "chkhsh": "9ca2dd618e8afaf09731a7cf6e2105b373ba6a1821559f258b272fe83e6eb902"},
{"name": "glm4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/zai-org/GLM-4.7-Flash", "chkhsh": "cdf5f35325780597efd76153d4d1c16778f766173908894c04afc20108536267"},
{"name": "minerva-7b", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/sapienzanlp/Minerva-7B-base-v1.0", "chkhsh": "1431a23e583c97432bc230bff598d103ddb5a1f89960c8f1d1051aaa944d0b35"},
{"name": "domyn-small", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/domyn/Domyn-Small-v1.0", "chkhsh": "6e4d72c6e5260b90606e26a0931fa15e4b42ec6e97719d12919f2b1f93478262"},
{"name": "hunyuan", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-A13B-Instruct", "chkhsh": "7e57df22b1fe23a7b1e1c7f3dc4e3f96d43a4eb0836d0c6bdc3436d7b2f1c664"},
{"name": "hunyuan-dense", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hunyuan-4B-Instruct", "chkhsh": "bba3b3366b646dbdded5dbc42d59598b849371afc42f7beafa914afaa5b70aa6"},
{"name": "hy_v4", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/tencent/Hy4-preview", "chkhsh": "e6ddf9c6686791c12d698d34c31ab9be1fea9af5a3d9a6909783ab382198ae1c"},
+18 -3
View File
@@ -783,7 +783,7 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| GGML_SYCL | ON (mandatory) | Enable build with SYCL code path. |
| GGML_SYCL_TARGET | INTEL *(default)* | Set the SYCL target device type. |
| GGML_SYCL_DEVICE_ARCH | Optional | Set the SYCL device architecture. Setting the device architecture can improve the performance. See the table [--offload-arch](https://github.com/intel/llvm/blob/sycl/sycl/doc/design/OffloadDesign.md#--offload-arch) for a list of valid architectures. |
| GGML_SYCL_F16 | OFF *(default)* \|ON *(optional)* | Enable FP16 build with SYCL code path. (1.) |
| GGML_SYCL_F16 | OFF *(default)* \|ON *(optional)* | Enable FP16 build with SYCL code path. [1.](#sycl-f16-footnote) |
| GGML_SYCL_GRAPH | ON *(default)* \|OFF *(Optional)* | Enable build with [SYCL Graph extension](https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_graph.asciidoc). |
| GGML_SYCL_DNN | ON *(default)* \|OFF *(Optional)* | Enable build with oneDNN. |
| GGML_SYCL_HOST_MEM_FALLBACK | ON *(default)* \|OFF *(Optional)* | Allow host memory fallback when device memory is full during quantized weight reorder. Enables inference to continue at reduced speed (reading over PCIe) instead of failing. Requires Linux kernel 6.8+. |
@@ -791,7 +791,7 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| CMAKE_C_COMPILER | `icx` *(Linux)*, `icx/cl` *(Windows)* | Set `icx` compiler for SYCL code path. |
| CMAKE_CXX_COMPILER | `icpx` *(Linux)*, `icx` *(Windows)* | Set `icpx/icx` compiler for SYCL code path. |
1. FP32 or FP16 have different performance impact to LLM. Recommended to test them for better prompt processing performance on your models. You need to rebuild the code after change `GGML_SYCL_F16=OFF/ON`.
1. <a id="sycl-f16-footnote"></a>FP32 or FP16 have different performance impact to LLM. Recommended to test them for better prompt processing performance on your models. You need to rebuild the code after change `GGML_SYCL_F16=OFF/ON`.
### Runtime
@@ -802,7 +802,8 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| GGML_SYCL_DEV2DEV_MEMCPY | 0 (default), 1, 2 | Choose the method of dev2dev memory copy.<br>Value: <br>* 0: SYCL API (default), only support dGPUs.<br>* 1: L0 API -- Better performance, only support dGPUs, found to lead to abnormal crash in some case. <br>* 2: Host Forward -- Most stable method for all cases (including iGPU + dGPU*N), but with lower performance (-2% to -5%).<br>SYCL & L0 API are easy to be impacted by Intel GPU driver issue. When you meet the garbled output or crash issues in multiple GPUs case, try with this debug flag to work around or check the issue.|
| GGML_SYCL_ENABLE_FLASH_ATTN | 1 (default) or 0| Enable Flash-Attention. It can reduce memory usage. The performance impact depends on the LLM.|
| GGML_SYCL_ENABLE_OPT | 0 or 1 (default)| Enable optimize features for Intel GPUs. (Recommended to 0 for Intel devices older than Gen 10) |
| GGML_SYCL_ENABLE_GRAPH | 0 (default) or 1 | Enable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still on development, no better performance. |
| GGML_SYCL_ENABLE_GRAPH | 0 (default) or 1 | Enable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still in development, performance depends on model and device. [1.](#sycl-graph-footnote) |
| GGML_SYCL_GRAPH_EVICTION_TIMEOUT | 10 (default) or positive integer | Seconds a cached SYCL graph can go unused before it is evicted. Checked on a sweep that runs every half of this timeout. |
| GGML_SYCL_ENABLE_HOST_PINNED_MEM | 0 or 1 (default) | Enable host pinned memory to speed up copy data from host to device. When disable it, host memory will common malloc() on CPU. Disable it when use `--load-model mlock`.|
| GGML_SYCL_HOST_PINNED_MEM_2G | 0 (default) or 1 | Limit the max memory allocation to be no more than 2GB when enable host pinned memory. USM allocations above 2 GiB take the relaxed/large-allocation path, which serializes H2D copies with compute and prevents copy/compute overlap. It will impact the startup time. Need more test. Depend on `GGML_SYCL_ENABLE_HOST_PINNED_MEM=1`.|
| GGML_SYCL_UPLOAD_STAGING_SLOTS | 4 (default) or non-negative integer | Number of 8 MiB pinned host slots used to stage tensor uploads (model loading), so the host copy of one slot overlaps the transfer of the previous one. Set to 0 to use the old path: a malloc'd bounce buffer and a blocking copy per tensor. |
@@ -819,6 +820,9 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| GGML_SYCL_MKL_FA_DIAG | 0 (default) or 1 | Enable output fingerprinting for MKL flash attention. Dumps the first 64 float output values for the first 6 FA calls with n_kv ≥ 1024, labeled with kernel type (MKL/TILE/VEC) for cross-kernel comparison. |
| GGML_SYCL_ENABLE_FUSION | 0 or 1 (default) | Enable fused-kernel dispatch in graph compute. Unsupported types and layouts fall back to the standalone op kernels. See `ggml_sycl_can_fuse()`. |
| GGML_SYCL_ENABLE_ESIMD | 0 or 1 (default)| Enable ESIMD kernels when available. |
| GGML_SYCL_ENABLE_XMX_ESIMD | 0 or 1 (default) | Use the XMX (DPAS) multi-column mat-vec kernels for reordered Q2_K to Q6_K and Q8_0 weights on Xe2 GPUs. Speeds up speculative decoding and short prompt batches. |
| GGML_SYCL_XMX_MAX_COLS | non-negative integer, 80 (default) | Most activation columns (batch size) the `GGML_SYCL_ENABLE_XMX_ESIMD` kernels take; wider batches use the other mat-mul kernels. A larger value keeps XMX for bigger batches (as repeated launches), `0` never uses it. |
| GGML_SYCL_XMX_GLU_MAX_COLS | 0 to 16, 16 (default) | Most columns the fused gate and up XMX kernel takes; wider batches run gate and up as separate mat-vecs. 16 is the most the kernel is built for, larger values are clamped to 16 and `0` turns the fused kernel off. |
| GGML_SYCL_XMX_GATHER_TYPES | decimal bitmask, all bits set (default) | Weight formats that may use the XMX dequant-GEMM paths, which dequantize weights straight into the XMX tiles. This speeds up prompt processing of MoE models on GPUs with XMX units (Arc A- and B-series, Arc Pro, Data Center GPU Max), for example pp512 of Qwen3-30B-A3B UD-IQ3_XXS by about 50% on an Arc Pro B60. Bits:<br>* 1: IQ4_NL, 2: IQ3_S, 4: IQ4_XS, 8: IQ3_XXS, 16: IQ2_XXS, 32: IQ2_XS, 64: IQ2_S, 128: IQ1_S, 256: IQ1_M<br>* 512: Q8_0, 1024: Q4_K, 2048: Q5_K, 4096: Q6_K (MoE `MUL_MAT_ID` only)<br>Add values to combine them, for example `3` for IQ4_NL and IQ3_S; `0` disables the paths. A set bit does not force the path: batches of more than 64 tokens per expert or row lengths that are not a multiple of 256 (32 for IQ4_NL and Q8_0) use the library GEMM. |
| GGML_SYCL_XMX_GATHER_SHAPES | decimal bitmask, 255 (default) | XMX `joint_matrix` combinations the paths of `GGML_SYCL_XMX_GATHER_TYPES` may use; the operand type comes from `GGML_SYCL_DYNAMIC_PRECISION` and the best supported combination is picked automatically (logged as `fg_pick_combo`). Bits:<br>* Xe2, Xe3, Xe-HPC: 1: f16 8x16x16, 2: f16 16x16x16, 4: f16 32x64x16, 8: f16 32x64x32, 32: tf32 8x16x8, 64: bf16 8x16x16<br>* Xe-HPG (Arc A770, ARL-H): 16: f16 8x8x16, 128: bf16 8x8x16<br>Clear a bit to exclude a combination, or set a single bit to force one for testing. |
| GGML_SYCL_DYNAMIC_PRECISION | `F16` (default with `GGML_SYCL_F16=ON`), `BF16`, `TF32` or `F32` (default otherwise) | Operand type of the XMX dequant-GEMM paths (`GGML_SYCL_XMX_GATHER_TYPES`); accumulation is always f32. `F16` is the fastest, but activations above 65504 overflow. `BF16` keeps the f32 range at a 7-bit mantissa, `TF32` keeps the range and the f16 mantissa but is about 30% slower and needs Xe2, Xe3 or Xe-HPC, and `F32` turns the XMX paths off. Ops that request a higher src1 precision ([TAG_GGML_PREC]) get it regardless of this setting. |
@@ -831,6 +835,17 @@ User can use the device management in [docs/multi-gpu.md](https://github.com/ggm
| UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS | 0 (default) or 1 | Allow SYCL/Unified Runtime Level Zero device allocations larger than 4 GiB. llama.cpp's direct Level Zero allocation path requests the relaxed maximum-size limit itself when GGML_SYCL_ENABLE_LEVEL_ZERO=1. |
| GGML_SYCL_USM_SYSTEM | 0 (default) or 1 | Enable experimental support for [USM system allocations](https://github.khronos.org/SYCL_Reference/iface/usm_basic_concept.html#system-allocations) for large GPU buffers. This requires enough host memory for model weights and caches, an Intel Xe2+ GPU such as BMG or newer and supported on Linux only, with CONFIG_DRM_XE_GPUSVM enabled. |
1. <a id="sycl-graph-footnote"></a>SYCL Graph support (`GGML_SYCL_ENABLE_GRAPH=1`) is limited to single-GPU workloads and may also depend on the model architecture and kernels in use.
Performance depends on how often a recorded graph can be reused, which will vary by workload.
For this reason, it is recommended to measure performance both with and without graphs enabled on your workloads.
To check whether graphs are supported for your workload, enable INFO logging (`-lv 4`) and look for `enabling SYCL graphs` or `disabling SYCL graphs`.
Support may differ between prefill and decode, so check the status for the phases that matter to you.
DEBUG logging (`-lv 5`) reports why graphs are disabled.
Then benchmark with your preferred tool, such as `llama-bench`.
For graph activity details (including reuse), set `GGML_SYCL_DEBUG=1` and look for `[SYCL-GRAPH]` messages.
## Compile-time Flags
Pass these via `CXXFLAGS` or add a one-off `#define` to enable a flag on the spot.
+9
View File
@@ -2,6 +2,7 @@
> [!WARNING]
> **Note:** zDNN is **not** the same as ZenDNN.
>
> - **zDNN** (this page): IBM's Deep Neural Network acceleration library for IBM Z & LinuxONE Mainframes
> - **ZenDNN**: AMD's deep learning library for AMD EPYC CPUs ([see ZenDNN documentation](ZenDNN.md))
@@ -46,6 +47,14 @@ The IBM zDNN backend has the following CMake options that control the behaviour
| --------------------- | ------------- | -------------------------------------------------------------------------------------- |
| `GGML_ZDNN_HIGH_PREC` | `0` | Set to `1` to requantize Q8_0 weights in smaller groups, for lower loss at lower speed |
## Performance Tunables
| Flag Option | Default Value | Description |
| ----------------------- | ------------- | ------------------------------------------------------ |
| `--flash-attn` or `-fa` | `auto` | Set to `on` to run attention on the NNPA (Recommended) |
Note: Set `-fa on` explicitly. Flash Attention fuses each layer's attention (the KQ matrix multiplication, softmax and KQV matrix multiplication) into one `FLASH_ATTN_EXT` operation, which zDNN runs on the NNPA instead of the CPU. The speedup grows with the context length. With `auto`, llama.cpp currently turns Flash Attention off, because the operation runs on a different device than the layer. zDNN runs it when the KV cache is F16 (the default), the model uses no attention sinks, ALiBi or logit softcapping, and there is one KV cache stream (one sequence, or `--kv-unified`). Otherwise it falls back to the CPU.
## 1. Install zDNN Library
Note: A minimum of libzdnn 1.1.2 is required for the IBM zDNN backend to link correctly.
+18 -18
View File
@@ -14,7 +14,7 @@ Legend:
| Operation | BLAS | CANN | CPU | CUDA | ET | HTP | MTL | OpenCL | SYCL | Vulkan | WebGPU | ZenDNN | zDNN |
|-----------|------|------|------|------|------|------|------|------|------|------|------|------|------|
| ABS | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ABS | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ACC | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| ADD | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ADD1 | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
@@ -22,12 +22,12 @@ Legend:
| ARANGE | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| ARGMAX | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ARGSORT | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| CEIL | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| CEIL | ❌ | ❌ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| CLAMP | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| COL2IM_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| CONCAT | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
| CONT | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| CONV_2D | ❌ | ❌ | 🟡 | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ❌ | ❌ |
| CONV_2D_DW | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| CONV_3D | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -45,7 +45,7 @@ Legend:
| DSV4_HC_COMB | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| DSV4_HC_POST | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| DSV4_HC_PRE | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| DUP | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ | ❌ |
| DUP | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ | ❌ |
| ELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| EXP | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| EXPM1 | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
@@ -55,10 +55,10 @@ Legend:
| GATED_DELTA_NET | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | 🟡 | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| GATED_LINEAR_ATTN | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| GEGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| GEGLU_ERF | ❌ | ✅ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| GEGLU_ERF | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| GEGLU_QUICK | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| GELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| GELU_ERF | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| GELU_ERF | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| GELU_QUICK | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| GET_ROWS | ❌ | 🟡 | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ❌ | ❌ |
| GET_ROWS_BACK | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ |
@@ -66,7 +66,7 @@ Legend:
| HARDSIGMOID | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| HARDSWISH | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| IM2COL | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| IM2COL_3D | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| IM2COL_3D | ❌ | ❌ | ✅ | ✅ | ❌ | 🟡 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| L2_NORM | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | 🟡 | ❌ | ❌ |
| LEAKY_RELU | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 | 🟡 | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| LIGHTNING_INDEXER | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
@@ -74,12 +74,12 @@ Legend:
| MEAN | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | 🟡 | ✅ | ❌ | ❌ | ❌ |
| MUL | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| MUL_MAT | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
| MUL_MAT_HADAMARD | ❌ | ❌ | ✅ | ❌ | ✅ | 🟡 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 |
| MUL_MAT_HADAMARD | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | 🟡 |
| MUL_MAT_ID | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | 🟡 | ❌ |
| MUL_MAT_ID_W4A4 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_ID_W4A8 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_W4A4 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_W4A8 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_ID_W4A4 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_ID_W4A8 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_W4A4 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| MUL_MAT_W4A8 | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| NEG | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| NORM | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | 🟡 | ❌ | ❌ |
| OPT_STEP_ADAMW | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -87,9 +87,9 @@ Legend:
| OUT_PROD | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | ❌ |
| PAD | ❌ | 🟡 | ✅ | 🟡 | ❌ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ |
| PAD_REFLECT_1D | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| POOL_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| POOL_2D | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| REGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| POOL_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| POOL_2D | ❌ | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| REGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| RELU | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| REPEAT | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
| REPEAT_BACK | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -101,7 +101,7 @@ Legend:
| ROUND | ❌ | ❌ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| RWKV_WKV6 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| RWKV_WKV7 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| SCALE | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SCALE | ❌ | 🟡 | ✅ | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SET | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ |
| SET_ROWS | ❌ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
| SGN | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
@@ -117,12 +117,12 @@ Legend:
| SQRT | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SSM_CONV | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SSM_SCAN | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | 🟡 | 🟡 | ✅ | ❌ | ❌ |
| STEP | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| STEP | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SUB | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SUM | ❌ | 🟡 | ✅ | 🟡 | ❌ | 🟡 | 🟡 | ❌ | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
| SUM_ROWS | ❌ | ✅ | ✅ | 🟡 | ❌ | 🟡 | ✅ | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ |
| SWIGLU | ❌ | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SWIGLU_CLAMP | ❌ | ❌ | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| SWIGLU_CLAMP | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| SWIGLU_OAI | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| TANH | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| TIMESTEP_EMBEDDING | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
+6792 -5051
View File
File diff suppressed because it is too large Load Diff
+7793 -6047
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -8,10 +8,10 @@ extern "C" {
#define RPC_PROTO_MAJOR_VERSION 8
#define RPC_PROTO_MINOR_VERSION 0
#define RPC_PROTO_PATCH_VERSION 0
#define RPC_PROTO_PATCH_VERSION 1
#ifdef __cplusplus
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
#endif
#define GGML_RPC_MAX_SERVERS 16
+33
View File
@@ -585,6 +585,7 @@ extern "C" {
GGML_OP_DSV4_HC_COMB,
GGML_OP_DSV4_HC_PRE,
GGML_OP_DSV4_HC_POST,
GGML_OP_TOP_K_MASK,
GGML_OP_UNARY,
@@ -925,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,
@@ -1476,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]
@@ -1497,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`
@@ -2732,6 +2745,26 @@ extern "C" {
struct ggml_tensor * post,
struct ggml_tensor * comb);
// top-k sparse attention mask: keep the mask cells of the live picks and the extra cells, set the rest to -inf
//
// mask: [n_kv, m1, m2, m3] f16 or f32, broadcast to the rows of top_k
// top_k: [n_top, ne1, ne2, ne3] i32, picks of each row
// score: [n_pick, ne1, ne2, ne3] f32, can be NULL: a pick is live when its score is above -inf, else every pick is live
// blocks: [n_blk, n_pick] i32, can be NULL: the member cells of each pick, else a pick is a cell
// extra: [n_extra, ne1, ne2, ne3] i32, can be NULL: cells of each row kept without a pick
// res: [n_kv, ne1, ne2, ne3] same type as mask
//
// res[c, i] = mask[c, i % m] when c is a cell of a live pick of row i or an extra cell of row i, else -inf
// picks and cells out of range are skipped
//
GGML_API struct ggml_tensor * ggml_top_k_mask(
struct ggml_context * ctx,
struct ggml_tensor * mask,
struct ggml_tensor * top_k,
struct ggml_tensor * score,
struct ggml_tensor * blocks,
struct ggml_tensor * extra);
// custom operators
typedef void (*ggml_custom1_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, int ith, int nth, void * userdata);
+2 -1
View File
@@ -1046,7 +1046,8 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
} break;
case GGML_OP_DSV4_HC_COMB:
case GGML_OP_DSV4_HC_PRE:
case GGML_OP_DSV4_HC_POST: {
case GGML_OP_DSV4_HC_POST:
case GGML_OP_TOP_K_MASK: {
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
} break;
case GGML_OP_UNARY: {
+1
View File
@@ -2226,6 +2226,7 @@ bool ggml_op_alloc_size_may_expand(enum ggml_op op) {
case GGML_OP_CUMSUM:
case GGML_OP_ARGSORT:
case GGML_OP_TOP_K:
case GGML_OP_ARGMAX:
return true;
default:
return false;
+3 -3
View File
@@ -920,7 +920,7 @@ static void * ggml_backend_cann_buffer_get_base(ggml_backend_buffer_t buffer) {
* @param dst Pointer to the destination buffer where transformed data will be
* stored.
*/
static void ggml_backend_cann_transform_q4_0(ggml_tensor * tensor, const void * src, void * dst) {
static void ggml_backend_cann_transform_q4_0(const ggml_tensor * tensor, const void * src, void * dst) {
int64_t n_elems = ggml_nelements(tensor);
int64_t groups = n_elems / QK4_0;
size_t quant_bytes = n_elems * sizeof(uint8_t) / 2;
@@ -1014,7 +1014,7 @@ static void ggml_backend_cann_transform_back_q4_0(const ggml_tensor * tensor, vo
* @param dst Pointer to the destination buffer where transformed data will be
* stored.
*/
static void ggml_backend_cann_transform_q8_0(ggml_tensor * tensor, const void * src, void * dst) {
static void ggml_backend_cann_transform_q8_0(const ggml_tensor * tensor, const void * src, void * dst) {
int64_t n_elems = ggml_nelements(tensor);
int64_t groups = n_elems / QK8_0;
size_t quant_bytes = n_elems * sizeof(uint8_t);
@@ -1075,7 +1075,7 @@ static void ggml_backend_cann_transform_back_q8_0(const ggml_tensor * tensor, co
* @param dst Pointer to the destination buffer where transformed data will be
* stored.
*/
static void ggml_backend_cann_transform(ggml_tensor * tensor, const void * src, void * dst) {
static void ggml_backend_cann_transform(const ggml_tensor * tensor, const void * src, void * dst) {
switch (tensor->type) {
case GGML_TYPE_Q4_0:
ggml_backend_cann_transform_q4_0(tensor, src, dst);
+2 -2
View File
@@ -650,9 +650,9 @@ function(ggml_add_cpu_backend_variant_impl tag_name)
# Fetch KleidiAI sources:
include(FetchContent)
set(KLEIDIAI_COMMIT_TAG "v1.31.0")
set(KLEIDIAI_COMMIT_TAG "v1.32.0")
set(KLEIDIAI_DOWNLOAD_URL "https://github.com/ARM-software/kleidiai/releases/download/${KLEIDIAI_COMMIT_TAG}/kleidiai-${KLEIDIAI_COMMIT_TAG}-src.tar.gz")
set(KLEIDIAI_RELEASE_ARCHIVE_MD5 "930495f3aeb16b25dc18a2bde45924f4")
set(KLEIDIAI_RELEASE_ARCHIVE_MD5 "05f1953428ffe8e8c7f585dc6d508887")
set(KLEIDIAI_FETCH_ARGS
URL ${KLEIDIAI_DOWNLOAD_URL}
+3
View File
@@ -287,6 +287,9 @@ void ggml_vec_dot_q2_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
sumf = vaddvq_f32(sumv);
#else
UNUSED(nb);
UNUSED(x);
UNUSED(y);
ggml_vec_dot_q2_0_q8_0_generic(n, s, bs, vx, bx, vy, by, nrc);
return;
#endif
+11 -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;
@@ -2105,6 +2110,10 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm
{
ggml_compute_forward_dsv4_hc_post(params, tensor);
} break;
case GGML_OP_TOP_K_MASK:
{
ggml_compute_forward_top_k_mask(params, tensor);
} break;
case GGML_OP_MAP_CUSTOM1:
{
ggml_compute_forward_map_custom1(params, tensor);
@@ -2288,6 +2297,7 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) {
case GGML_OP_DSV4_HC_COMB:
case GGML_OP_DSV4_HC_PRE:
case GGML_OP_DSV4_HC_POST:
case GGML_OP_TOP_K_MASK:
{
n_tasks = n_threads;
} break;
+169 -89
View File
@@ -201,85 +201,117 @@ static struct kai_matmul_uker_config q4_k_matmul_config() {
return config;
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static size_t q4_k_get_m_step() {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
return api.get_step(&config).m;
static struct kai_matmul_uker_config default_matmul_config() {
return {};
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static size_t q4_k_get_n_step() {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
return api.get_step(&config).n;
}
template<
struct kai_matmul_uker_api (*GetApi)(void),
struct kai_matmul_uker_config (*GetConfig)(void),
size_t MR, size_t NR, size_t KR, size_t SR>
struct matmul_uker_adapter {
static size_t get_m_step() {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
return api.get_step(&config).m;
}
template<size_t Value>
static size_t q4_k_get_constant() {
return Value;
}
static size_t get_n_step() {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
return api.get_step(&config).n;
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static size_t q4_k_get_dst_offset(size_t m_idx, size_t n_idx, size_t dst_stride) {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
const struct kai_matmul_uker_dst_dim_args index = { m_idx, n_idx };
const struct kai_matmul_uker_dst_stride_args stride = { dst_stride };
return api.get_dst_offset(&config, &index, &stride);
}
static size_t get_mr() {
if constexpr (MR == 0) {
return get_m_step();
}
return MR;
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static size_t q4_k_get_lhs_packed_offset(size_t m_idx, size_t k, size_t bl) {
GGML_ASSERT(bl == KLEIDIAI_Q4_K_BLOCK_LENGTH);
static size_t get_nr() {
if constexpr (NR == 0) {
return get_n_step();
}
return NR;
}
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
const struct kai_matmul_uker_lhs_dim_args shape = { m_idx, k };
const struct kai_matmul_uker_lhs_dim_args index = { m_idx, 0 };
const struct kai_matmul_uker_lhs_stride_args stride = api.get_lhs_stride(&config, &shape);
return api.get_lhs_offset(&config, &index, &stride);
}
static size_t get_kr() {
return KR;
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static size_t q4_k_get_rhs_packed_offset(size_t n_idx, size_t k, size_t bl) {
GGML_ASSERT(bl == KLEIDIAI_Q4_K_BLOCK_LENGTH);
static size_t get_sr() {
return SR;
}
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
const struct kai_matmul_uker_rhs_dim_args shape = { n_idx, k };
const struct kai_matmul_uker_rhs_dim_args index = { n_idx, 0 };
const struct kai_matmul_uker_rhs_stride_args stride = api.get_rhs_stride(&config, &shape);
return api.get_rhs_offset(&config, &index, &stride);
}
static size_t get_dst_offset(size_t m_idx, size_t n_idx, size_t dst_stride) {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
const struct kai_matmul_uker_dst_dim_args index = { m_idx, n_idx };
const struct kai_matmul_uker_dst_stride_args stride = { dst_stride };
return api.get_dst_offset(&config, &index, &stride);
}
template<struct kai_matmul_uker_api (*GetApi)(void)>
static void q4_k_run_matmul(
size_t m, size_t n, size_t k, size_t bl,
const void * lhs_packed, const void * rhs_packed,
void * dst, size_t dst_stride_row, size_t dst_stride_col,
float clamp_min, float clamp_max) {
GGML_ASSERT(bl == KLEIDIAI_Q4_K_BLOCK_LENGTH);
GGML_ASSERT(dst_stride_col == sizeof(float));
static size_t get_lhs_packed_offset(size_t m_idx, size_t k, size_t bl) {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
GGML_ASSERT(bl == config.format.bl);
const struct kai_matmul_uker_lhs_dim_args shape = { m_idx, k };
const struct kai_matmul_uker_lhs_dim_args index = { m_idx, 0 };
const struct kai_matmul_uker_lhs_stride_args stride = api.get_lhs_stride(&config, &shape);
return api.get_lhs_offset(&config, &index, &stride);
}
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = q4_k_matmul_config();
const struct kai_matmul_uker_lhs_dim_args lhs_shape = { m, k };
const struct kai_matmul_uker_rhs_dim_args rhs_shape = { n, k };
static size_t get_rhs_packed_offset(size_t n_idx, size_t k, size_t bl) {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
GGML_ASSERT(bl == config.format.bl);
const struct kai_matmul_uker_rhs_dim_args shape = { n_idx, k };
const struct kai_matmul_uker_rhs_dim_args index = { n_idx, 0 };
const struct kai_matmul_uker_rhs_stride_args stride = api.get_rhs_stride(&config, &shape);
return api.get_rhs_offset(&config, &index, &stride);
}
struct kai_matmul_uker_args args = {};
args.flags = KAI_MATMUL_UKER_FLAGS_ARGS_CLAMP;
args.shape = { m, n, k };
args.operand.lhs.ptr = lhs_packed;
args.operand.lhs.stride = api.get_lhs_stride(&config, &lhs_shape);
args.operand.rhs.ptr = rhs_packed;
args.operand.rhs.stride = api.get_rhs_stride(&config, &rhs_shape);
args.operand.dst.ptr = dst;
args.operand.dst.stride.m = dst_stride_row;
args.activation.clamp.min_ptr = &clamp_min;
args.activation.clamp.max_ptr = &clamp_max;
api.run(&config, &args);
}
static void run(
size_t m, size_t n, size_t k, size_t bl,
const void * lhs_packed, const void * rhs_packed,
void * dst, size_t dst_stride_row, size_t dst_stride_col,
float clamp_min, float clamp_max) {
const struct kai_matmul_uker_api api = GetApi();
const struct kai_matmul_uker_config config = GetConfig();
GGML_ASSERT(bl == config.format.bl);
GGML_ASSERT(dst_stride_col == sizeof(float));
const struct kai_matmul_uker_lhs_dim_args lhs_shape = { m, k };
const struct kai_matmul_uker_rhs_dim_args rhs_shape = { n, k };
struct kai_matmul_uker_args args = {};
args.flags = KAI_MATMUL_UKER_FLAGS_ARGS_CLAMP;
args.shape = { m, n, k };
args.operand.lhs.ptr = lhs_packed;
args.operand.lhs.stride = api.get_lhs_stride(&config, &lhs_shape);
args.operand.rhs.ptr = rhs_packed;
args.operand.rhs.stride = api.get_rhs_stride(&config, &rhs_shape);
args.operand.dst.ptr = dst;
args.operand.dst.stride.m = dst_stride_row;
args.activation.clamp.min_ptr = &clamp_min;
args.activation.clamp.max_ptr = &clamp_max;
api.run(&config, &args);
}
};
using q4_k_gemm_adapter = matmul_uker_adapter<
kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa,
q4_k_matmul_config, 0, 0, 4, 2>;
using q4_k_gemv_adapter = matmul_uker_adapter<
kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot,
q4_k_matmul_config, 0, 0, 4, 2>;
// GEMM and GEMV share the RHS layout only at SVE256 (NR=8, KR=8, SR=1).
using sve_q8_gemm_adapter = matmul_uker_adapter<
kai_matmul_clamp_f32_qai8dxp4x8sf32_qsi8cxp4vsx8sf32bf32_16x4vs_sve_i8mm,
default_matmul_config, 4, 0, 8, 1>;
using sve_q8_gemv_adapter = matmul_uker_adapter<
kai_matmul_clamp_f32_qai8dxp1x8_qsi8cxp8x8_1x8_sve_dot,
default_matmul_config, 1, 8, 8, 1>;
static struct kai_matmul_pack_lhs_uker_config q4_k_lhs_pack_config() {
struct kai_matmul_pack_lhs_uker_config config = {};
@@ -628,7 +660,7 @@ static ggml_kleidiai_kernels gemm_gemv_kernels[] = {
/* .packed_size_ex = */ &rhs_ps_fn5<kai_get_rhs_packed_size_rhs_pack_nxk_qsi4c32pscalef16_qsu4c32s16s0>,
/* .pack_func_ex = */ &rhs_pack_fn12<kai_run_rhs_pack_nxk_qsi4c32pscalef16_qsu4c32s16s0>,
},
/* .required_cpu = */ CPU_FEATURE_SVE | CPU_FEATURE_I8MM | CPU_FEATURE_DOTPROD,
/* .required_cpu = */ CPU_FEATURE_SVE | CPU_FEATURE_SVEI8MM | CPU_FEATURE_DOTPROD,
/* .lhs_type = */ GGML_TYPE_F32,
/* .rhs_type = */ GGML_TYPE_Q4_0,
/* .op_type = */ GGML_TYPE_F32,
@@ -737,16 +769,16 @@ static ggml_kleidiai_kernels gemm_gemv_kernels_q4_k[] = {
{
/* SME2 GEMM */
{
/* .get_m_step = */ &q4_k_get_m_step<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_n_step = */ &q4_k_get_n_step<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_mr = */ &q4_k_get_m_step<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_nr = */ &q4_k_get_n_step<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_kr = */ &q4_k_get_constant<4>,
/* .get_sr = */ &q4_k_get_constant<2>,
/* .get_dst_offset = */ &q4_k_get_dst_offset<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_lhs_offset_ex = */ &q4_k_get_lhs_packed_offset<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_rhs_packed_offset_ex = */ &q4_k_get_rhs_packed_offset<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .run_kernel_ex = */ &q4_k_run_matmul<kai_matmul_clamp_f32_f16p4vsx2_qai4c32p16vsx4s1s0sf16_4vsx16vs_sme2_mopa>,
/* .get_m_step = */ q4_k_gemm_adapter::get_m_step,
/* .get_n_step = */ q4_k_gemm_adapter::get_n_step,
/* .get_mr = */ q4_k_gemm_adapter::get_mr,
/* .get_nr = */ q4_k_gemm_adapter::get_nr,
/* .get_kr = */ q4_k_gemm_adapter::get_kr,
/* .get_sr = */ q4_k_gemm_adapter::get_sr,
/* .get_dst_offset = */ q4_k_gemm_adapter::get_dst_offset,
/* .get_lhs_offset_ex = */ q4_k_gemm_adapter::get_lhs_packed_offset,
/* .get_rhs_packed_offset_ex = */ q4_k_gemm_adapter::get_rhs_packed_offset,
/* .run_kernel_ex = */ q4_k_gemm_adapter::run,
},
{
/* .get_offset = */ kai_get_lhs_offset_lhs_pack_f16pmrx2_f32_neon,
@@ -756,16 +788,16 @@ static ggml_kleidiai_kernels gemm_gemv_kernels_q4_k[] = {
},
/* SME2 GEMV */
{
/* .get_m_step = */ &q4_k_get_m_step<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_n_step = */ &q4_k_get_n_step<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_mr = */ &q4_k_get_m_step<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_nr = */ &q4_k_get_n_step<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_kr = */ &q4_k_get_constant<4>,
/* .get_sr = */ &q4_k_get_constant<2>,
/* .get_dst_offset = */ &q4_k_get_dst_offset<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_lhs_offset_ex = */ &q4_k_get_lhs_packed_offset<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_rhs_packed_offset_ex = */ &q4_k_get_rhs_packed_offset<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .run_kernel_ex = */ &q4_k_run_matmul<kai_matmul_clamp_f32_qsi8d32p1x4_qai4c32p16vsx4s1s0sf16_1x16vs_sme2_dot>,
/* .get_m_step = */ q4_k_gemv_adapter::get_m_step,
/* .get_n_step = */ q4_k_gemv_adapter::get_n_step,
/* .get_mr = */ q4_k_gemv_adapter::get_mr,
/* .get_nr = */ q4_k_gemv_adapter::get_nr,
/* .get_kr = */ q4_k_gemv_adapter::get_kr,
/* .get_sr = */ q4_k_gemv_adapter::get_sr,
/* .get_dst_offset = */ q4_k_gemv_adapter::get_dst_offset,
/* .get_lhs_offset_ex = */ q4_k_gemv_adapter::get_lhs_packed_offset,
/* .get_rhs_packed_offset_ex = */ q4_k_gemv_adapter::get_rhs_packed_offset,
/* .run_kernel_ex = */ q4_k_gemv_adapter::run,
},
{
/* .get_offset = */ q4_k_lhs_get_offset,
@@ -883,6 +915,54 @@ static ggml_kleidiai_kernels gemm_gemv_kernels_q8[] = {
/* .rhs_type = */ GGML_TYPE_Q8_0,
/* .op_type = */ GGML_TYPE_F32,
},
{
/* SVE I8MM GEMM */
{
/* .get_m_step = */ sve_q8_gemm_adapter::get_m_step,
/* .get_n_step = */ sve_q8_gemm_adapter::get_n_step,
/* .get_mr = */ sve_q8_gemm_adapter::get_mr,
/* .get_nr = */ sve_q8_gemm_adapter::get_nr,
/* .get_kr = */ sve_q8_gemm_adapter::get_kr,
/* .get_sr = */ sve_q8_gemm_adapter::get_sr,
/* .get_dst_offset = */ sve_q8_gemm_adapter::get_dst_offset,
/* .get_lhs_offset_ex = */ sve_q8_gemm_adapter::get_lhs_packed_offset,
/* .get_rhs_packed_offset_ex = */ sve_q8_gemm_adapter::get_rhs_packed_offset,
/* .run_kernel_ex = */ sve_q8_gemm_adapter::run,
},
/* .gemm_lhs_info = */ {
/* .get_offset = */ kai_get_lhs_offset_lhs_quant_pack_qai8dxp_f32,
/* .get_packed_offset_ex = */ &lhs_offs_fn5<kai_get_lhs_packed_offset_lhs_quant_pack_qai8dxp_f32>,
/* .packed_size_ex = */ &lhs_ps_fn5<kai_get_lhs_packed_size_lhs_quant_pack_qai8dxp_f32>,
/* .pack_func_ex = */ &lhs_pack_float_fn9_no_bl<kai_run_lhs_quant_pack_qai8dxp_f32>,
},
/* SVE DOT GEMV */
{
/* .get_m_step = */ sve_q8_gemv_adapter::get_m_step,
/* .get_n_step = */ sve_q8_gemv_adapter::get_n_step,
/* .get_mr = */ sve_q8_gemv_adapter::get_mr,
/* .get_nr = */ sve_q8_gemv_adapter::get_nr,
/* .get_kr = */ sve_q8_gemv_adapter::get_kr,
/* .get_sr = */ sve_q8_gemv_adapter::get_sr,
/* .get_dst_offset = */ sve_q8_gemv_adapter::get_dst_offset,
/* .get_lhs_offset_ex = */ sve_q8_gemv_adapter::get_lhs_packed_offset,
/* .get_rhs_packed_offset_ex = */ sve_q8_gemv_adapter::get_rhs_packed_offset,
/* .run_kernel_ex = */ sve_q8_gemv_adapter::run,
},
/* .gemv_lhs_info = */ {
/* .get_offset = */ kai_get_lhs_offset_lhs_quant_pack_qai8dxp_f32,
/* .get_packed_offset_ex = */ &lhs_offs_fn5<kai_get_lhs_packed_offset_lhs_quant_pack_qai8dxp_f32>,
/* .packed_size_ex = */ &lhs_ps_fn5<kai_get_lhs_packed_size_lhs_quant_pack_qai8dxp_f32>,
/* .pack_func_ex = */ &lhs_pack_float_fn9_no_bl<kai_run_lhs_quant_pack_qai8dxp_f32>,
},
/* .rhs_info = */ {
/* .packed_size_ex = */ &rhs_ps_fn5<kai_get_rhs_packed_size_rhs_pack_nxk_qsi8cxp_qsi8cx_neon>,
/* .pack_func_ex = */ &rhs_pack_scale_fn12<kai_run_rhs_pack_nxk_qsi8cxp_qsi8cx_neon>,
},
/* .required_cpu = */ CPU_FEATURE_SVE | CPU_FEATURE_SVEI8MM | CPU_FEATURE_DOTPROD,
/* .lhs_type = */ GGML_TYPE_F32,
/* .rhs_type = */ GGML_TYPE_Q8_0,
/* .op_type = */ GGML_TYPE_F32,
},
{
/* I8MM GEMM */
{
+2 -1
View File
@@ -13,7 +13,8 @@ enum cpu_feature {
CPU_FEATURE_SVE = 4,
CPU_FEATURE_SME = 8,
CPU_FEATURE_SME2 = 16,
CPU_FEATURE_FP16 = 32
CPU_FEATURE_FP16 = 32,
CPU_FEATURE_SVEI8MM = 64
};
inline cpu_feature& operator|=(cpu_feature& lhs, cpu_feature rhs) {
+7
View File
@@ -319,8 +319,10 @@ static void init_kleidiai_context(void) {
size_t detected_smcus = 0;
// The fixed-width SVE kernels require 32-byte vectors, including the Q8 shared RHS layout.
ctx.features = (runtime_feat.has_dotprod ? CPU_FEATURE_DOTPROD : CPU_FEATURE_NONE) |
(runtime_feat.has_i8mm ? CPU_FEATURE_I8MM : CPU_FEATURE_NONE) |
(runtime_feat.has_svei8mm ? CPU_FEATURE_SVEI8MM : CPU_FEATURE_NONE) |
(runtime_feat.has_fp16 ? CPU_FEATURE_FP16 : CPU_FEATURE_NONE) |
(runtime_feat.sve_cnt == QK8_0 ? CPU_FEATURE_SVE : CPU_FEATURE_NONE);
@@ -1616,6 +1618,11 @@ public:
const size_t kr = kernel->get_kr();
const size_t sr = kernel->get_sr();
const ggml_type rhs_type = kernels->rhs_type;
if (rhs_type == GGML_TYPE_Q8_0 && (kernels->required_cpu & CPU_FEATURE_SVE)) {
GGML_ASSERT(nr == kernels->gemv.get_nr());
GGML_ASSERT(kr == kernels->gemv.get_kr());
GGML_ASSERT(sr == kernels->gemv.get_sr());
}
const size_t block_len = rhs_type == GGML_TYPE_Q8_0 ? QK8_0 :
rhs_type == GGML_TYPE_Q4_0 ? QK4_0 :
rhs_type == GGML_TYPE_Q4_K ? GGML_KLEIDIAI_Q4_K_BL :
+160 -195
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));
}
}
@@ -1909,213 +1914,87 @@ void ggml_compute_forward_repeat_back(
}
// ggml_compute_forward_concat
static void ggml_compute_forward_concat_any(
const ggml_compute_params * params,
ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
// rows of dst copied together, so that a strided source is read along neighboring rows
static constexpr int64_t GGML_CONCAT_TILE = 32;
const int ith = params->ith;
const int nth = params->nth;
GGML_TENSOR_BINARY_OP_LOCALS
const int32_t dim = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(dim >= 0 && dim < 4);
GGML_ASSERT(ggml_is_contiguous_rows(src0));
GGML_ASSERT(ggml_is_contiguous_rows(src1));
int64_t o[4] = {0, 0, 0, 0};
if (dim == 0) {
GGML_ASSERT(src0->ne[0] % ggml_blck_size(src0->type) == 0);
GGML_ASSERT(src1->ne[0] % ggml_blck_size(src1->type) == 0);
o[dim] = src0->ne[dim]/ggml_blck_size(src0->type);
} else {
o[dim] = src0->ne[dim];
}
// Region 1: copy rows from src0
for (int i3 = 0; i3 < ne03; i3++) {
for (int i2 = ith; i2 < ne02; i2 += nth) {
for (int i1 = 0; i1 < ne01; i1++) {
const char * x = (const char *) src0->data + i1*nb01 + i2*nb02 + i3*nb03;
char * y = ( char *) dst->data + i1*nb1 + i2*nb2 + i3*nb3;
memcpy(y, x, ggml_row_size(src0->type, ne00));
}
}
}
// Region 2: copy rows from src1, offset into dst by o[]
for (int i3 = 0; i3 < ne13; i3++) {
for (int i2 = ith; i2 < ne12; i2 += nth) {
for (int i1 = 0; i1 < ne11; i1++) {
const char * x = (const char *) src1->data + i1*nb11 + i2*nb12 + i3*nb13;
char * y = ( char *) dst->data + (i1 + o[1])*nb1 + (i2 + o[2])*nb2 + (i3 + o[3])*nb3 + o[0]*nb0;
memcpy(y, x, ggml_row_size(src1->type, ne10));
}
// copy n_rows rows of n elements of es bytes from a source with element stride nb0, element by element
template <size_t es>
static void ggml_concat_copy_strided(char * y, size_t nby, const char * x, size_t nbx, size_t nb0, int64_t n, int64_t n_rows) {
for (int64_t i0 = 0; i0 < n; i0++) {
for (int64_t r = 0; r < n_rows; r++) {
memcpy(y + r*nby + i0*es, x + r*nbx + i0*nb0, es);
}
}
}
static void ggml_compute_forward_concat_i8(
const ggml_compute_params * params,
ggml_tensor * dst) {
// copy n_rows rows of n elements of src into dst, rows are nbx bytes apart in src and nby bytes apart in dst
static void ggml_concat_copy_rows(char * y, size_t nby, const ggml_tensor * src, const char * x, size_t nbx, int64_t n, int64_t n_rows) {
const size_t es = ggml_type_size(src->type);
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(int8_t));
const int ith = params->ith;
const int nth = params->nth;
GGML_TENSOR_BINARY_OP_LOCALS
const int32_t dim = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(dim >= 0 && dim < 4);
int64_t o[4] = {0, 0, 0, 0};
o[dim] = src0->ne[dim];
const int8_t * x;
// TODO: smarter multi-theading
for (int i3 = 0; i3 < ne3; i3++) {
for (int i2 = ith; i2 < ne2; i2 += nth) {
for (int i1 = 0; i1 < ne1; i1++) {
for (int i0 = 0; i0 < ne0; i0++) {
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
x = (const int8_t *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
} else {
x = (const int8_t *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
}
int8_t * y = (int8_t *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
*y = *x;
}
}
}
}
}
static void ggml_compute_forward_concat_f16(
const ggml_compute_params * params,
ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(ggml_fp16_t));
const int ith = params->ith;
const int nth = params->nth;
GGML_TENSOR_BINARY_OP_LOCALS
const int32_t dim = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(dim >= 0 && dim < 4);
int64_t o[4] = {0, 0, 0, 0};
o[dim] = src0->ne[dim];
const ggml_fp16_t * x;
// TODO: smarter multi-theading
for (int i3 = 0; i3 < ne3; i3++) {
for (int i2 = ith; i2 < ne2; i2 += nth) {
for (int i1 = 0; i1 < ne1; i1++) {
for (int i0 = 0; i0 < ne0; i0++) {
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
x = (const ggml_fp16_t *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
} else {
x = (const ggml_fp16_t *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
}
ggml_fp16_t * y = (ggml_fp16_t *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
*y = *x;
}
}
}
}
}
static void ggml_compute_forward_concat_f32(
const ggml_compute_params * params,
ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
GGML_ASSERT(ggml_type_size(src0->type) == sizeof(float));
const int ith = params->ith;
const int nth = params->nth;
GGML_TENSOR_BINARY_OP_LOCALS
const int32_t dim = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(dim >= 0 && dim < 4);
int64_t o[4] = {0, 0, 0, 0};
o[dim] = src0->ne[dim];
const float * x;
// TODO: smarter multi-theading
for (int i3 = 0; i3 < ne3; i3++) {
for (int i2 = ith; i2 < ne2; i2 += nth) {
for (int i1 = 0; i1 < ne1; i1++) {
for (int i0 = 0; i0 < ne0; i0++) {
if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) {
x = (const float *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03);
} else {
x = (const float *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13);
}
float * y = (float *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3);
*y = *x;
}
}
if (src->nb[0] == es) {
const size_t row_size = ggml_row_size(src->type, n);
for (int64_t r = 0; r < n_rows; r++) {
memcpy(y + r*nby, x + r*nbx, row_size);
}
return;
}
switch (es) {
case 1: ggml_concat_copy_strided<1>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
case 2: ggml_concat_copy_strided<2>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
case 4: ggml_concat_copy_strided<4>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
case 8: ggml_concat_copy_strided<8>(y, nby, x, nbx, src->nb[0], n, n_rows); break;
default: GGML_ABORT("unsupported element size");
}
}
// each dst row comes from src0 or src1, or for dim 0 from the row of src0 followed by the row of src1
void ggml_compute_forward_concat(
const ggml_compute_params * params,
ggml_tensor * dst) {
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
switch (src0->type) {
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_I16:
{
ggml_compute_forward_concat_f16(params, dst);
} break;
case GGML_TYPE_I8:
{
ggml_compute_forward_concat_i8(params, dst);
} break;
case GGML_TYPE_F32:
case GGML_TYPE_I32:
{
ggml_compute_forward_concat_f32(params, dst);
} break;
default:
{
ggml_compute_forward_concat_any(params, dst);
}
GGML_TENSOR_BINARY_OP_LOCALS
const int32_t dim = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(dim >= 0 && dim < 4);
GGML_ASSERT(nb0 == ggml_type_size(dst->type));
GGML_ASSERT(!ggml_is_quantized(dst->type) || (nb00 == nb0 && nb10 == nb0));
GGML_ASSERT(dim != 0 || (ne00 % ggml_blck_size(dst->type) == 0 && ne10 % ggml_blck_size(dst->type) == 0));
const size_t y1 = dim == 0 ? ggml_row_size(dst->type, ne00) : 0;
const int64_t nr = ne1*ne2*ne3;
const int64_t dr = (nr + params->nth - 1)/params->nth;
const int64_t ir0 = dr*params->ith;
const int64_t ir1 = MIN(ir0 + dr, nr);
for (int64_t ir = ir0; ir < ir1; ) {
int64_t i[4] = { 0, ir % ne1, (ir/ne1) % ne2, ir/(ne1*ne2) };
// a tile of consecutive rows sharing i2, i3 and their source
int64_t nt = MIN(MIN(ir1 - ir, ne1 - i[1]), GGML_CONCAT_TILE);
if (dim == 1 && i[1] < ne01) {
nt = MIN(nt, ne01 - i[1]);
}
char * y = (char *) dst->data + i[1]*nb1 + i[2]*nb2 + i[3]*nb3;
if (dim == 0 || i[dim] < src0->ne[dim]) {
ggml_concat_copy_rows(y, nb1, src0, (const char *) src0->data + i[1]*nb01 + i[2]*nb02 + i[3]*nb03, nb01, ne00, nt);
}
if (dim == 0 || i[dim] >= src0->ne[dim]) {
i[dim] -= src0->ne[dim]; // i[0] does not address rows
ggml_concat_copy_rows(y + y1, nb1, src1, (const char *) src1->data + i[1]*nb11 + i[2]*nb12 + i[3]*nb13, nb11, ne10, nt);
}
ir += nt;
}
}
@@ -11736,6 +11615,92 @@ void ggml_compute_forward_dsv4_hc_post(
}
}
// ggml_compute_forward_top_k_mask
void ggml_compute_forward_top_k_mask(
const ggml_compute_params * params,
ggml_tensor * dst) {
const ggml_tensor * mask = dst->src[0];
const ggml_tensor * top_k = dst->src[1];
const ggml_tensor * score = dst->src[2];
const ggml_tensor * blocks = dst->src[3];
const ggml_tensor * extra = dst->src[4];
GGML_ASSERT(dst->type == mask->type);
GGML_ASSERT(dst->nb[0] == ggml_type_size(dst->type));
const int64_t n_kv = mask->ne[0];
const int64_t ne1 = dst->ne[1];
const int64_t ne2 = dst->ne[2];
const int64_t n_rows = ggml_nrows(dst);
const int64_t n_top = top_k->ne[0];
const int64_t n_blk = blocks ? blocks->ne[0] : 1;
const int64_t n_pick = blocks ? blocks->ne[1] : score ? score->ne[0] : n_kv;
const int64_t n_extra = extra ? extra->ne[0] : 0;
const size_t ts = ggml_type_size(dst->type);
const int ith = params->ith;
const int nth = params->nth;
for (int64_t ir = ith; ir < n_rows; ir += nth) {
const int64_t i1 = ir % ne1;
const int64_t i2 = (ir / ne1) % ne2;
const int64_t i3 = ir / (ne1*ne2);
const char * mask_row = (const char *) mask->data
+ (i1 % mask->ne[1])*mask->nb[1] + (i2 % mask->ne[2])*mask->nb[2] + (i3 % mask->ne[3])*mask->nb[3];
char * dst_row = (char *) dst->data + i1*dst->nb[1] + i2*dst->nb[2] + i3*dst->nb[3];
if (dst->type == GGML_TYPE_F16) {
const ggml_fp16_t ninf = GGML_CPU_FP32_TO_FP16(-INFINITY);
for (int64_t c = 0; c < n_kv; ++c) {
((ggml_fp16_t *) dst_row)[c] = ninf;
}
} else {
for (int64_t c = 0; c < n_kv; ++c) {
((float *) dst_row)[c] = -INFINITY;
}
}
auto keep = [&](int32_t c) {
if (c >= 0 && c < n_kv) {
memcpy(dst_row + c*ts, mask_row + c*ts, ts);
}
};
const char * top_k_row = (const char *) top_k->data + i1*top_k->nb[1] + i2*top_k->nb[2] + i3*top_k->nb[3];
for (int64_t j = 0; j < n_top; ++j) {
const int32_t p = *(const int32_t *) (top_k_row + j*top_k->nb[0]);
if (p < 0 || p >= n_pick) {
continue;
}
if (score) {
const float s = *(const float *) ((const char *) score->data
+ p*score->nb[0] + i1*score->nb[1] + i2*score->nb[2] + i3*score->nb[3]);
if (!(s > -INFINITY)) {
continue;
}
}
if (!blocks) {
keep(p);
continue;
}
for (int64_t k = 0; k < n_blk; ++k) {
keep(*(const int32_t *) ((const char *) blocks->data + k*blocks->nb[0] + p*blocks->nb[1]));
}
}
if (extra) {
const char * extra_row = (const char *) extra->data + i1*extra->nb[1] + i2*extra->nb[2] + i3*extra->nb[3];
for (int64_t j = 0; j < n_extra; ++j) {
keep(*(const int32_t *) (extra_row + j*extra->nb[0]));
}
}
}
}
// ggml_compute_forward_rwkv_wkv7
static void ggml_compute_forward_rwkv_wkv7_f32(
+1
View File
@@ -108,6 +108,7 @@ void ggml_compute_forward_lightning_indexer(const struct ggml_compute_params * p
void ggml_compute_forward_dsv4_hc_comb(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_dsv4_hc_pre(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_dsv4_hc_post(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_top_k_mask(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_map_custom1(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_map_custom2(const struct ggml_compute_params * params, struct ggml_tensor * dst);
void ggml_compute_forward_map_custom3(const struct ggml_compute_params * params, struct ggml_tensor * dst);
+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;
+10 -3
View File
@@ -42,6 +42,7 @@ extern "C" {
#define GGML_CPU_COMPUTE_FP32_TO_FP16(x) neon_compute_fp32_to_fp16(x)
#define GGML_CPU_FP16_TO_FP32(x) GGML_CPU_COMPUTE_FP16_TO_FP32(x)
#define GGML_CPU_FP32_TO_FP16(x) GGML_CPU_COMPUTE_FP32_TO_FP16(x)
static inline float neon_compute_fp16_to_fp32(ggml_fp16_t h) {
__fp16 tmp;
@@ -150,9 +151,15 @@ extern float ggml_table_f32_ue4m3[1 << 8];
#define GGML_CPU_FP32_TO_FP16(x) GGML_CPU_COMPUTE_FP32_TO_FP16(x)
#endif
// On ARM NEON, it's quicker to directly convert x -> x instead of calling into ggml_lookup_fp16_to_fp32,
// so we define GGML_CPU_FP16_TO_FP32 and GGML_CPU_FP32_TO_FP16 elsewhere for NEON.
// This is also true for POWER9.
// a branch that defines a GGML_CPU_COMPUTE_* macro must also select it, or the fallback takes over silently
#if defined(GGML_CPU_COMPUTE_FP16_TO_FP32) && !defined(GGML_CPU_FP16_TO_FP32) && !defined(__F16C__) // see: https://github.com/ggml-org/llama.cpp/issues/30342
#error "a branch defined GGML_CPU_COMPUTE_FP16_TO_FP32 without selecting it as GGML_CPU_FP16_TO_FP32"
#endif
#if defined(GGML_CPU_COMPUTE_FP32_TO_FP16) && !defined(GGML_CPU_FP32_TO_FP16)
#error "a branch defined GGML_CPU_COMPUTE_FP32_TO_FP16 without selecting it as GGML_CPU_FP32_TO_FP16"
#endif
// fall back to the lookup table / the portable software conversion for whichever direction no branch selected
#if !defined(GGML_CPU_FP16_TO_FP32)
inline static float ggml_lookup_fp16_to_fp32(ggml_fp16_t f) {
uint16_t s;
+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) {
+86
View File
@@ -460,6 +460,92 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
}
}
#ifndef GGML_GELU_QUICK_FP16
// computed in f32 without the fp16 table, which loses precision on f32 input
void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x) {
int i = 0;
#if defined(__AVX512F__) && defined(__AVX512DQ__)
for (; i + 15 < n; i += 16) {
_mm512_storeu_ps(y + i, ggml_v_gelu_quick(_mm512_loadu_ps(x + i)));
}
#elif defined(__AVX2__) && defined(__FMA__)
for (; i + 7 < n; i += 8) {
_mm256_storeu_ps(y + i, ggml_v_gelu_quick(_mm256_loadu_ps(x + i)));
}
#elif defined(__SSE2__)
for (; i + 3 < n; i += 4) {
_mm_storeu_ps(y + i, ggml_v_gelu_quick(_mm_loadu_ps(x + i)));
}
#elif defined(__ARM_FEATURE_SVE) && defined(__aarch64__)
const int vlen = svcntw();
for (; i < n; i += vlen) {
const svbool_t pg = svwhilelt_b32_s32(i, n);
svst1_f32(pg, y + i, ggml_v_gelu_quick(pg, svld1_f32(pg, x + i)));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, ggml_v_gelu_quick(vld1q_f32(x + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(ggml_v_gelu_quick(vec_xl(0, x + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
vfloat32m2_t vx = __riscv_vle32_v_f32m2(&x[i], vl);
vfloat32m2_t vy = ggml_v_gelu_quick_m2(vx, vl);
__riscv_vse32_v_f32m2(&y[i], vy, vl);
}
#endif
for (; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]);
}
}
void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g) {
int i = 0;
#if defined(__AVX512F__) && defined(__AVX512DQ__)
for (; i + 15 < n; i += 16) {
_mm512_storeu_ps(y + i, _mm512_mul_ps(ggml_v_gelu_quick(_mm512_loadu_ps(x + i)), _mm512_loadu_ps(g + i)));
}
#elif defined(__AVX2__) && defined(__FMA__)
for (; i + 7 < n; i += 8) {
_mm256_storeu_ps(y + i, _mm256_mul_ps(ggml_v_gelu_quick(_mm256_loadu_ps(x + i)), _mm256_loadu_ps(g + i)));
}
#elif defined(__SSE2__)
for (; i + 3 < n; i += 4) {
_mm_storeu_ps(y + i, _mm_mul_ps(ggml_v_gelu_quick(_mm_loadu_ps(x + i)), _mm_loadu_ps(g + i)));
}
#elif defined(__ARM_FEATURE_SVE) && defined(__aarch64__)
const int vlen = svcntw();
for (; i < n; i += vlen) {
const svbool_t pg = svwhilelt_b32_s32(i, n);
svst1_f32(pg, y + i, svmul_f32_x(pg, ggml_v_gelu_quick(pg, svld1_f32(pg, x + i)), svld1_f32(pg, g + i)));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, vmulq_f32(ggml_v_gelu_quick(vld1q_f32(x + i)), vld1q_f32(g + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(vec_mul(ggml_v_gelu_quick(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
vfloat32m2_t vx = __riscv_vle32_v_f32m2(&x[i], vl);
vfloat32m2_t vg = __riscv_vle32_v_f32m2(&g[i], vl);
vfloat32m2_t vy = __riscv_vfmul_vv_f32m2(ggml_v_gelu_quick_m2(vx, vl), vg, vl);
__riscv_vse32_v_f32m2(&y[i], vy, vl);
}
#endif
for (; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]) * g[i];
}
}
#endif
ggml_float ggml_vec_cvar_f32(const int n, float * y, const float * x, const float mean) {
int i = 0;
ggml_float sum = 0;
+50 -11
View File
@@ -44,7 +44,6 @@ inline static ggml_float ggml_sve_sum_f32x2(svfloat32_t sum_lo, svfloat32_t sum_
#endif
#define GGML_GELU_FP16
#define GGML_GELU_QUICK_FP16
#define GGML_SOFT_MAX_UNROLL 4
#define GGML_VEC_DOT_UNROLL 2
@@ -1054,11 +1053,7 @@ inline static void ggml_vec_gelu_quick_f32(const int n, float * y, const float *
}
}
#else
inline static void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x) {
for (int i = 0; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]);
}
}
void ggml_vec_gelu_quick_f32(const int n, float * y, const float * x);
#endif
// Sigmoid Linear Unit (SiLU) function
@@ -1143,6 +1138,13 @@ inline static svfloat32_t ggml_v_silu(svbool_t pg, svfloat32_t x) {
return svdiv_f32_x(pg, x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static svfloat32_t ggml_v_gelu_quick(svbool_t pg, svfloat32_t x) {
const svfloat32_t one = svdup_n_f32_x(pg, 1.0f);
const svfloat32_t exp_cx = ggml_v_expf(pg, svmul_n_f32_x(pg, x, GELU_QUICK_COEF));
return svdiv_f32_x(pg, x, svadd_f32_x(pg, one, exp_cx));
}
#elif defined(__ARM_NEON) && defined(__aarch64__)
// adapted from arm limited optimized routine
@@ -1182,6 +1184,13 @@ inline static float32x4_t ggml_v_silu(float32x4_t x) {
return vdivq_f32(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static float32x4_t ggml_v_gelu_quick(float32x4_t x) {
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t exp_cx = ggml_v_expf(vmulq_n_f32(x, GELU_QUICK_COEF));
return vdivq_f32(x, vaddq_f32(one, exp_cx));
}
#elif defined(__AVX512F__) && defined(__AVX512DQ__)
// adapted from arm limited optimized routine
@@ -1225,6 +1234,13 @@ inline static __m512 ggml_v_silu(__m512 x) {
return _mm512_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m512 ggml_v_gelu_quick(__m512 x) {
const __m512 one = _mm512_set1_ps(1);
const __m512 exp_cx = ggml_v_expf(_mm512_mul_ps(x, _mm512_set1_ps(GELU_QUICK_COEF)));
return _mm512_div_ps(x, _mm512_add_ps(one, exp_cx));
}
#elif defined(__AVX2__) && defined(__FMA__)
// adapted from arm limited optimized routine
@@ -1280,6 +1296,13 @@ inline static __m256 ggml_v_silu(__m256 x) {
return _mm256_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m256 ggml_v_gelu_quick(__m256 x) {
const __m256 one = _mm256_set1_ps(1);
const __m256 exp_cx = ggml_v_expf(_mm256_mul_ps(x, _mm256_set1_ps(GELU_QUICK_COEF)));
return _mm256_div_ps(x, _mm256_add_ps(one, exp_cx));
}
#elif defined(__SSE2__) // __AVX2__ / __ARM_NEON
#if defined(__FMA__)
@@ -1334,6 +1357,13 @@ inline static __m128 ggml_v_silu(__m128 x) {
return _mm_div_ps(x, one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static __m128 ggml_v_gelu_quick(__m128 x) {
const __m128 one = _mm_set1_ps(1);
const __m128 exp_cx = ggml_v_expf(_mm_mul_ps(x, _mm_set1_ps(GELU_QUICK_COEF)));
return _mm_div_ps(x, _mm_add_ps(one, exp_cx));
}
#elif defined(__VXE__) || defined(__VXE2__)
// adapted from arm limited optimized routine
@@ -1374,6 +1404,13 @@ inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
return vec_div(v_x, v_one_plus_exp_neg_x);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static float32x4_t ggml_v_gelu_quick(float32x4_t x) {
const float32x4_t one = vec_splats(1.0f);
const float32x4_t exp_cx = ggml_v_expf(vec_mul(x, vec_splats(GELU_QUICK_COEF)));
return vec_div(x, vec_add(one, exp_cx));
}
#elif defined(__riscv_v_intrinsic)
// adapted from arm limited optimized routine
@@ -1426,6 +1463,12 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
}
// gelu_quick(x) = x / (1 + exp(-1.702 x)); x stays the numerator so it cannot overflow
inline static vfloat32m2_t ggml_v_gelu_quick_m2(vfloat32m2_t x, int vl) {
const vfloat32m2_t exp_cx = ggml_v_expf_m2(__riscv_vfmul_vf_f32m2(x, GELU_QUICK_COEF, vl), vl);
return __riscv_vfdiv_vv_f32m2(x, __riscv_vfadd_vf_f32m2(exp_cx, 1.0f, vl), vl);
}
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __VXE__ / __riscv_v_intrinsic
inline static void ggml_vec_silu_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x) {
@@ -1571,11 +1614,7 @@ inline static void ggml_vec_geglu_quick_f32(const int n, float * y, const float
}
}
#else
inline static void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g) {
for (int i = 0; i < n; ++i) {
y[i] = ggml_gelu_quick_f32(x[i]) * g[i];
}
}
void ggml_vec_geglu_quick_f32(const int n, float * y, const float * x, const float * g);
#endif
inline static void ggml_vec_geglu_quick_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x, const ggml_fp16_t * g) {
+6 -9
View File
@@ -1264,15 +1264,12 @@ struct ggml_tensor_extra_gpu {
struct ggml_cuda_graph {
#ifdef USE_CUDA_GRAPH
~ggml_cuda_graph() {
if (instance != nullptr) {
for (cudaGraphExec_t instance : instances) {
CUDA_CHECK(cudaGraphExecDestroy(instance));
}
if (graph != nullptr) {
CUDA_CHECK(cudaGraphDestroy(graph));
}
}
cudaGraph_t graph = nullptr;
cudaGraphExec_t instance = nullptr;
// the ggml graph is captured as a series of CUDA graphs, so the GPU can run one chunk while the next is enqueued
std::vector<cudaGraphExec_t> instances;
size_t num_nodes = 0;
std::vector<cudaGraphNode_t> nodes;
bool disable_due_to_gpu_arch = false;
@@ -1501,7 +1498,7 @@ struct ggml_backend_cuda_context {
// Check if any CUDA graph has an instance for this context
bool any_cuda_graph_has_instance() const {
for (const auto & [key, graph] : cuda_graphs) {
if (graph && graph->instance != nullptr) {
if (graph && !graph->instances.empty()) {
return true;
}
}
@@ -1551,11 +1548,11 @@ struct ggml_backend_cuda_context {
// pool
std::unique_ptr<ggml_cuda_pool> pools[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS];
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(int device, int stream_no);
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(ggml_backend_cuda_context & ctx, int device, int stream_no);
ggml_cuda_pool & pool(int device) {
if (pools[device][curr_stream_no] == nullptr) {
pools[device][curr_stream_no] = new_pool_for_device(device, curr_stream_no);
pools[device][curr_stream_no] = new_pool_for_device(*this, device, curr_stream_no);
}
return *pools[device][curr_stream_no];
}
+195 -67
View File
@@ -70,6 +70,7 @@
#include "ggml-cuda/cumsum.cuh"
#include "ggml-cuda/fill.cuh"
#include "ggml-cuda/lightning-indexer.cuh"
#include "ggml-cuda/top-k-mask.cuh"
#include "ggml.h"
#include <algorithm>
@@ -213,52 +214,36 @@ static cudaError_t ggml_cuda_device_malloc(void ** ptr, size_t size, int device)
}
#if defined(GGML_USE_HIP)
static int ggml_cuda_parse_id(char devName[]) {
static int ggml_cuda_parse_id(const char * devName) {
// A list of possible Target IDs can be found under the rocclr/clr repo in device.cpp
// these values are not stable so this is susceptible to breakage
// https://github.com/ROCm/clr/blob/amd-staging/rocclr/device/device.cpp
int archMajor = 0x0;
int archMinor = 0x0;
int archNum = GGML_CUDA_CC_OFFSET_AMD;
int archLen = strlen(devName);
char archName[archLen + 1];
// Extract the base architecture model ("gfx908:sramecc+:xnack-" -> "908")
std::string archName = devName;
archName.erase(0, 3);
archName = archName.substr(0, archName.find(':'));
// strip leading 'gfx' while copying into our buffer
if (archLen > 3) {
strcpy(archName, &devName[3]);
archLen -= 3;
}
const auto hex = [](const std::string & s) { return (int) strtoul(s.c_str(), nullptr, 16); };
// trim trailing :xnack- or :sramecc- statuses
archLen = strcspn(archName, ":");
archName[archLen] = '\0';
int cc = 0;
// tease out the version information
if (archLen > 8) {
if (archName.size() > 8) {
// versions labeled generic use '-' as delimiter
// strip the trailing "-generic" then iterate through what remains
if ((strstr(archName, "-generic"))) {
archName[archLen - 8] = '\0';
char * pch;
if ((pch = strtok(archName, "-"))) {
archMajor = (int)strtoul(pch, 0, 16);
if ((pch = strtok(NULL, "-"))) {
archMinor = 0x10 * (int)strtoul(pch, 0, 16);
}
if (archName.compare(archName.size() - 8, 8, "-generic") == 0) {
archName.resize(archName.size() - 8);
const size_t sep = archName.find('-');
cc = 0x100 * hex(archName.substr(0, sep));
if (sep != std::string::npos) {
cc += 0x10 * hex(archName.substr(sep + 1));
}
}
} else if (archLen >= 3) {
// last two digits should be the minor * 0x10 + stepping
archMinor = (int)strtoul(&archName[archLen - 2], 0, 16);
archName[archLen - 2] = '\0';
// only the major version remains
archMajor = (int)strtoul(archName, 0, 16);
} else if (archName.size() >= 3) {
// ordinary IDs are the version in hex
cc = hex(archName);
}
archNum += archMajor * 0x100;
archNum += archMinor;
return archNum;
return GGML_CUDA_CC_OFFSET_AMD + cc;
}
#endif // defined(GGML_USE_HIP)
@@ -457,6 +442,7 @@ const ggml_cuda_device_info & ggml_cuda_info() {
struct ggml_cuda_pool_leg : public ggml_cuda_pool {
static const int MAX_BUFFERS = 256;
ggml_backend_cuda_context & ctx;
int device;
struct ggml_cuda_buffer {
void * ptr = nullptr;
@@ -466,7 +452,8 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
ggml_cuda_buffer buffer_pool[MAX_BUFFERS] = {};
size_t pool_size = 0;
explicit ggml_cuda_pool_leg(int device) :
ggml_cuda_pool_leg(ggml_backend_cuda_context & ctx, int device) :
ctx(ctx),
device(device) {
}
@@ -488,6 +475,20 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
}
}
// captured CUDA graphs can still use freed buffers, so they must be captured again
void invalidate_cuda_graphs() {
#ifdef USE_CUDA_GRAPH
int n_captured = 0;
for (const auto & [key, graph] : ctx.cuda_graphs) {
n_captured += graph->instances.size();
}
if (n_captured > 0) {
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: memory freed, %d captured CUDA graphs must be captured again\n", device, n_captured);
}
ctx.cuda_graphs.clear();
#endif // USE_CUDA_GRAPH
}
void * alloc(size_t size, size_t * actual_size) override {
#ifdef DEBUG_CUDA_MALLOC
int nnz = 0;
@@ -538,6 +539,7 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
device, look_ahead_size/1024.0/1024.0, cached_bytes/1024.0/1024.0);
CUDA_CHECK(cudaDeviceSynchronize());
clear_pool();
invalidate_cuda_graphs();
err = ggml_cuda_device_malloc(&ptr, look_ahead_size, device);
if (err == cudaSuccess) {
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: retry succeeded\n", device);
@@ -566,6 +568,7 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
ggml_cuda_set_device(device);
CUDA_CHECK(cudaFree(ptr));
pool_size -= size;
invalidate_cuda_graphs();
}
};
@@ -720,14 +723,15 @@ struct ggml_cuda_pool_vmm : public ggml_cuda_pool {
};
#endif // defined(GGML_USE_VMM)
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(int device,
[[maybe_unused]] int stream_no) {
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(ggml_backend_cuda_context & ctx,
int device,
[[maybe_unused]] int stream_no) {
#if defined(GGML_USE_VMM)
if (ggml_cuda_info().devices[device].vmm) {
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_vmm(device));
}
#endif // defined(GGML_USE_VMM)
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_leg(device));
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_leg(ctx, device));
}
// destroying a cuBLAS handle while a graph is being captured in a different thread can result in a CUDA error
@@ -1953,6 +1957,61 @@ static bool ggml_cuda_match_shared_expert(const ggml_cgraph * graph, int routed_
return true;
}
// Buffer- and batch-size independent, so graph_optimize produces the same graph topology for every ubatch size (see #29986).
static bool ggml_cuda_mul_mat_q_fusion_matches(const ggml_tensor * up, const ggml_tensor * gate, const ggml_tensor * glu, const int device) {
const ggml_tensor * x_up = up->src[0];
const ggml_tensor * y = up->src[1];
if (up->op != GGML_OP_MUL_MAT || !ggml_cuda_mmq_fusion_supported(x_up->type) || y->type != GGML_TYPE_F32) {
return false;
}
// The fused kernel is only compiled for weights whose rows fill whole tiles (no fallback).
if (ggml_cuda_mmq_needs_fallback(x_up->ne[1])) {
return false;
}
if (!ggml_cuda_should_fuse_mul_mat(up, gate, glu)) {
return false;
}
// The fused write-back applies SWIGLU_OAI with the default alpha and limit of ggml_cuda_op_swiglu_oai_single.
if (ggml_get_glu_op(glu) == GGML_GLU_OP_SWIGLU_OAI && (ggml_get_op_params_f32(glu, 2) != 1.702f || ggml_get_op_params_f32(glu, 3) != 7.0f)) {
return false;
}
// NVIDIA only: the fused write-back relies on the MMA accumulator layout.
return turing_mma_available(ggml_cuda_info().devices[device].cc);
}
static bool ggml_cuda_should_fuse_mul_mat_q(const ggml_tensor * up, const ggml_tensor * gate, const ggml_tensor * glu) {
// A hinted matmul can be fused, but its unfused path may be faster (e.g. FWHT for GGML_HINT_SRC0_IS_HADAMARD).
if (ggml_get_op_params_i32(up, 1) != GGML_HINT_NONE || ggml_get_op_params_i32(gate, 1) != GGML_HINT_NONE) {
return false;
}
// The padding of a compute-buffer weight that is a view cannot be cleared, see ggml_cuda_mul_mat.
const auto bad_padding_clear = [](const ggml_tensor * x) {
return ggml_backend_buffer_get_usage(x->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE &&
ggml_nbytes(x) != ggml_backend_buffer_get_alloc_size(x->buffer, x) && x->view_src;
};
const int device = ggml_cuda_get_device();
if (!ggml_cuda_mul_mat_q_fusion_matches(up, gate, glu, device) || bad_padding_clear(up->src[0]) || bad_padding_clear(gate->src[0])) {
return false;
}
// The batch size checks are done here, not in ggml_cuda_mul_mat_q_fusion_matches.
const ggml_tensor * x_up = up->src[0];
const ggml_tensor * y = up->src[1];
const int cc = ggml_cuda_info().devices[device].cc;
if (ggml_cuda_should_use_mmvq(x_up->type, cc, y->ne[1]) || !ggml_cuda_should_use_mmq(x_up->type, cc, y->ne[1], 0)) {
return false;
}
// The fused kernel needs one block per tile (no stream-k split), so only fuse if the tiling is efficient.
return ggml_cuda_mmq_fusion_is_efficient(x_up->type, x_up->ne[1], y->ne[1], y->ne[2]*y->ne[3], device);
}
static void ggml_cuda_mul_mat(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
GGML_TENSOR_BINARY_OP_LOCALS
@@ -2014,6 +2073,8 @@ static bool ggml_cuda_mul_mat_id_needs_sync(const ggml_tensor * dst, const int c
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src1 = dst->src[1];
const int warp_size = ggml_cuda_info().devices[ggml_cuda_get_device()].warp_size;
if (src1->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
return true;
}
@@ -2032,7 +2093,7 @@ static bool ggml_cuda_mul_mat_id_needs_sync(const ggml_tensor * dst, const int c
return false;
}
if (ggml_cuda_should_use_mmf(src0->type, cc, WARP_SIZE, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
if (ggml_cuda_should_use_mmf(src0->type, cc, warp_size, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
return false;
}
@@ -2050,6 +2111,7 @@ static void ggml_cuda_mul_mat_id(ggml_backend_cuda_context & ctx, ggml_tensor *
GGML_TENSOR_BINARY_OP_LOCALS
const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
const int warp_size = ggml_cuda_info().devices[ggml_cuda_get_device()].warp_size;
// [TAG_MUL_MAT_ID_CUDA_GRAPHS]
if (src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
@@ -2074,7 +2136,7 @@ static void ggml_cuda_mul_mat_id(ggml_backend_cuda_context & ctx, ggml_tensor *
return;
}
if (ggml_cuda_should_use_mmf(src0->type, cc, WARP_SIZE, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
if (ggml_cuda_should_use_mmf(src0->type, cc, warp_size, src0->ne, src0->nb, src1->ne[2], /*mul_mat_id=*/true)) {
ggml_cuda_mul_mat_f(ctx, src0, src1, ids, dst);
return;
}
@@ -2548,6 +2610,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
case GGML_OP_LIGHTNING_INDEXER:
ggml_cuda_lightning_indexer(ctx, dst);
break;
case GGML_OP_TOP_K_MASK:
ggml_cuda_op_top_k_mask(ctx, dst);
break;
default:
return false;
}
@@ -2777,16 +2842,14 @@ static bool ggml_cuda_graph_update_required(ggml_backend_cuda_context * cuda_ctx
return res;
}
static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
static void ggml_cuda_graph_update_executable(cudaGraphExec_t & instance, cudaGraph_t cuda_graph) {
#if CUDART_VERSION >= 12000
cudaGraphExecUpdateResultInfo result_info;
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &result_info);
cudaError_t stat = cudaGraphExecUpdate(instance, cuda_graph, &result_info);
#else
cudaGraphNode_t errorNode;
cudaGraphExecUpdateResult result_info;
cudaError_t stat = cudaGraphExecUpdate(graph->instance, graph->graph, &errorNode, &result_info);
cudaError_t stat = cudaGraphExecUpdate(instance, cuda_graph, &errorNode, &result_info);
#endif // CUDART_VERSION >= 12000
if (stat == cudaErrorGraphExecUpdateFailure) {
@@ -2797,13 +2860,39 @@ static void ggml_cuda_graph_update_executable(ggml_backend_cuda_context * cuda_c
// The pre-existing graph exec cannot be updated due to violated constraints
// so instead clear error and re-instantiate
(void)cudaGetLastError();
CUDA_CHECK(cudaGraphExecDestroy(graph->instance));
graph->instance = nullptr;
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
CUDA_CHECK(cudaGraphExecDestroy(instance));
instance = nullptr;
CUDA_CHECK(cudaGraphInstantiate(&instance, cuda_graph, nullptr, nullptr, 0));
} else {
GGML_ASSERT(stat == cudaSuccess);
}
}
// number of ggml nodes per CUDA graph chunk, 0 captures the whole ggml graph as one CUDA graph
static int ggml_cuda_graph_chunk_size() {
static const int chunk_size = [] {
const char * env = getenv("GGML_CUDA_GRAPH_CHUNK_SIZE");
return env != nullptr ? atoi(env) : 512;
}();
return chunk_size;
}
// end the capture of chunk `idx`, update or create its executable and launch it
static void ggml_cuda_graph_launch_chunk(ggml_backend_cuda_context * cuda_ctx, ggml_cuda_graph * graph, size_t idx) {
cudaGraph_t cuda_graph = nullptr;
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &cuda_graph));
if (idx < graph->instances.size()) {
ggml_cuda_graph_update_executable(graph->instances[idx], cuda_graph);
} else {
GGML_ASSERT(idx == graph->instances.size());
graph->instances.emplace_back(nullptr);
CUDA_CHECK(cudaGraphInstantiate(&graph->instances[idx], cuda_graph, nullptr, nullptr, 0));
}
CUDA_CHECK(cudaGraphDestroy(cuda_graph));
CUDA_CHECK(cudaGraphLaunch(graph->instances[idx], cuda_ctx->stream()));
}
#endif // USE_CUDA_GRAPH
static bool ggml_cuda_should_fuse_rope_set_rows(const ggml_tensor * rope,
@@ -2831,9 +2920,10 @@ static bool ggml_cuda_should_fuse_rope_set_rows(const ggml_tensor * rope,
return false;
}
// Only norm/neox shaders have the fusion code
// Only norm/neox/multi shaders have the fusion code
const int mode = ((const int32_t *) rope->op_params)[2];
if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX) {
if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX &&
mode != GGML_ROPE_TYPE_MROPE && mode != GGML_ROPE_TYPE_IMROPE) {
return false;
}
@@ -4327,6 +4417,18 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
fused_node_count = 3;
break;
}
if (ggml_cuda_should_fuse_mul_mat_q(up, gate, glu)) {
ggml_cuda_mm_fusion_args_host fusion_data{};
fusion_data.gate = gate->src[0];
fusion_data.glu_op = ggml_get_glu_op(glu);
fusion_data.glu_limit = ggml_get_op_params_f32(glu, 3);
ggml_cuda_mul_mat_q(*cuda_ctx, src0, src1, ids, glu, &fusion_data);
fused_mul_mat_vec = true;
fused_node_count = 3;
break;
}
}
}
@@ -4557,6 +4659,13 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
ggml_cuda_concurrent_event * concurrent_event = nullptr;
bool should_launch_concurrent_events = false;
#ifdef USE_CUDA_GRAPH
ggml_cuda_graph * graph = use_cuda_graph ? cuda_ctx->cuda_graph(graph_key) : nullptr;
const int chunk_size = ggml_cuda_graph_chunk_size();
int chunk_end = chunk_size;
size_t n_chunks = 0;
#endif // USE_CUDA_GRAPH
const auto try_launch_concurrent_event = [&](const ggml_tensor * node) {
if (stream_ctx.concurrent_events.find(node) != stream_ctx.concurrent_events.end()) {
concurrent_event = &stream_ctx.concurrent_events[node];
@@ -4646,6 +4755,15 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
for (int i = 0; i < cgraph->n_nodes; i++) {
ggml_tensor * node = cgraph->nodes[i];
#ifdef USE_CUDA_GRAPH
// fused nodes are skipped together, so a cut never splits a fusion
// a concurrent region must join before its capture can end
if (use_cuda_graph && chunk_size > 0 && i >= chunk_end && !is_concurrent_event_active) {
ggml_cuda_graph_launch_chunk(cuda_ctx, graph, n_chunks++);
CUDA_CHECK(cudaStreamBeginCapture(cuda_ctx->stream(), cudaStreamCaptureModeRelaxed));
chunk_end = i + chunk_size;
}
#endif // USE_CUDA_GRAPH
if (is_concurrent_event_active) {
GGML_ASSERT(concurrent_event);
@@ -4728,14 +4846,14 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
}
#ifdef USE_CUDA_GRAPH
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
if (use_cuda_graph && cuda_graph_update_required) { // End CUDA graph capture
if (graph->graph != nullptr) {
CUDA_CHECK(cudaGraphDestroy(graph->graph));
graph->graph = nullptr;
}
ggml_cuda_graph_launch_chunk(cuda_ctx, graph, n_chunks++);
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &graph->graph));
// drop the chunks left over from a longer graph
for (size_t i = n_chunks; i < graph->instances.size(); ++i) {
CUDA_CHECK(cudaGraphExecDestroy(graph->instances[i]));
}
graph->instances.resize(n_chunks);
graph_evaluated_or_captured = true; // CUDA graph has been captured
std::lock_guard<std::mutex> lock(ggml_cuda_lock);
@@ -4747,16 +4865,11 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
}
}
if (use_cuda_graph) {
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
if (graph->instance == nullptr) { // Create executable graph from captured graph.
CUDA_CHECK(cudaGraphInstantiate(&graph->instance, graph->graph, NULL, NULL, 0));
if (use_cuda_graph && !cuda_graph_update_required) {
// the chunks were launched during capture, otherwise launch them here
for (cudaGraphExec_t instance : graph->instances) {
CUDA_CHECK(cudaGraphLaunch(instance, cuda_ctx->stream()));
}
if (cuda_graph_update_required) { // Update graph executable
ggml_cuda_graph_update_executable(cuda_ctx, graph_key);
}
// Launch graph
CUDA_CHECK(cudaGraphLaunch(graph->instance, cuda_ctx->stream()));
#else
GGML_UNUSED(graph_key);
graph_evaluated_or_captured = true;
@@ -4768,7 +4881,7 @@ static void ggml_cuda_graph_evaluate_and_capture(ggml_backend_cuda_context * cud
static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx, const void * graph_key) {
ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key);
if (graph->graph == nullptr) {
if (graph->instances.empty()) {
if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_VOLTA) {
if (!graph->disable_due_to_gpu_arch && ggml_cuda_graph_debug_enabled()) {
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__);
@@ -4822,7 +4935,7 @@ static enum ggml_status ggml_backend_cuda_graph_compute(ggml_backend_t backend,
}
} else {
use_cuda_graph = true;
cuda_graph_update_required = graph->instance == nullptr;
cuda_graph_update_required = graph->instances.empty();
}
}
}
@@ -4912,6 +5025,19 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph
}
}
for (int i = 0; i < cgraph->n_nodes; ++i) {
// Keep the inputs of the fused gate/up/GLU MMQ kernel alive until the GLU so that they cannot alias its output (under op offload the weights are buffer copies).
if (i + 2 < cgraph->n_nodes && cgraph->nodes[i + 2]->op == GGML_OP_GLU) {
ggml_tensor * glu = cgraph->nodes[i + 2];
ggml_tensor * gate = glu->src[0];
ggml_tensor * up = glu->src[1];
const bool ok = (gate == cgraph->nodes[i] && up == cgraph->nodes[i + 1]) || (gate == cgraph->nodes[i + 1] && up == cgraph->nodes[i]);
if (ok && ggml_cuda_mul_mat_q_fusion_matches(up, gate, glu, cuda_ctx->device)) {
add_alloc_deps(i, i + 2);
i += 2;
continue;
}
}
ggml_cuda_moe_weighted_reduction_match match;
if (ggml_cuda_match_moe_weighted_reduction(cgraph, i, match)) {
params->add_alloc_dep(params->user_data, const_cast<ggml_tensor *>(match.experts), match.dst);
@@ -5981,6 +6107,8 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
return true;
case GGML_OP_LIGHTNING_INDEXER:
return ggml_cuda_lightning_indexer_supported(dev_ctx->device, op);
case GGML_OP_TOP_K_MASK:
return op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_F32;
default:
return false;
+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]);
+88 -50
View File
@@ -5,7 +5,9 @@
#include "mmq.cuh"
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q1_0(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -60,10 +62,10 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
const int v0 = ggml_hip_perm(0x000001FF, 0x000001FF, q0_indices);
const int v1 = ggml_hip_perm(0x000001FF, 0x000001FF, q1_indices);
const int v2 = ggml_hip_perm(0x000001FF, 0x000001FF, q2_indices);
const int v3 = ggml_hip_perm(0x000001FF, 0x000001FF, q3_indices);
#else
// unpack crumbs into nibble indices
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
@@ -116,7 +118,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q2_0(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -162,8 +166,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
const uint32_t qx_indices = (q & 0x03) | ((q & 0x0C) << 6) | ((q & 0x30) << 12) | ((q & 0xC0) << 18);
const uint32_t qy_bits = q >> 8;
const uint32_t qy_indices = (qy_bits & 0x03) | ((qy_bits & 0x0C) << 6) | ((qy_bits & 0x30) << 12) | ((qy_bits & 0xC0) << 18);
const int qx = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qx_indices);
const int qy = __builtin_amdgcn_perm(0x020100FF, 0x020100FF, qy_indices);
const int qx = ggml_hip_perm(0x020100FF, 0x020100FF, qx_indices);
const int qy = ggml_hip_perm(0x020100FF, 0x020100FF, qy_indices);
#else
// unpack even and odd crumbs into byte values
const int qe = __byte_perm(0x020100FF, 0x020100FF, q >> 0);
@@ -205,7 +209,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_0(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -268,7 +274,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_1(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -331,7 +339,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_0(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -411,7 +421,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_1(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -488,8 +500,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q8_0(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q8_0(
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -519,7 +531,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q8_0 * bxi = (const block_q8_0 *) x + kbx0 + i*stride + kbx;
const block_q8_0 * bxi = ggml_cuda_mmq_x_row<block_q8_0, has_fusion>(x, x_gate, kbx0, i, stride) + kbx;
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
x_qs[i*sram_stride + 0 + txi] = get_int_b2(bxi[0].qs, kqsx);
@@ -542,7 +554,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q8_0 * bxi = (const block_q8_0 *) x + kbx0 + i*stride + kbxd;
const block_q8_0 * bxi = ggml_cuda_mmq_x_row<block_q8_0, has_fusion>(x, x_gate, kbx0, i, stride) + kbxd;
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
x_df[i*sram_stride + kbxd] = bxi->d;
@@ -555,7 +567,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
// ---------------------------------------------------------------------------------------------
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q2_K(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -616,7 +630,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q3_K(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -728,8 +744,8 @@ static __device__ __forceinline__ int unpack_scales_q45_K(const int * scales, co
((scales[ksc/2] >> (2 * (ksc % 2))) & 0x30303030); // upper 2 bits
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_K(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q4_K(
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -757,7 +773,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int qs0 = get_int_b4(bxi->qs, txi);
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
@@ -786,7 +802,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int * scales = (const int *) bxi->scales;
const int ksc = threadIdx.x % 2;
@@ -814,7 +830,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride;
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride);
x_dm[i] = bxi->dm;
}
@@ -827,7 +843,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q4_K * bxi = (const block_q4_K *) x + kbx0 + i*stride + (threadIdx.x % (MMQ_TILE_NE_K/8)) / (QI4_K/8);
const block_q4_K * bxi = ggml_cuda_mmq_x_row<block_q4_K, has_fusion>(x, x_gate, kbx0, i, stride) + (threadIdx.x % (MMQ_TILE_NE_K/8)) / (QI4_K/8);
const int * scales = (const int *) bxi->scales;
@@ -839,8 +855,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
#endif // defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_K(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q5_K(
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -868,7 +884,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int ky = QR5_K*txi;
const int ql = get_int_b4(bxi->qs, txi);
@@ -909,7 +925,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int * scales = (const int *) bxi->scales;
const int ksc = threadIdx.x % 2;
@@ -937,7 +953,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
x_dm[i] = bxi->dm;
}
@@ -951,7 +967,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q5_K * bxi = (const block_q5_K *) x + kbx0 + i*stride;
const block_q5_K * bxi = ggml_cuda_mmq_x_row<block_q5_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int * scales = (const int *) bxi->scales;
@@ -963,8 +979,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
#endif // defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q6_K(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_q6_K(
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -993,7 +1009,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride;
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride);
const int ql = get_int_b2(bxi->ql, txi);
const int ql0 = (ql >> 0) & 0x0F0F0F0F;
@@ -1023,7 +1039,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride;
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride);
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
x_df[i*sram_stride] = bxi->d;
@@ -1041,7 +1057,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_q6_K * bxi = (const block_q6_K *) x + kbx0 + i*stride + (threadIdx.x % (MMQ_TILE_NE_K/8)) / 4;
const block_q6_K * bxi = ggml_cuda_mmq_x_row<block_q6_K, has_fusion>(x, x_gate, kbx0, i, stride) + (threadIdx.x % (MMQ_TILE_NE_K/8)) / 4;
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
x_sc[i*sram_stride + threadIdx.x%4] = get_int_b2(bxi->scales, threadIdx.x % (MMQ_TILE_NE_K/8));
@@ -1054,7 +1070,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
// ---------------------------------------------------------------------------------------------
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq1_s(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1116,7 +1134,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_xxs(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1180,7 +1200,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_xs(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1245,7 +1267,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq2_s(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1313,7 +1337,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq3_xxs(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1377,7 +1403,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq3_s(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1445,8 +1473,8 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_xs(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
template <ggml_type type, int J, bool fallback, bool has_fusion> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_xs(
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1473,7 +1501,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_iq4_xs * bxi = (const block_iq4_xs *) x + kbx0 + i*stride;
const block_iq4_xs * bxi = ggml_cuda_mmq_x_row<block_iq4_xs, has_fusion>(x, x_gate, kbx0, i, stride);
const int aux_q4 = get_int_b4(bxi->qs, kqsx);
const int2 v = get_int_from_table_16(aux_q4, kvalues_iq4nl);
@@ -1497,7 +1525,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
i = min(i, i_max);
}
const block_iq4_xs * bxi = (const block_iq4_xs *) x + kbx0 + i*stride;
const block_iq4_xs * bxi = ggml_cuda_mmq_x_row<block_iq4_xs, has_fusion>(x, x_gate, kbx0, i, stride);
const float d = __half2float(bxi->d);
@@ -1513,7 +1541,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_iq4_nl(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1582,7 +1612,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
// ---------------------------------------------------------------------------------------------
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_mxfp4(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q8) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q8);
@@ -1649,7 +1681,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_mxfp4_fp4(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q4) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q4);
@@ -1691,7 +1725,9 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
}
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_nvfp4(
const char * __restrict__ x, int * __restrict__ x_tile, const int kb0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kb0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
@@ -1747,7 +1783,9 @@ template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __de
}
template <ggml_type type, int J, bool fallback> static __device__ __forceinline__ void ggml_cuda_mmq_load_tiles_nvfp4_nvfp4(
const char * __restrict__ x, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
const char * __restrict__ x, const char * __restrict__ x_gate, int * __restrict__ x_tile, const int kbx0, const int i_max, const int stride) {
GGML_UNUSED(x_gate);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, GGML_PREC_Q4) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, GGML_PREC_Q4);
+81 -31
View File
@@ -131,12 +131,51 @@ static ggml_prec ggml_cuda_mmq_get_prec_src1(const ggml_tensor * src0, const ggm
return GGML_PREC_Q4;
}
// Tile width in ne11 (dense) / ne12 (MoE) direction with the fewest tiles that fits into shared memory, 0 if there is none.
static int ggml_cuda_mmq_get_J_best(
const ggml_type type, const int64_t ncols, const bool fallback, const int cc, const size_t smpbo, const ggml_prec prec_src1,
int * nthreads_best = nullptr) {
int J_best = 0;
int ntiles_J_best = INT_MAX;
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(type, J, fallback, cc, prec_src1);
if (config.type == GGML_TYPE_COUNT) {
continue;
}
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
continue;
}
const int ntiles_x = (ncols + config.J - 1) / config.J;
if (ntiles_x < ntiles_J_best) {
J_best = J;
if (nthreads_best) {
*nthreads_best = config.nthreads;
}
ntiles_J_best = ntiles_x;
}
}
return J_best;
}
void ggml_cuda_mul_mat_q(
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst) {
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst,
const ggml_cuda_mm_fusion_args_host * fusion) {
GGML_ASSERT( src1->type == GGML_TYPE_F32);
GGML_ASSERT( dst->type == GGML_TYPE_F32);
GGML_ASSERT(!ids || ids->type == GGML_TYPE_I32); // Optional, used for batched GGML_MUL_MAT_ID.
// Fused gate/up/GLU: src0 is the up weight, dst the GLU output.
const ggml_tensor * gate = fusion ? fusion->gate : nullptr;
if (fusion) {
// TODO MoE support
GGML_ASSERT(gate && !ids && !fusion->x_bias && !fusion->gate_bias && !fusion->x_scale && !fusion->gate_scale);
GGML_ASSERT(gate->type == src0->type && ggml_are_same_shape(gate, src0) && ggml_are_same_stride(gate, src0));
}
GGML_TENSOR_BINARY_OP_LOCALS;
cudaStream_t stream = ctx.stream();
@@ -158,14 +197,16 @@ void ggml_cuda_mul_mat_q(
const float * src1_d = (const float *) src1->data;
float * dst_d = (float *) dst->data;
// If src0 is a temporary compute buffer, clear any potential padding.
if (ggml_backend_buffer_get_usage(src0->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
const size_t size_data = ggml_nbytes(src0);
const size_t size_alloc = ggml_backend_buffer_get_alloc_size(src0->buffer, src0);
if (size_alloc > size_data) {
GGML_ASSERT(ggml_is_contiguously_allocated(src0));
GGML_ASSERT(!src0->view_src);
CUDA_CHECK(cudaMemsetAsync((char *) src0->data + size_data, 0, size_alloc - size_data, stream));
// If src0 (or the fused gate weight) is a temporary compute buffer, clear any potential padding.
for (const ggml_tensor * t : { src0, gate }) {
if (t && ggml_backend_buffer_get_usage(t->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE) {
const size_t size_data = ggml_nbytes(t);
const size_t size_alloc = ggml_backend_buffer_get_alloc_size(t->buffer, t);
if (size_alloc > size_data) {
GGML_ASSERT(ggml_is_contiguously_allocated(t));
GGML_ASSERT(!t->view_src);
CUDA_CHECK(cudaMemsetAsync((char *) t->data + size_data, 0, size_alloc - size_data, stream));
}
}
}
@@ -180,7 +221,9 @@ void ggml_cuda_mul_mat_q(
const bool fallback = ggml_cuda_mmq_needs_fallback(ne01);
const ggml_prec prec_src1 = ggml_cuda_mmq_get_prec_src1(src0, dst, cc);
// With fusion dst is the GLU output, not a MUL_MAT; the fused types always quantize src1 to Q8_1.
// TODO support for Q4 precision if both tensors allow it
const ggml_prec prec_src1 = gate ? GGML_PREC_Q8 : ggml_cuda_mmq_get_prec_src1(src0, dst, cc);
const bool use_native_fp4 = prec_src1 == GGML_PREC_Q4;
const size_t y_block_size = use_native_fp4 ? sizeof(block_fp4_mmq) : sizeof(block_q8_1_mmq);
@@ -201,26 +244,7 @@ void ggml_cuda_mul_mat_q(
}
}
int ntiles_J_best = INT_MAX;
for (int J = 8; J <= 128 && ntiles_J_best > 1; J += 8) {
const ggml_cuda_mmq_config config = ggml_cuda_mmq_get_config(src0->type, J, fallback, cc, prec_src1);
if (config.type == GGML_TYPE_COUNT) {
continue;
}
if (mmq_get_nbytes_shared(config, cc) > smpbo) {
continue;
}
const int ntiles_x = (ncols_opt + config.J - 1) / config.J;
if (ntiles_x < ntiles_J_best) {
J_best = J;
nthreads_best = config.nthreads;
ntiles_J_best = ntiles_x;
}
}
J_best = ggml_cuda_mmq_get_J_best(src0->type, ncols_opt, fallback, cc, smpbo, prec_src1, &nthreads_best);
}
GGML_ASSERT(J_best > 0);
@@ -262,13 +286,20 @@ void ggml_cuda_mul_mat_q(
ne11 * ne10_padded * sizeof(block_q8_1) / (QK8_1 * sizeof(int));
const int64_t s13 = ne12*s12;
ggml_cuda_mm_fusion_args_device fusion_device = {};
if (gate) {
fusion_device.gate = gate->data;
fusion_device.glu_op = fusion->glu_op;
fusion_device.glu_limit = fusion->glu_limit;
}
const mmq_args args = {
src0_d, src0->type, (const int *) src1_q8_1.ptr, nullptr, nullptr, dst_d,
src0->type == GGML_TYPE_NVFP4 && use_native_fp4 ? src1_scale.ptr : nullptr,
ne00, ne01, ne1, s01, ne11, s1,
ne02, ne12, s02, s12, s2,
ne03, ne13, s03, s13, s3,
ne1, J_best};
ne1, J_best, fusion_device};
ggml_cuda_mul_mat_q_switch_type(ctx, args, stream, prec_src1);
return;
}
@@ -485,3 +516,22 @@ bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t
return (!GGML_CUDA_CC_IS_CDNA(cc)) || ne11 < MMQ_DP4A_MAX_BATCH_SIZE;
}
bool ggml_cuda_mmq_fusion_is_efficient(const ggml_type type, const int64_t nrows_x, const int64_t ncols, const int64_t nchannels, const int device) {
const int cc = ggml_cuda_info().devices[device].cc;
const int nsm = ggml_cuda_info().devices[device].nsm;
const size_t smpbo = ggml_cuda_info().devices[device].smpbo;
// Same tile width as ggml_cuda_mul_mat_q; the fused kernel has no fallback, quantizes src1 to Q8_1 and writes I/2 output rows per tile.
const bool fallback = false;
const int J_best = ggml_cuda_mmq_get_J_best(type, ncols, fallback, cc, smpbo, GGML_PREC_Q8);
if (J_best == 0) {
return false;
}
const int64_t nrows_tile = ggml_cuda_mmq_get_config(type, J_best, fallback, cc, GGML_PREC_Q8).I / 2;
const int64_t ntiles = (ncols + J_best - 1)/J_best * ((nrows_x + nrows_tile - 1)/nrows_tile) * nchannels;
// Same condition as launch_mul_mat_q uses to run the stream-k kernel with one block per tile.
const int64_t tiles_nwaves = (ntiles + nsm - 1) / nsm;
const int64_t tiles_efficiency_percent = 100 * ntiles / (nsm*tiles_nwaves);
return tiles_efficiency_percent >= 90;
}
+178 -106
View File
@@ -1,6 +1,7 @@
#pragma once
#include "common.cuh"
#include "unary.cuh"
#include <climits>
#include <cstdint>
@@ -10,10 +11,12 @@
#define MMQ_ITER_K_FP4 512
#define MMQ_NWARPS 8
typedef void (*ggml_cuda_mmq_load_tiles_t)(const char * __restrict__ x, int * x_tile, const int kbx0, const int i_max, const int stride);
typedef void (*ggml_cuda_mmq_load_tiles_t)(const char * __restrict__ x, const char * __restrict__ x_gate, int * x_tile,
const int kbx0, const int i_max, const int stride);
typedef void (*ggml_cuda_mmq_vec_dot_t)(const int * __restrict__ x, const int * __restrict__ y, float * __restrict__ sum, const int k00);
typedef void (*ggml_cuda_mmq_write_back_t)(const float * __restrict__ sum, const int32_t * __restrict__ get_rows_to_sorted,
float * __restrict__ dst, const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max);
float * __restrict__ dst, const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
const ggml_glu_op glu_op, const float glu_limit);
enum mmq_q8_1_ds_layout {
MMQ_Q8_1_DS_LAYOUT_D4,
@@ -426,15 +429,41 @@ static __host__ int ggml_cuda_mmq_get_nbytes_shared_x(const ggml_cuda_mmq_config
// ------------------------------------------------------------
// Weight types with a fused gate/up/GLU path (see ggml_cuda_mmq_get_util_funcs).
static constexpr __host__ __device__ bool ggml_cuda_mmq_fusion_supported(const ggml_type type) {
return type == GGML_TYPE_Q4_K || type == GGML_TYPE_Q5_K || type == GGML_TYPE_Q6_K || type == GGML_TYPE_Q8_0 || type == GGML_TYPE_IQ4_XS;
}
// Fused gate/up/GLU: the weight tile alternates chunks of this many up and gate rows, so rows r and r + 8 of an MMA tile (same thread) pair up.
static constexpr int GGML_CUDA_MMQ_FUSION_ROWS = 8;
// Source of weight tile row i; with has_fusion the up or gate row that the interleaved tile holds there.
template <typename block_t, bool has_fusion>
static __device__ __forceinline__ const block_t * ggml_cuda_mmq_x_row(
const char * __restrict__ x, const char * __restrict__ x_gate, const int kbx0, const int i, const int stride) {
if constexpr (has_fusion) {
constexpr int R = GGML_CUDA_MMQ_FUSION_ROWS;
const char * x_src = (i / R) % 2 == 0 ? x : x_gate;
return (const block_t *) x_src + kbx0 + ((i / (2*R))*R + i % R)*stride;
} else {
GGML_UNUSED(x_gate);
return (const block_t *) x + kbx0 + i*stride;
}
}
#include "mmq-load-tiles.cuh"
#include "mmq-vec-dot.cuh"
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __device__ __forceinline__ void ggml_cuda_mmq_write_back_dp4a(
const float * __restrict__ sum, const int32_t * __restrict__ ids_dst, float * __restrict__ dst,
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max) {
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
const ggml_glu_op glu_op, const float glu_limit) {
GGML_UNUSED_VARS(glu_op, glu_limit);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
static_assert(I % warp_size == 0, "DP4A MMQ tile height must be a multiple of the physical warp size");
const bool y_scale_used = y_scale != nullptr;
@@ -468,16 +497,19 @@ template <ggml_type type, int J, bool fallback, ggml_prec prec_src1> static __de
}
}
template<ggml_type type, int J, bool fallback, ggml_prec prec_src1>
// has_fusion: accumulators l and l + ne/2 hold the up and gate values of one output element, the GLU of the two is written.
template<ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
const float * __restrict__ sum, const int * __restrict__ ids_dst, float * __restrict__ dst,
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max) {
const float * __restrict__ y_scale, const int stride, const int i_max, const int j_max,
const ggml_glu_op glu_op, const float glu_limit) {
#if defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
typedef tile<16, 16, int, DATA_LAYOUT_J_MAJOR> tile_C;
#else
typedef tile<16, 8, int> tile_C;
#endif // defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
static_assert(!has_fusion || tile_C::I == 2*GGML_CUDA_MMQ_FUSION_ROWS, "each C tile must hold one chunk of up rows and one of gate rows");
constexpr int rows_per_warp = ggml_cuda_mmq_get_rows_per_warp(type, J, fallback, prec_src1);
constexpr int ntx = rows_per_warp/tile_C::I; // Number of x minitiles per warp.
@@ -491,20 +523,23 @@ static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
#pragma unroll
for (int n = 0; n < ntx; ++n) {
#pragma unroll
for (int l = 0; l < tile_C::ne; ++l) {
for (int l = 0; l < (has_fusion ? tile_C::ne/2 : tile_C::ne); ++l) {
const int j = j0 + (threadIdx.y % ntx) * tile_C::J + tile_C::get_j(l);
if (j > j_max) {
continue;
}
const int i = i0 + n*tile_C::I + tile_C::get_i(l);
const int i = (i0 + n*tile_C::I)/(has_fusion ? 2 : 1) + tile_C::get_i(l);
if (fallback && i > i_max) {
continue;
}
if constexpr (type == GGML_TYPE_NVFP4) {
if constexpr (has_fusion) {
const int sum_index = (j0/tile_C::J + n)*tile_C::ne + l;
dst[ids_dst[j]*stride + i] = ggml_cuda_op_glu_single(glu_op, sum[sum_index + tile_C::ne/2], sum[sum_index], glu_limit);
} else if constexpr (type == GGML_TYPE_NVFP4) {
if (y_scale_used) {
dst[ids_dst[j]*stride + i] = y_scale[j] * sum[(j0/tile_C::J + n)*tile_C::ne + l];
} else {
@@ -517,6 +552,8 @@ static __device__ __forceinline__ void ggml_cuda_mmq_write_back_mma(
}
}
}
GGML_UNUSED_VARS(y_scale_used, glu_op, glu_limit);
}
// -------------------------------------------------------------------------------------------------------------------------------------
@@ -533,8 +570,9 @@ struct ggml_cuda_mmq_util_funcs {
vdr(vdr), load_tiles(load_tiles), vec_dot(vec_dot), write_back(write_back) {}
};
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_funcs() {
static_assert(!has_fusion || ggml_cuda_mmq_fusion_supported(type), "no fused loader for this type");
if (!ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).use_mma_data_layout()) {
switch (type) {
case GGML_TYPE_Q1_0:
@@ -576,7 +614,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
case GGML_TYPE_Q8_0:
return ggml_cuda_mmq_util_funcs(
VDR_Q8_0_Q8_1_MMQ,
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback, false>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_dp4a<type, J, fallback>,
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
// ---------------------------------------------------------------------------------------------
@@ -595,19 +633,19 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
case GGML_TYPE_Q4_K:
return ggml_cuda_mmq_util_funcs(
VDR_Q4_K_Q8_1_MMQ,
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback, false>,
ggml_cuda_mmq_vec_dot_q4_K_q8_1_dp4a<type, J, fallback>,
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
case GGML_TYPE_Q5_K:
return ggml_cuda_mmq_util_funcs(
VDR_Q5_K_Q8_1_MMQ,
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback, false>,
ggml_cuda_mmq_vec_dot_q5_K_q8_1_dp4a<type, J, fallback>,
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
case GGML_TYPE_Q6_K:
return ggml_cuda_mmq_util_funcs(
VDR_Q6_K_Q8_1_MMQ,
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback, false>,
ggml_cuda_mmq_vec_dot_q6_K_q8_1_dp4a<type, J, fallback>,
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
// ---------------------------------------------------------------------------------------------
@@ -650,7 +688,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
case GGML_TYPE_IQ4_XS:
return ggml_cuda_mmq_util_funcs(
VDR_IQ4_XS_Q8_1_MMQ,
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback>,
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback, false>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_dp4a<type, J, fallback>,
ggml_cuda_mmq_write_back_dp4a<type, J, fallback, prec_src1>);
case GGML_TYPE_IQ4_NL:
@@ -687,7 +725,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
-1,
ggml_cuda_mmq_load_tiles_mxfp4_fp4<type, J, fallback>,
ggml_cuda_mmq_vec_dot_fp4_fp4_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
}
break;
case GGML_TYPE_NVFP4:
@@ -696,7 +734,7 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
-1,
ggml_cuda_mmq_load_tiles_nvfp4_nvfp4<type, J, fallback>,
ggml_cuda_mmq_vec_dot_fp4_fp4_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
}
break;
default:
@@ -712,136 +750,136 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
-1,
ggml_cuda_mmq_load_tiles_q1_0<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q2_0:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q2_0<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q4_0:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q4_0<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_DS4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q4_1:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q4_1<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q5_0:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q5_0<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q5_1:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q5_1<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q8_0:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q8_0<type, J, fallback, has_fusion>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
// ---------------------------------------------------------------------------------------------
case GGML_TYPE_Q2_K:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q2_K<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q2_K_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q3_K:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q3_K<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_Q4_K:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q4_K<type, J, fallback, has_fusion>,
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
case GGML_TYPE_Q5_K:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q5_K<type, J, fallback, has_fusion>,
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
case GGML_TYPE_Q6_K:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback>,
ggml_cuda_mmq_load_tiles_q6_K<type, J, fallback, has_fusion>,
ggml_cuda_mmq_vec_dot_q6_K_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
// ---------------------------------------------------------------------------------------------
case GGML_TYPE_IQ1_S:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq1_s<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_1_q8_1_mma<type, J, fallback>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ2_XXS:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq2_xxs<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ2_XS:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq2_xs<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ2_S:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq2_s<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ3_XXS:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq3_xxs<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ3_S:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq3_s<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_IQ4_XS:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback>,
ggml_cuda_mmq_load_tiles_iq4_xs<type, J, fallback, has_fusion>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, has_fusion>);
case GGML_TYPE_IQ4_NL:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_iq4_nl<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
// ---------------------------------------------------------------------------------------------
case GGML_TYPE_MXFP4:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_mxfp4<type, J, fallback>,
ggml_cuda_mmq_vec_dot_q8_0_q8_1_mma<type, J, fallback, MMQ_Q8_1_DS_LAYOUT_D4>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
case GGML_TYPE_NVFP4:
return ggml_cuda_mmq_util_funcs(
-1,
ggml_cuda_mmq_load_tiles_nvfp4<type, J, fallback, prec_src1>,
ggml_cuda_mmq_vec_dot_q8_0_16_q8_1_mma<type, J, fallback, prec_src1>,
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1>);
ggml_cuda_mmq_write_back_mma<type, J, fallback, prec_src1, false>);
default:
return ggml_cuda_mmq_util_funcs(1, nullptr, nullptr, nullptr);
}
@@ -849,41 +887,44 @@ static constexpr __device__ ggml_cuda_mmq_util_funcs ggml_cuda_mmq_get_util_func
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
static constexpr __device__ int ggml_cuda_mmq_get_vdr() {
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().vdr;
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, false>().vdr;
}
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
static constexpr __device__ ggml_cuda_mmq_load_tiles_t ggml_cuda_mmq_get_load_tiles() {
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().load_tiles;
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, has_fusion>().load_tiles;
}
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
static constexpr __device__ ggml_cuda_mmq_vec_dot_t ggml_cuda_mmq_get_vec_dot() {
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().vec_dot;
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, false>().vec_dot;
}
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
static constexpr __device__ ggml_cuda_mmq_write_back_t ggml_cuda_mmq_get_write_back() {
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1>().write_back;
return ggml_cuda_mmq_get_util_funcs<type, J, fallback, prec_src1, has_fusion>().write_back;
}
// ---------------------------------------------------------------------------------------------
template <ggml_type type, int J, bool fallback, bool fixup, ggml_prec prec_src1>
// has_fusion: x and x_gate are the up and gate weights.
template <ggml_type type, int J, bool fallback, bool fixup, ggml_prec prec_src1, bool has_fusion>
static __device__ __forceinline__ void mul_mat_q_process_tile(
const char * __restrict__ x, const int offset_x, const int * __restrict__ y,
const char * __restrict__ x, const char * __restrict__ x_gate, const int offset_x, const int * __restrict__ y,
const int * __restrict__ ids_dst, float * __restrict__ dst, float * __restrict__ tmp_fixup,
const float * __restrict__ y_scale,
const float * __restrict__ y_scale, const ggml_glu_op glu_op, const float glu_limit,
const int stride_row_x, const int ncols_y, const int stride_col_dst,
const int tile_x_max_i, const int tile_y_max_j, const int kb0_start, const int kb0_stop) {
static_assert(!(fixup && has_fusion), "the fused gate/up path does not split K");
static_assert(!(fallback && has_fusion), "the fused gate/up path needs nrows_x % 128 == 0");
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr int nwarps = ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1) / warp_size;
constexpr int qk = ggml_cuda_type_traits<type>::qk;
constexpr int I = ggml_cuda_mmq_get_I(type, J, fallback, prec_src1);
constexpr ggml_cuda_mmq_load_tiles_t load_tiles = ggml_cuda_mmq_get_load_tiles<type, J, fallback, prec_src1>();
constexpr ggml_cuda_mmq_load_tiles_t load_tiles = ggml_cuda_mmq_get_load_tiles<type, J, fallback, prec_src1, has_fusion>();
constexpr ggml_cuda_mmq_vec_dot_t vec_dot = ggml_cuda_mmq_get_vec_dot<type, J, fallback, prec_src1>();
constexpr ggml_cuda_mmq_write_back_t write_back = ggml_cuda_mmq_get_write_back<type, J, fallback, prec_src1>();
constexpr ggml_cuda_mmq_write_back_t write_back = ggml_cuda_mmq_get_write_back<type, J, fallback, prec_src1, has_fusion>();
extern __shared__ int data_mul_mat_q[];
int * tile_y = data_mul_mat_q + J;
@@ -906,7 +947,7 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
constexpr int sz = sizeof(block_q8_1_mmq) / sizeof(int);
for (int kb0 = kb0_start; kb0 < kb0_stop; kb0 += blocks_per_iter) {
load_tiles(x, tile_x, offset_x + kb0, tile_x_max_i, stride_row_x);
load_tiles(x, x_gate, tile_x, offset_x + kb0, tile_x_max_i, stride_row_x);
{
const int * by0 = y + ncols_y * (kb0 * qk / ne_block) * sz;
#pragma unroll
@@ -941,28 +982,34 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
}
if (fixup) {
write_back(sum, ids_dst, tmp_fixup + blockIdx.x*(J*I), y_scale, I, I, J);
write_back(sum, ids_dst, tmp_fixup + blockIdx.x*(J*I), y_scale, I, I, J, glu_op, glu_limit);
} else {
write_back(sum, ids_dst, dst, y_scale, stride_col_dst, tile_x_max_i, tile_y_max_j);
write_back(sum, ids_dst, dst, y_scale, stride_col_dst, tile_x_max_i, tile_y_max_j, glu_op, glu_limit);
}
}
// The mul_mat_q kernel implements "stream-k" work partitioning as described in https://arxiv.org/abs/2301.03598
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
__launch_bounds__(ggml_cuda_mmq_get_nthreads(type, J, fallback, prec_src1), ggml_cuda_mmq_get_occupancy(type, J, fallback, prec_src1))
static __global__ void mul_mat_q(
const char * __restrict__ x, const int * __restrict__ y, const int32_t * __restrict__ ids_dst,
const char * __restrict__ x, const char * __restrict__ x_gate, const int * __restrict__ y, const int32_t * __restrict__ ids_dst,
const int32_t * __restrict__ expert_bounds, float * __restrict__ dst, float * __restrict__ tmp_fixup,
const float * __restrict__ y_scale,
const float * __restrict__ y_scale, const ggml_glu_op glu_op, const float glu_limit,
const uint3 blocks_per_ne00, const int nrows_x, const int ncols_dst, const int stride_row_x, const int ncols_y, const int stride_col_dst,
const uint3 channel_ratio, const uint3 nchannels_y, const int stride_channel_x, const int stride_channel_y, const int stride_channel_dst,
const uint3 sample_ratio, const uint3 nsamples_y, const int stride_sample_x, const int stride_sample_y, const int stride_sample_dst,
const uint3 ntx) {
#ifdef TURING_MMA_AVAILABLE
constexpr bool fusion_available = true;
#else
constexpr bool fusion_available = false;
#endif // TURING_MMA_AVAILABLE
// Skip unused template specializations for faster compilation:
if (ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).type == GGML_TYPE_COUNT) {
if (ggml_cuda_mmq_get_config(type, J, fallback, prec_src1).type == GGML_TYPE_COUNT || (has_fusion && !fusion_available)) {
NO_DEVICE_CODE;
return;
}
@@ -990,12 +1037,16 @@ static __global__ void mul_mat_q(
}
__syncthreads();
if constexpr (!ggml_cuda_mmq_get_stream_k(type, J, fallback, prec_src1)) {
// The fused kernel needs the whole K range of a tile in one block, so it always uses tiling.
if constexpr (has_fusion || !ggml_cuda_mmq_get_stream_k(type, J, fallback, prec_src1)) {
// The fused kernel writes the GLU of I/2 up rows and I/2 gate rows per tile.
constexpr int I_dst = has_fusion ? I/2 : I;
const uint2 tmp2 = fast_div_modulo(blockIdx.z, nchannels_y);
const int wt = tmp2.x;
const int zt = tmp2.y;
const int jt = blockIdx.y;
const int it = blockIdx.x;
const int jt = blockIdx.x;
const int it = blockIdx.y;
// Defaults for regular matrix multiplication:
int col_low = 0;
@@ -1040,22 +1091,22 @@ static __global__ void mul_mat_q(
}
offset_y += (col_low + jt*J)*(sizeof(block_q8_1_mmq)/sizeof(int));
offset_dst += it*I;
offset_dst += it*I_dst;
const float * y_scale_tile = nullptr;
if constexpr (type == GGML_TYPE_NVFP4) {
offset_y_scale += col_low + jt*J;
y_scale_tile = y_scale ? y_scale + offset_y_scale : nullptr;
}
const int tile_x_max_i = nrows_x - it*I - 1;
const int tile_x_max_i = nrows_x - it*I_dst - 1;
const int tile_y_max_j = col_diff - jt*J - 1;
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I_dst*stride_row_x;
constexpr bool fixup = false;
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
stride_row_x, ncols_y, stride_col_dst,
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, has_fusion>
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
tile_x_max_i, tile_y_max_j, 0, blocks_per_ne00.z);
return;
}
@@ -1147,9 +1198,9 @@ static __global__ void mul_mat_q(
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
constexpr bool fixup = false; // All but (potentially) the last iterations write their data to dst rather than the fixup buffer.
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
stride_row_x, ncols_y, stride_col_dst,
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, false>
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
tile_x_max_i, tile_y_max_j, kb0_start, kb0_stop);
kbc += blocks_per_ne00.z;
@@ -1231,9 +1282,9 @@ static __global__ void mul_mat_q(
const int offset_x = fastdiv(wt, sample_ratio)*stride_sample_x + fastdiv(zt, channel_ratio)*stride_channel_x + it*I*stride_row_x;
constexpr bool fixup = true; // Last index writes its data to fixup buffer to avoid data races with other blocks.
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1>
(x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
stride_row_x, ncols_y, stride_col_dst,
mul_mat_q_process_tile<type, J, fallback, fixup, prec_src1, false>
(x, x_gate, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup, y_scale_tile,
glu_op, glu_limit, stride_row_x, ncols_y, stride_col_dst,
tile_x_max_i, tile_y_max_j, kb0_start, kb0_stop);
}
@@ -1383,6 +1434,7 @@ struct mmq_args {
int64_t nsamples_x; int64_t nsamples_y; int64_t stride_sample_x; int64_t stride_sample_y; int64_t stride_sample_dst;
int64_t ncols_max;
int J_best; // Tile width in ne11(dense)/ne12(MoE) direction to use for optimal performance.
ggml_cuda_mm_fusion_args_device fusion = {}; // gate != nullptr selects the fused gate/up/GLU path
};
static size_t mmq_get_nbytes_shared(const ggml_cuda_mmq_config & config, const int cc) {
@@ -1392,7 +1444,7 @@ static size_t mmq_get_nbytes_shared(const ggml_cuda_mmq_config & config, const i
return nbs_ids + nbs_x + GGML_PAD(nbs_y, config.nthreads*sizeof(int));
}
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1>
template <ggml_type type, int J, bool fallback, ggml_prec prec_src1, bool has_fusion>
static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
const int id = ggml_cuda_get_device();
const int cc = ggml_cuda_info().devices[id].cc;
@@ -1406,13 +1458,17 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
const dim3 block_dims(warp_size, nwarps, 1);
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, false, prec_src1>), nbytes_shared);
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, true, prec_src1>), nbytes_shared);
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, /*fallback =*/ false, prec_src1, has_fusion>), nbytes_shared);
if constexpr (!has_fusion) {
CUDA_SET_SHARED_MEMORY_LIMIT((mul_mat_q<type, J, /*fallback =*/ true, prec_src1, false>), nbytes_shared);
}
const int nty = (args.nrows_x + config.I - 1) / config.I;
const int I_dst = has_fusion ? config.I/2 : config.I; // Rows of dst per tile, see mul_mat_q.
const int nty = (args.nrows_x + I_dst - 1) / I_dst;
const int ntx = (args.ncols_max + config.J - 1) / config.J;
const int ntzw = args.nchannels_y * args.nsamples_y;
const dim3 block_nums_xy_tiling(nty, ntx, ntzw);
// Column tiles innermost so that the concurrently running blocks share the same tile of x and read it from L2.
const dim3 block_nums_xy_tiling(ntx, nty, ntzw);
GGML_ASSERT(args.nchannels_y % args.nchannels_x == 0);
GGML_ASSERT(args.nsamples_y % args.nsamples_x == 0);
@@ -1426,9 +1482,10 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
const uint3 channel_ratio_fd = init_fastdiv_values(channel_ratio);
const uint3 sample_ratio_fd = init_fastdiv_values(sample_ratio);
if (!config.stream_k) {
mul_mat_q<type, J, fallback, prec_src1><<<block_nums_xy_tiling, block_dims, nbytes_shared, stream>>>
(args.x, args.y, args.ids_dst, args.expert_bounds, args.dst, nullptr, args.y_scale,
if (has_fusion || !config.stream_k) {
GGML_ASSERT(nty <= 65535); // gridDim.y limit
mul_mat_q<type, J, fallback, prec_src1, has_fusion><<<block_nums_xy_tiling, block_dims, nbytes_shared, stream>>>
(args.x, (const char *) args.fusion.gate, args.y, args.ids_dst, args.expert_bounds, args.dst, nullptr, args.y_scale, args.fusion.glu_op, args.fusion.glu_limit,
blocks_per_ne00_fd, args.nrows_x, args.ncols_dst, args.stride_row_x, args.ncols_y, args.nrows_dst,
channel_ratio_fd, nchannels_y_fd, args.stride_channel_x, args.stride_channel_y, args.stride_channel_dst,
sample_ratio_fd, nsamples_y_fd, args.stride_sample_x, args.stride_sample_y, args.stride_sample_dst,
@@ -1458,8 +1515,8 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
const dim3 block_nums_fixup(block_nums_stream_k.x, config.I/warp_size, 1);
const dim3 block_dims_fixup(block_dims.x, block_dims.y/2, block_dims.z);
mul_mat_q<type, J, fallback, prec_src1><<<block_nums_stream_k, block_dims, nbytes_shared, stream>>>
(args.x, args.y, args.ids_dst, args.expert_bounds, args.dst, tmp_fixup.ptr, args.y_scale,
mul_mat_q<type, J, fallback, prec_src1, has_fusion><<<block_nums_stream_k, block_dims, nbytes_shared, stream>>>
(args.x, (const char *) args.fusion.gate, args.y, args.ids_dst, args.expert_bounds, args.dst, tmp_fixup.ptr, args.y_scale, args.fusion.glu_op, args.fusion.glu_limit,
blocks_per_ne00_fd, args.nrows_x, args.ncols_dst, args.stride_row_x, args.ncols_y, args.nrows_dst,
channel_ratio_fd, nchannels_y_fd, args.stride_channel_x, args.stride_channel_y, args.stride_channel_dst,
sample_ratio_fd, nsamples_y_fd, args.stride_sample_x, args.stride_sample_y, args.stride_sample_dst,
@@ -1476,56 +1533,56 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
ntx_fd);
}
template <ggml_type type, bool fallback, ggml_prec prec_src1>
template <ggml_type type, bool fallback, ggml_prec prec_src1, bool has_fusion>
void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
switch (args.J_best) {
case 8:
launch_mul_mat_q<type, 8, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 8, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 16:
launch_mul_mat_q<type, 16, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 16, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 24:
launch_mul_mat_q<type, 24, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 24, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 32:
launch_mul_mat_q<type, 32, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 32, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 40:
launch_mul_mat_q<type, 40, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 40, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 48:
launch_mul_mat_q<type, 48, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 48, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 56:
launch_mul_mat_q<type, 56, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 56, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 64:
launch_mul_mat_q<type, 64, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 64, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 72:
launch_mul_mat_q<type, 72, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 72, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 80:
launch_mul_mat_q<type, 80, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 80, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 88:
launch_mul_mat_q<type, 88, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 88, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 96:
launch_mul_mat_q<type, 96, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 96, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 104:
launch_mul_mat_q<type, 104, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 104, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 112:
launch_mul_mat_q<type, 112, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 112, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 120:
launch_mul_mat_q<type, 120, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 120, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
case 128:
launch_mul_mat_q<type, 128, fallback, prec_src1>(ctx, args, stream);
launch_mul_mat_q<type, 128, fallback, prec_src1, has_fusion>(ctx, args, stream);
break;
default:
fprintf(stderr, "J_best=%d\n", args.J_best);
@@ -1533,14 +1590,26 @@ void mul_mat_q_switch_J(ggml_backend_cuda_context & ctx, const mmq_args & args,
}
}
template <ggml_type type, bool fallback, ggml_prec prec_src1>
static void mul_mat_q_switch_fusion(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
if constexpr (!fallback && ggml_cuda_mmq_fusion_supported(type)) {
if (args.fusion.gate) {
mul_mat_q_switch_J<type, fallback, prec_src1, true>(ctx, args, stream);
return;
}
}
GGML_ASSERT(!args.fusion.gate);
mul_mat_q_switch_J<type, fallback, prec_src1, false>(ctx, args, stream);
}
template <ggml_type type, ggml_prec prec_src1>
void mul_mat_q_case(ggml_backend_cuda_context & ctx, const mmq_args & args, cudaStream_t stream) {
if (ggml_cuda_mmq_needs_fallback(args.nrows_x)) {
constexpr bool fallback = true;
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
mul_mat_q_switch_fusion<type, fallback, prec_src1>(ctx, args, stream);
} else {
constexpr bool fallback = false;
mul_mat_q_switch_J<type, fallback, prec_src1>(ctx, args, stream);
mul_mat_q_switch_fusion<type, fallback, prec_src1>(ctx, args, stream);
}
}
@@ -1582,6 +1651,9 @@ extern DECL_MMQ_CASE_W4A4(GGML_TYPE_NVFP4);
// -------------------------------------------------------------------------------------------------------------------------
void ggml_cuda_mul_mat_q(
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst);
ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst,
const ggml_cuda_mm_fusion_args_host * fusion = nullptr);
bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t n_experts);
bool ggml_cuda_mmq_fusion_is_efficient(ggml_type type, int64_t nrows_x, int64_t ncols, int64_t nchannels, int device);
+39 -20
View File
@@ -196,9 +196,9 @@ static __global__ void rope_neox(const T * x,
dst[idst + n_offs/2 + n_dims / 2] = ggml_cuda_cast<D>(x0 * sin_theta + x1 * cos_theta);
}
template <bool forward, bool has_ff, typename T>
template <bool forward, bool has_ff, typename T, typename D>
static __global__ void rope_multi(const T * x,
T * dst,
D * dst,
const int ne00,
const int ne01,
const int ne02,
@@ -219,6 +219,8 @@ static __global__ void rope_multi(const T * x,
const float * freq_factors,
const mrope_sections sections,
const bool is_imrope,
const int64_t * row_indices,
const int set_rows_stride,
const bool inplace) {
const int i0 = 2 * (blockDim.y * blockIdx.y + threadIdx.y);
@@ -236,12 +238,20 @@ static __global__ void rope_multi(const T * x,
const int ix = i0 / 2 + i1 * s01 + i2 * s02 + i3 * s03;
ggml_cuda_pdl_sync();
// Fusion optimization: ROPE + VIEW + SET_ROWS.
// The rope output is viewed as a 1D tensor and offset based on a row index in row_indices.
if (set_rows_stride != 0) {
idst = i1 * s1 + i0 / 2;
idst += row_indices[i2] * set_rows_stride;
}
if (i0 < n_offs || i0 >= n_offs + n_dims) {
if (inplace) {
return;
}
dst[idst + i0/2 + 0] = x[ix + i0/2 + 0];
dst[idst + i0/2 + 1] = x[ix + i0/2 + 1];
dst[idst + i0/2 + 0] = ggml_cuda_cast<D>(x[ix + i0/2 + 0]);
dst[idst + i0/2 + 1] = ggml_cuda_cast<D>(x[ix + i0/2 + 1]);
return;
}
@@ -286,8 +296,8 @@ static __global__ void rope_multi(const T * x,
const float x0 = x[ix + n_offs/2 + 0];
const float x1 = x[ix + n_offs/2 + n_dims/2];
dst[idst + n_offs/2 + 0] = x0*cos_theta - x1*sin_theta;
dst[idst + n_offs/2 + n_dims/2] = x0*sin_theta + x1*cos_theta;
dst[idst + n_offs/2 + 0] = ggml_cuda_cast<D>(x0*cos_theta - x1*sin_theta);
dst[idst + n_offs/2 + n_dims/2] = ggml_cuda_cast<D>(x0*sin_theta + x1*cos_theta);
}
template <bool forward, bool has_ff, typename T>
@@ -443,9 +453,9 @@ static void rope_neox_cuda(const T * x,
}
}
template <bool forward, typename T>
template <bool forward, typename T, typename D>
static void rope_multi_cuda(const T * x,
T * dst,
D * dst,
const int ne00,
const int ne01,
const int ne02,
@@ -467,6 +477,8 @@ static void rope_multi_cuda(const T * x,
const float * freq_factors,
const mrope_sections sections,
const bool is_imrope,
const int64_t * row_indices,
const int set_rows_stride,
const bool inplace,
cudaStream_t stream) {
GGML_ASSERT(ne00 % 2 == 0);
@@ -478,14 +490,14 @@ static void rope_multi_cuda(const T * x,
if (freq_factors == nullptr) {
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
ggml_cuda_kernel_launch(rope_multi<forward, false, T>, launch_params,
ggml_cuda_kernel_launch(rope_multi<forward, false, T, D>, launch_params,
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, row_indices, set_rows_stride, inplace);
} else {
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
ggml_cuda_kernel_launch(rope_multi<forward, true, T>, launch_params,
ggml_cuda_kernel_launch(rope_multi<forward, true, T, D>, launch_params,
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, row_indices, set_rows_stride, inplace);
}
}
@@ -648,14 +660,21 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
GGML_ABORT("fatal error");
}
} else if (is_mrope && !is_vision) {
if (src0->type == GGML_TYPE_F32) {
rope_multi_cuda<forward>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
} else if (src0->type == GGML_TYPE_F16) {
rope_multi_cuda<forward>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F32) {
rope_multi_cuda<forward, float, float>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02,
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
row_indices, set_rows_stride, inplace, stream);
} else if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F16) {
rope_multi_cuda<forward, float, half>((const float *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
row_indices, set_rows_stride, inplace, stream);
} else if (src0->type == GGML_TYPE_F16 && dst_type == GGML_TYPE_F16) {
rope_multi_cuda<forward, half, half>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
ext_factor, attn_factor, corr_dims, freq_factors, sections, is_imrope,
row_indices, set_rows_stride, inplace, stream);
} else {
GGML_ABORT("fatal error");
}
+2 -1
View File
@@ -151,9 +151,10 @@ static void ssm_conv_f32_cuda(const float * src0, const float * src1, const floa
case 3: launch_kernel(std::integral_constant<int, 3 >{}); break;
case 4: launch_kernel(std::integral_constant<int, 4 >{}); break;
case 5: launch_kernel(std::integral_constant<int, 5 >{}); break;
case 7: launch_kernel(std::integral_constant<int, 7 >{}); break;
case 9: launch_kernel(std::integral_constant<int, 9 >{}); break;
case 15: launch_kernel(std::integral_constant<int, 15>{}); break;
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 9, 15 right now.");
default: GGML_ABORT("Only support kernel sizes 3, 4, 5, 7, 9, 15 right now.");
}
}
+141
View File
@@ -0,0 +1,141 @@
#include "top-k-mask.cuh"
#include "convert.cuh"
#define CUDA_TOP_K_MASK_BLOCK_SIZE 512
// one block per row: fill the row with -inf, then copy the mask cells of the live picks and the extra cells
template <typename T>
static __global__ void top_k_mask_kernel(
const T * mask, const int32_t * top_k, const float * score, const int32_t * blocks, const int32_t * extra, T * dst,
const int64_t n_kv, const int64_t ne1, const int64_t ne2,
const int64_t m1, const int64_t m2, const int64_t m3,
const int64_t n_top, const int64_t n_pick, const int64_t n_blk, const int64_t n_extra,
const int64_t sm1, const int64_t sm2, const int64_t sm3,
const int64_t sd1, const int64_t sd2, const int64_t sd3,
const int64_t sk0, const int64_t sk1, const int64_t sk2, const int64_t sk3,
const int64_t ss0, const int64_t ss1, const int64_t ss2, const int64_t ss3,
const int64_t sb0, const int64_t sb1,
const int64_t se0, const int64_t se1, const int64_t se2, const int64_t se3,
const bool vec) {
ggml_cuda_pdl_lc();
const int64_t ir = blockIdx.x;
const int64_t i1 = ir % ne1;
const int64_t i2 = (ir / ne1) % ne2;
const int64_t i3 = ir / (ne1*ne2);
const T * mask_row = mask + (i1 % m1)*sm1 + (i2 % m2)*sm2 + (i3 % m3)*sm3;
T * dst_row = dst + i1*sd1 + i2*sd2 + i3*sd3;
const int32_t * top_k_row = top_k + i1*sk1 + i2*sk2 + i3*sk3;
ggml_cuda_pdl_sync();
const T ninf = ggml_cuda_cast<T>(-INFINITY);
if (vec) {
constexpr int nv = sizeof(int4)/sizeof(T);
T fill[nv];
#pragma unroll
for (int j = 0; j < nv; ++j) {
fill[j] = ninf;
}
const int4 fill4 = *(const int4 *) fill;
for (int64_t i = threadIdx.x; i < n_kv/nv; i += blockDim.x) {
((int4 *) dst_row)[i] = fill4;
}
} else {
for (int64_t i = threadIdx.x; i < n_kv; i += blockDim.x) {
dst_row[i] = ninf;
}
}
__syncthreads();
// one thread per pick or extra cell; cells kept twice get the same value
for (int64_t i = threadIdx.x; i < n_top + n_extra; i += blockDim.x) {
if (i >= n_top) {
const int32_t c = extra[(i - n_top)*se0 + i1*se1 + i2*se2 + i3*se3];
if (c >= 0 && c < n_kv) {
dst_row[c] = mask_row[c];
}
continue;
}
const int32_t p = top_k_row[i*sk0];
if (p < 0 || p >= n_pick) {
continue;
}
if (score && !(score[p*ss0 + i1*ss1 + i2*ss2 + i3*ss3] > -INFINITY)) {
continue;
}
if (!blocks) {
if (p < n_kv) {
dst_row[p] = mask_row[p];
}
continue;
}
for (int64_t k = 0; k < n_blk; ++k) {
const int32_t c = blocks[k*sb0 + p*sb1];
if (c >= 0 && c < n_kv) {
dst_row[c] = mask_row[c];
}
}
}
}
template <typename T>
static void top_k_mask_cuda(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
const ggml_tensor * mask = dst->src[0];
const ggml_tensor * top_k = dst->src[1];
const ggml_tensor * score = dst->src[2];
const ggml_tensor * blocks = dst->src[3];
const ggml_tensor * extra = dst->src[4];
const int64_t n_kv = mask->ne[0];
GGML_ASSERT(dst->nb[0] == sizeof(T) && mask->nb[0] == sizeof(T));
GGML_ASSERT(dst->nb[1] % sizeof(T) == 0 && mask->nb[1] % sizeof(T) == 0);
// 16-byte stores need every dst row aligned
const bool vec = (n_kv*sizeof(T)) % sizeof(int4) == 0 &&
dst->nb[1] % sizeof(int4) == 0 && dst->nb[2] % sizeof(int4) == 0 && dst->nb[3] % sizeof(int4) == 0 &&
(uintptr_t) dst->data % sizeof(int4) == 0;
const int64_t n_pick = blocks ? blocks->ne[1] : score ? score->ne[0] : n_kv;
const size_t si = sizeof(int32_t);
const size_t sf = sizeof(float);
const dim3 block_dims(CUDA_TOP_K_MASK_BLOCK_SIZE, 1, 1);
const dim3 grid_dims(ggml_nrows(dst), 1, 1);
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, ctx.stream());
ggml_cuda_kernel_launch(top_k_mask_kernel<T>, launch_params,
(const T *) mask->data, (const int32_t *) top_k->data,
score ? (const float *) score->data : nullptr,
blocks ? (const int32_t *) blocks->data : nullptr,
extra ? (const int32_t *) extra->data : nullptr,
(T *) dst->data,
n_kv, dst->ne[1], dst->ne[2],
mask->ne[1], mask->ne[2], mask->ne[3],
top_k->ne[0], n_pick, blocks ? blocks->ne[0] : 1, extra ? extra->ne[0] : 0,
mask->nb[1]/sizeof(T), mask->nb[2]/sizeof(T), mask->nb[3]/sizeof(T),
dst->nb[1]/sizeof(T), dst->nb[2]/sizeof(T), dst->nb[3]/sizeof(T),
top_k->nb[0]/si, top_k->nb[1]/si, top_k->nb[2]/si, top_k->nb[3]/si,
score ? score->nb[0]/sf : 0, score ? score->nb[1]/sf : 0, score ? score->nb[2]/sf : 0, score ? score->nb[3]/sf : 0,
blocks ? blocks->nb[0]/si : 0, blocks ? blocks->nb[1]/si : 0,
extra ? extra->nb[0]/si : 0, extra ? extra->nb[1]/si : 0, extra ? extra->nb[2]/si : 0, extra ? extra->nb[3]/si : 0,
vec);
}
void ggml_cuda_op_top_k_mask(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
switch (dst->type) {
case GGML_TYPE_F16:
top_k_mask_cuda<half>(ctx, dst);
break;
case GGML_TYPE_F32:
top_k_mask_cuda<float>(ctx, dst);
break;
default:
GGML_ABORT("unsupported type");
}
}
+3
View File
@@ -0,0 +1,3 @@
#include "common.cuh"
void ggml_cuda_op_top_k_mask(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
+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) {
+15
View File
@@ -121,3 +121,18 @@ __device__ __forceinline__ float ggml_cuda_op_swiglu_clamp_single(float gate, fl
return ggml_cuda_op_silu_single(gate) * up;
}
__device__ __forceinline__ float ggml_cuda_op_glu_single(ggml_glu_op op, float gate, float up, float limit) {
switch (op) {
case GGML_GLU_OP_SWIGLU:
return up * ggml_cuda_op_silu_single(gate);
case GGML_GLU_OP_GEGLU:
return up * ggml_cuda_op_gelu_single(gate);
case GGML_GLU_OP_SWIGLU_OAI:
return ggml_cuda_op_swiglu_oai_single(gate, up);
case GGML_GLU_OP_SWIGLU_CLAMP:
return ggml_cuda_op_swiglu_clamp_single(gate, up, limit);
default:
return up * gate;
}
}
+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)
}
+11
View File
@@ -22,6 +22,10 @@
#define HWCAP2_I8MM (1ULL << 13)
#endif
#if !defined(HWCAP2_SVEI8MM)
#define HWCAP2_SVEI8MM (1ULL << 9)
#endif
#if !defined(HWCAP_ASIMDDP)
#define HWCAP_ASIMDDP (1 << 20)
#endif
@@ -63,6 +67,10 @@
#define PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE 47
#endif
#if !defined(PF_ARM_SVE_I8MM_INSTRUCTIONS_AVAILABLE)
#define PF_ARM_SVE_I8MM_INSTRUCTIONS_AVAILABLE 57
#endif
#if !defined(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE)
#define PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE 66
#endif
@@ -87,6 +95,7 @@ typedef struct ggml_feats_arch64_runtime {
bool has_sve;
bool has_sve2;
bool has_i8mm;
bool has_svei8mm;
bool has_sme;
bool has_sme2;
int sve_cnt;
@@ -104,6 +113,7 @@ static inline ggml_feats_arch64_runtime_t ggml_feats_get_arch64_runtime(void) {
runtime_feat.has_sve = !!(hwcap & HWCAP_SVE);
runtime_feat.has_sve2 = !!(hwcap2 & HWCAP2_SVE2);
runtime_feat.has_i8mm = !!(hwcap2 & HWCAP2_I8MM);
runtime_feat.has_svei8mm = !!(hwcap2 & HWCAP2_SVEI8MM);
runtime_feat.has_sme = !!(hwcap2 & HWCAP2_SME);
runtime_feat.has_sme2 = !!(hwcap2 & HWCAP2_SME2);
@@ -153,6 +163,7 @@ static inline ggml_feats_arch64_runtime_t ggml_feats_get_arch64_runtime(void) {
runtime_feat.has_sve = IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) != 0;
runtime_feat.has_sve2 = IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) != 0;
runtime_feat.has_i8mm = IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE) != 0;
runtime_feat.has_svei8mm = IsProcessorFeaturePresent(PF_ARM_SVE_I8MM_INSTRUCTIONS_AVAILABLE) != 0;
runtime_feat.has_sme = IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE) != 0;
runtime_feat.has_sme2 = IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE) != 0;
+7
View File
@@ -51,6 +51,13 @@ endif()
set(TARGET_NAME ggml-hexagon)
ggml_add_backend_library(${TARGET_NAME}
ggml-hexagon.cpp
htp-common.h
htp-kparams.cpp
htp-kparams.h
htp-opnode.cpp
htp-opnode.h
htp-fusion.cpp
htp-fusion.h
htp-repack.cpp
htp-repack.h
htp-drv.cpp
File diff suppressed because it is too large Load Diff
+106
View File
@@ -0,0 +1,106 @@
#ifndef HTP_COMMON_H
#define HTP_COMMON_H
#include "htp-opnode.h"
#include "htp-repack.h"
struct htp_tensor_extra {
std::vector<uint8_t> shadow_buf;
size_t shadow_size { 0 };
uint32_t flags { 0 };
};
extern int opt_arch;
extern size_t opt_ndev;
extern size_t opt_nhvx;
extern int opt_nhmx;
extern size_t opt_vmem;
extern int opt_etm;
extern int opt_verbose;
extern int opt_profile;
extern bool opt_hostbuf;
extern bool opt_dma64;
extern int opt_power_mode;
extern size_t opt_mbuf_dyn;
extern size_t opt_mbuf_static;
extern size_t opt_mbuf_total;
extern int opt_mm_select;
extern int opt_fa_select;
extern int opt_fa_split;
extern int opt_gdn_select;
extern int opt_ar_select;
extern int opt_ar_scatter;
extern int opt_opbatch;
extern int opt_opqueue;
extern int opt_optrace;
extern int opt_oppoll;
extern int opt_opfusion;
extern int opt_rt_repack;
#ifndef HEX_VERBOSE
#define HEX_VERBOSE(...) \
if (opt_verbose) GGML_LOG_DEBUG(__VA_ARGS__)
#endif
static inline bool htp_tensor_is_fuseable(const struct ggml_tensor * t) {
if (!t || !t->extra) return false;
const auto * extra = (const struct htp_tensor_extra *) t->extra;
return (extra->flags & HTP_TENSOR_FUSEABLE) != 0;
}
static inline bool htp_can_rt_repack(const struct ggml_tensor * t) {
if (opt_rt_repack == 0 || !htp_is_tiled_weight_tensor(t)) {
return false;
}
if (t->buffer && ggml_backend_buft_is_host(t->buffer->buft)) {
return false;
}
return opt_rt_repack > 1 || ggml_is_quantized(t->type);
}
static inline bool htp_tensor_is_tiled(const struct ggml_tensor * t) {
const auto * extra = (const struct htp_tensor_extra *) t->extra;
if (extra && (extra->flags & HTP_TENSOR_TILED)) {
return true;
}
return htp_can_rt_repack(t);
}
static inline bool htp_needs_repack(const struct ggml_tensor * t) {
const auto * extra = (const struct htp_tensor_extra *) t->extra;
if (extra && (extra->flags & HTP_TENSOR_TILED)) {
return false;
}
return htp_can_rt_repack(t);
}
static inline bool htp_tensors_overlap(const struct ggml_tensor * a, const struct ggml_tensor * b) {
const uintptr_t a0 = (uintptr_t) a->data;
const uintptr_t b0 = (uintptr_t) b->data;
const uintptr_t a1 = a0 + ggml_nbytes(a);
const uintptr_t b1 = b0 + ggml_nbytes(b);
return a0 < b1 && b0 < a1;
}
static inline bool htp_can_row_partition(const struct ggml_tensor * t) {
if (t->ne[1] > 1 && (t->nb[1] & 127) != 0) return false;
if (t->ne[2] > 1 && (t->nb[2] & 127) != 0) return false;
if (t->ne[3] > 1 && (t->nb[3] & 127) != 0) return false;
return true;
}
struct ggml_hexagon_session;
const char * htp_session_name(const struct ggml_hexagon_session * sess);
size_t htp_session_vtcm_size(const struct ggml_hexagon_session * sess);
uint32_t htp_session_n_threads(const struct ggml_hexagon_session * sess);
uint32_t htp_session_n_hmx(const struct ggml_hexagon_session * sess);
uint32_t htp_session_mdev_count(const struct ggml_hexagon_session * sess);
htp_op_code htp_op_remap(const struct ggml_tensor * t);
#endif // HTP_COMMON_H
+649
View File
@@ -0,0 +1,649 @@
#include "htp-fusion.h"
#include "htp-kparams.h"
#include "ggml-impl.h"
#include <algorithm>
#include <cstring>
static inline bool htp_same_tensor_data(const ggml_tensor * a, const ggml_tensor * b) {
return a && b && (a == b || a->data == b->data);
}
static inline bool htp_same_shape_and_contiguity(const ggml_tensor * a, const ggml_tensor * b) {
return ggml_are_same_shape(a, b) && (ggml_is_contiguous(a) == ggml_is_contiguous(b));
}
static inline bool htp_inplace_overlap_unsafe(const ggml_tensor * dst, const ggml_tensor * in) {
return htp_tensors_overlap(dst, in) && (dst->data != in->data || !ggml_are_same_stride(dst, in));
}
static inline bool htp_inputs_overlap_unsafe(const htp_opnode & prev, const ggml_tensor * dst, const ggml_tensor * extra = nullptr) {
if (extra && htp_inplace_overlap_unsafe(dst, extra)) return true;
for (const auto * in : prev.inputs) {
if (htp_inplace_overlap_unsafe(dst, in)) return true;
}
return false;
}
static inline bool htp_match_consumer(
const htp_opnode & prev,
const htp_opnode & cur,
bool commutative,
const ggml_tensor ** other_src = nullptr
) {
if (prev.outputs.size() != 1) {
return false;
}
const ggml_tensor * prev_out = prev.dst();
if (!htp_tensor_is_fuseable(prev_out)) {
return false;
}
if (htp_same_tensor_data(cur.src0(), prev_out)) {
if (other_src) *other_src = cur.src1();
return true;
}
if (commutative && htp_same_tensor_data(cur.src1(), prev_out)) {
if (other_src) *other_src = cur.src0();
return true;
}
return false;
}
static bool is_supported_mul_mat_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
if (kparams->n_hmx) {
return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
}
return htp_tensor_is_tiled(src0) && (kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW);
}
static bool is_supported_mul_mat_id_nx_kernel(const ggml_tensor * src0, const struct htp_mm_kernel_params * kparams) {
if (kparams->n_hmx) {
return kparams->kernel_type == HTP_MM_KERNEL_HMX_2D;
}
return htp_tensor_is_tiled(src0) &&
(kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_ROW || kparams->kernel_type == HTP_MM_KERNEL_HVX_QUANT_BLOCK);
}
// --- Fusion rules ---
static bool fuse_allreduce_add(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (opt_ar_select != 2 || prev.opcode != HTP_OP_ALLREDUCE) return false;
const auto * ar_kparams = prev.kparams<htp_allreduce_kernel_params>();
const uint32_t rank = (uint32_t) ar_kparams->rank;
const uint32_t n_ranks = (uint32_t) ar_kparams->n_ranks;
const ggml_tensor * ar_local = prev.inputs[rank];
if (!htp_tensor_is_fuseable(ar_local)) return false;
const ggml_tensor * add_dst = cur.dst();
const ggml_tensor * res_tensor = nullptr;
if (htp_same_tensor_data(cur.src0(), ar_local)) {
res_tensor = cur.src1();
} else if (htp_same_tensor_data(cur.src1(), ar_local)) {
res_tensor = cur.src0();
} else {
return false;
}
if (ar_local->type != res_tensor->type) return false;
const bool is_same_shape = ggml_are_same_shape(ar_local, res_tensor);
const bool is_row_bcast = !is_same_shape && (ar_local->ne[0] == res_tensor->ne[0] &&
res_tensor->ne[1] == 1 && res_tensor->ne[2] == 1 && res_tensor->ne[3] == 1);
if (!is_same_shape && !is_row_bcast) return false;
if (is_same_shape && !ggml_are_same_stride(ar_local, res_tensor)) {
return false;
}
if (ggml_is_contiguous(ar_local) != ggml_is_contiguous(add_dst)) return false;
const bool is_shard_ok = n_ranks <= HTP_OP_MAX_OUTPUTS && add_dst->data == ar_local->data;
struct htp_allreduce_kernel_params kparams;
if (!htp_kparams_solve_allreduce(sess, add_dst, rank, n_ranks, true, is_row_bcast, is_shard_ok, &kparams)) {
HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: solver failed\n", htp_session_name(sess));
return false;
}
const bool scatter_ok = kparams.n_dsts > 1;
kparams.mode = scatter_ok ? HTP_ALLREDUCE_SHARDED_FANOUT : HTP_ALLREDUCE_FULL;
for (uint32_t r = 0; r < n_ranks; r++) {
const ggml_tensor * ar_src = prev.inputs[r];
if (!htp_tensors_overlap(add_dst, ar_src)) continue;
if (scatter_ok && r == rank) continue;
HEX_VERBOSE("ggml-hex: %s skip ALLREDUCE_ADD fusion: dst overlaps allreduce src %u\n", htp_session_name(sess), r);
return false;
}
prev.opcode = HTP_OP_ALLREDUCE_ADD;
prev.inputs.push_back(res_tensor);
if (kparams.mode != HTP_ALLREDUCE_SHARDED_FANOUT) {
prev.outputs = { add_dst };
}
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_rms_norm_mul(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_RMS_NORM) return false;
const ggml_tensor * weight = nullptr;
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &weight)) return false;
const ggml_tensor * src0 = prev.src0();
const ggml_tensor * dst = cur.dst();
if (src0->type != GGML_TYPE_F32 || src0->ne[0] != weight->ne[0] || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
if (htp_inputs_overlap_unsafe(prev, dst, weight)) return false;
const bool is_row_bcast = (weight->ne[1] == 1 && weight->ne[2] == 1 && weight->ne[3] == 1);
const bool is_same_shape = ggml_are_same_shape(src0, weight);
if (!is_row_bcast && !is_same_shape) return false;
struct htp_unary_kernel_params kparams;
if (!htp_kparams_solve_rms_norm_mul(sess, src0, weight, dst, &kparams)) {
HEX_VERBOSE("ggml-hex: %s skip RMS_NORM_MUL fusion: VTCM needed (%d) > budget (%zu)\n",
htp_session_name(sess), kparams.vtcm_size, htp_session_vtcm_size(sess));
return false;
}
prev.opcode = HTP_OP_RMS_NORM_MUL;
prev.inputs = { src0, weight };
prev.outputs = { dst };
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_rms_norm_scale(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
GGML_UNUSED(sess);
if (prev.opcode != HTP_OP_RMS_NORM && prev.opcode != HTP_OP_RMS_NORM_MUL) return false;
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
const ggml_tensor * src0 = prev.src0();
const ggml_tensor * dst = cur.dst();
if (src0->type != GGML_TYPE_F32 || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
if (htp_inputs_overlap_unsafe(prev, dst)) return false;
float scale = 0.0f;
float bias = 0.0f;
memcpy(&scale, (const float *) cur.params + 0, sizeof(float));
memcpy(&bias, (const float *) cur.params + 1, sizeof(float));
if (bias != 0.0f || scale == 0.0f) return false;
float prev_scale = 0.0f;
memcpy(&prev_scale, &prev.params[1], sizeof(float));
if (prev_scale != 0.0f) {
scale *= prev_scale;
}
memcpy(&prev.params[1], &scale, sizeof(float));
prev.outputs[0] = dst;
prev.fused.push_back(cur.node);
return true;
}
static bool fuse_mul_mat_add(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_MUL_MAT) return false;
const ggml_tensor * src2 = nullptr;
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &src2)) return false;
if (src2->type != GGML_TYPE_F32) return false;
const ggml_tensor * src0 = prev.node->src[0];
const ggml_tensor * src1 = prev.node->src[1];
const ggml_tensor * dst = cur.node;
if (!ggml_are_same_shape(prev.node, dst)) return false;
const bool prev_collapsed = htp_matmul_can_collapse(src0, src1, prev.node);
const bool cur_collapsed = htp_matmul_add_can_collapse(src0, src1, src2, dst);
if (prev_collapsed != cur_collapsed) {
return false;
}
const auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
struct htp_mm_kernel_params kparams;
if (!htp_kparams_solve_matmul_add(sess, src0, src1, src2, dst, &kparams)) {
return false;
}
const int src1_nrows = src1->ne[1] * src1->ne[2] * src1->ne[3];
if (kparams.n_hmx == 0 && src1_nrows != 1) return false;
if (kparams.n_hmx > 0 && orig_kparams->n_hmx > 0) {
if (kparams.m_chunk < orig_kparams->m_chunk ||
kparams.n_chunk < orig_kparams->n_chunk ||
kparams.n_act_threads < orig_kparams->n_act_threads) {
HEX_VERBOSE("ggml-hex: %s skip MUL_MAT_ADD fusion: HMX efficiency reduced (m %d->%d, n %d->%d, th %d->%d)\n",
htp_session_name(sess), orig_kparams->m_chunk, kparams.m_chunk,
orig_kparams->n_chunk, kparams.n_chunk,
orig_kparams->n_act_threads, kparams.n_act_threads);
return false;
}
}
prev.opcode = HTP_OP_MUL_MAT_ADD;
prev.inputs = { src0, src1, src2 };
prev.outputs = { dst };
htp_opnode_collapse(sess, prev);
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_mul_mat_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_MUL_MAT && prev.opcode != HTP_OP_MUL_MAT_NX) return false;
const uint32_t curr_n = (uint32_t) prev.outputs.size();
if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 1 >= HTP_OP_MAX_INPUTS) return false;
const ggml_tensor * w0 = prev.inputs[0];
const ggml_tensor * x_orig = prev.node->src[1];
const ggml_tensor * w_in = cur.node->src[0];
const ggml_tensor * x_in = cur.node->src[1];
const ggml_tensor * d_in = cur.node;
auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
const auto * cur_kparams = cur.kparams<htp_mm_kernel_params>();
if (x_in != x_orig || w_in->type != w0->type || w_in->ne[0] != w0->ne[0] ||
w0->ne[2] != 1 || w0->ne[3] != 1 || w_in->ne[2] != 1 || w_in->ne[3] != 1 ||
htp_tensor_is_tiled(w_in) != htp_tensor_is_tiled(w0) ||
orig_kparams->n_hmx != cur_kparams->n_hmx) {
return false;
}
const bool collapse = htp_matmul_can_collapse(w0, x_orig, prev.node) && htp_matmul_can_collapse(w_in, x_in, d_in);
if ((x_orig->ne[2] > 1 || x_orig->ne[3] > 1) && !collapse) return false;
if (prev.opcode == HTP_OP_MUL_MAT_NX) {
orig_kparams->n_weights = curr_n + 1;
} else {
struct htp_mm_kernel_params kparams;
const ggml_tensor x_collapsed = collapse ? htp_tensor_collapse_rows(x_orig) : *x_orig;
if (!htp_kparams_solve_mmnx(sess, w0, &x_collapsed, curr_n + 1, &kparams)) return false;
if (!is_supported_mul_mat_nx_kernel(w0, &kparams)) return false;
prev.set_kparams(kparams);
prev.opcode = HTP_OP_MUL_MAT_NX;
}
prev.inputs.insert(prev.inputs.begin() + curr_n, w_in);
prev.outputs.push_back(collapse ? prev.add_dummy(htp_tensor_collapse_rows(d_in)) : d_in);
prev.fused.push_back(cur.node);
return true;
}
static bool fuse_mul_mat_id_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_MUL_MAT_ID && prev.opcode != HTP_OP_MUL_MAT_ID_NX) return false;
const uint32_t curr_n = (uint32_t) prev.outputs.size();
if (curr_n >= HTP_OP_MAX_OUTPUTS || curr_n + 2 >= HTP_OP_MAX_INPUTS) return false;
const ggml_tensor * w0 = prev.inputs[0];
const ggml_tensor * x = prev.inputs[curr_n];
const ggml_tensor * ids = prev.inputs[curr_n + 1];
const ggml_tensor * w_in = cur.src0();
const ggml_tensor * x_in = cur.src1();
const ggml_tensor * ids_in = cur.node->src[2];
const ggml_tensor * d_in = cur.dst();
auto * orig_kparams = prev.kparams<htp_mm_kernel_params>();
const auto * cur_kparams = cur.kparams<htp_mm_kernel_params>();
if (x_in != x || ids_in != ids || w_in->type != w0->type ||
w_in->ne[0] != w0->ne[0] || w_in->ne[2] != w0->ne[2] ||
htp_tensor_is_tiled(w_in) != htp_tensor_is_tiled(w0) ||
orig_kparams->n_hmx != cur_kparams->n_hmx) {
return false;
}
struct htp_mm_kernel_params kparams;
if (d_in->nb[1] == prev.node->nb[1]) {
kparams = *orig_kparams;
kparams.n_weights = curr_n + 1;
} else if (!htp_kparams_solve_mmidnx(sess, w0, x, d_in, curr_n + 1, &kparams)) {
return false;
}
if (!is_supported_mul_mat_id_nx_kernel(w0, &kparams)) return false;
prev.opcode = HTP_OP_MUL_MAT_ID_NX;
prev.inputs.insert(prev.inputs.begin() + curr_n, w_in);
prev.outputs.push_back(d_in);
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_gdn_cpy(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
GGML_UNUSED(sess);
if (prev.opcode != HTP_OP_GATED_DELTA_NET || prev.outputs.size() != 1) return false;
const ggml_tensor * gdn_out = prev.dst();
const ggml_tensor * cpy_node = cur.node;
const ggml_tensor * cpy_src = cur.src0();
const ggml_tensor * cpy_dst = cur.dst();
if (gdn_out->type != GGML_TYPE_F32 || cpy_src->type != GGML_TYPE_F32 || cpy_dst->type != GGML_TYPE_F32) return false;
if ((gdn_out->flags & GGML_TENSOR_FLAG_OUTPUT) || (cpy_node->flags & GGML_TENSOR_FLAG_OUTPUT)) return false;
const ggml_tensor * v = prev.node->src[2];
if (!v) return false;
const int64_t S_v = v->ne[0];
const int64_t H = v->ne[1];
const int64_t n_tokens = v->ne[2];
const int64_t n_seqs = v->ne[3];
const int64_t K = ggml_get_op_params_i32(prev.node, 0);
const size_t tail_off = (size_t) S_v * H * n_tokens * n_seqs * sizeof(float);
const int64_t D = S_v * S_v * H;
const int64_t n_written = std::min<int64_t>(n_tokens, K);
if (cpy_src->op != GGML_OP_VIEW || !htp_same_tensor_data(cpy_src->view_src, gdn_out) ||
cpy_src->view_offs != tail_off || !ggml_is_contiguous(cpy_src)) {
return false;
}
if (cpy_dst->ne[0] != D || cpy_dst->ne[1] != n_seqs || cpy_dst->ne[2] != n_written || !ggml_is_contiguous(cpy_dst)) {
return false;
}
prev.outputs.push_back(cpy_dst);
prev.fused.push_back(cur.node);
return true;
}
static bool fuse_ssm_conv_silu(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
GGML_UNUSED(sess);
if (prev.opcode != HTP_OP_SSM_CONV) return false;
auto * kparams = prev.kparams<htp_ssm_conv_kernel_params>();
if (kparams->silu) return false;
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
const ggml_tensor * conv_dst = prev.dst();
const ggml_tensor * dst = cur.dst();
if (!ggml_are_same_layout(conv_dst, dst)) return false;
if (htp_tensors_overlap(dst, prev.src0()) || htp_tensors_overlap(dst, prev.src1())) return false;
kparams->silu = 1;
prev.outputs[0] = dst;
prev.fused.push_back(cur.node);
return true;
}
static bool fuse_repeat_get_rows(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_REPEAT) return false;
if (!htp_match_consumer(prev, cur, /*commutative=*/ false)) return false;
const ggml_tensor * repeat_out = prev.dst();
const ggml_tensor * repeat_src0 = prev.src0();
const ggml_tensor * gr_src1 = cur.src1();
const ggml_tensor * gr_dst = cur.dst();
if (repeat_src0->ne[0] != repeat_out->ne[0] || repeat_src0->ne[1] != repeat_out->ne[1]) return false;
if (repeat_src0->type != repeat_out->type) return false;
struct htp_get_rows_kernel_params kparams;
if (!htp_kparams_solve_repeat_get_rows(sess, repeat_src0, gr_src1, gr_dst, &kparams) || kparams.vtcm_size == 0) return false;
prev.opcode = HTP_OP_GET_ROWS;
prev.inputs = { repeat_src0, gr_src1 };
prev.outputs = { gr_dst };
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_unary_mul_glu(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
htp_op_code glu_op = HTP_OP_INVALID;
switch (prev.opcode) {
case HTP_OP_UNARY_SILU: glu_op = HTP_OP_GLU_SWIGLU; break;
case HTP_OP_UNARY_GELU: glu_op = HTP_OP_GLU_GEGLU; break;
case HTP_OP_UNARY_GELU_QUICK: glu_op = HTP_OP_GLU_GEGLU_QUICK; break;
case HTP_OP_UNARY_GELU_ERF: glu_op = HTP_OP_GLU_GEGLU_ERF; break;
case HTP_OP_UNARY_RELU: glu_op = HTP_OP_GLU_REGLU; break;
case HTP_OP_UNARY_SIGMOID: glu_op = HTP_OP_GLU_SIGMOID; break;
default: return false;
}
const ggml_tensor * up = nullptr;
if (!htp_match_consumer(prev, cur, /*commutative=*/ true, &up)) return false;
const ggml_tensor * gate = prev.src0();
const ggml_tensor * dst = cur.dst();
if (htp_inputs_overlap_unsafe(prev, dst, up)) return false;
prev.opcode = glu_op;
prev.inputs = { gate, up };
prev.outputs = { dst };
if (!htp_kparams_solve(sess, prev)) return false;
prev.fused.push_back(cur.node);
memset(prev.params, 0, sizeof(prev.params));
return true;
}
static bool fuse_mul_mul_scalar(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != HTP_OP_MUL || prev.inputs.size() != 2) return false;
const auto * k0 = prev.kparams<htp_binary_kernel_params>();
const auto * k1 = cur.kparams<htp_binary_kernel_params>();
if (k0->kernel_type != HTP_BINARY_KERNEL_SCALAR || k1->kernel_type != HTP_BINARY_KERNEL_SCALAR) return false;
if (k0->src1_strided || k1->src1_strided) return false;
const ggml_tensor * src2 = nullptr;
if (!htp_match_consumer(prev, cur, /*commutative=*/ false, &src2)) return false;
const ggml_tensor * src1 = prev.src1();
const ggml_tensor * dst = cur.dst();
if (!ggml_are_same_shape(src1, src2) || !htp_same_shape_and_contiguity(prev.dst(), dst)) return false;
if (htp_inputs_overlap_unsafe(prev, dst, src2)) return false;
const size_t vtcm_size = htp_session_vtcm_size(sess);
struct htp_binary_kernel_params kparams = *k0;
kparams.src2_size = kparams.src1_size;
kparams.rows_per_buffer = 0;
if (kparams.src1_size > 65536 || kparams.src1_size + kparams.src2_size > vtcm_size) return false;
struct htp_binary_vtcm_layout L;
htp_binary_vtcm_layout_build(&L, &kparams, vtcm_size);
if (L.rows_per_buffer == 0 || L.total_bytes > vtcm_size) return false;
kparams.rows_per_buffer = L.rows_per_buffer;
kparams.vtcm_size = L.total_bytes;
prev.inputs.push_back(src2);
prev.outputs[0] = dst;
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
static bool fuse_binary_nx(const ggml_hexagon_session * sess, htp_opnode & prev, const htp_opnode & cur) {
if (prev.opcode != cur.opcode) return false;
const uint32_t curr_n = (uint32_t) prev.inputs.size();
if (curr_n < 2 || curr_n >= HTP_OP_MAX_INPUTS) return false;
const auto * k0 = prev.kparams<htp_binary_kernel_params>();
const auto * k1 = cur.kparams<htp_binary_kernel_params>();
const bool k0_ok = (k0->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) ||
(k0->kernel_type == HTP_BINARY_KERNEL_CHUNKED && !k0->is_scalar);
const bool k1_ok = (k1->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) ||
(k1->kernel_type == HTP_BINARY_KERNEL_CHUNKED && !k1->is_scalar);
if (!k0_ok || !k1_ok) return false;
const bool is_comm = (cur.opcode == HTP_OP_ADD || cur.opcode == HTP_OP_MUL);
const ggml_tensor * new_src = nullptr;
if (!htp_match_consumer(prev, cur, is_comm, &new_src)) return false;
const ggml_tensor * src0 = prev.inputs[0];
const ggml_tensor * dst = cur.dst();
if (!ggml_are_same_shape(src0, prev.inputs[1]) ||
!ggml_are_same_shape(src0, new_src) ||
!htp_same_shape_and_contiguity(prev.dst(), dst)) {
return false;
}
if (htp_inputs_overlap_unsafe(prev, dst, new_src)) return false;
const size_t vtcm_size = htp_session_vtcm_size(sess);
struct htp_binary_kernel_params kparams = *k0;
struct htp_binary_vtcm_layout L;
memset(&L, 0, sizeof(L));
const uint32_t n_srcs = curr_n + 1;
if (k0->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE || k1->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE) {
if (k0->kernel_type != HTP_BINARY_KERNEL_SAME_SHAPE) {
kparams = *k1;
}
kparams.n_srcs = (uint8_t) n_srcs;
kparams.rows_per_buffer = 0;
auto check_split = [&](const ggml_tensor * t) {
if (src0->ne[2] > 1 && t->nb[2] != t->ne[1] * t->nb[1]) kparams.split_at_ne01 = 1;
if (src0->ne[3] > 1 && t->nb[3] != t->ne[2] * t->nb[2]) kparams.split_at_ne02 = 1;
};
for (const auto * in : prev.inputs) check_split(in);
check_split(new_src);
check_split(dst);
htp_binary_vtcm_layout_build(&L, &kparams, vtcm_size);
}
if (L.rows_per_buffer == 0 || L.total_bytes == 0 || L.total_bytes > vtcm_size) {
bool all_contig = ggml_is_contiguous(new_src) && ggml_is_contiguous(dst);
for (const auto * in : prev.inputs) {
all_contig = all_contig && ggml_is_contiguous(in);
}
if (!all_contig) return false;
const uint32_t total_elems = (uint32_t) ggml_nelements(src0);
const uint32_t elem_size = (uint32_t) ggml_type_size(src0->type);
const uint32_t max_threads = htp_session_n_threads(sess);
if (!htp_binary_solve_chunked(&L, &kparams, total_elems, elem_size, max_threads, n_srcs, false, vtcm_size)) {
return false;
}
}
kparams.rows_per_buffer = L.rows_per_buffer;
kparams.vtcm_size = (uint32_t) L.total_bytes;
prev.inputs.push_back(new_src);
prev.outputs[0] = dst;
prev.fused.push_back(cur.node);
prev.set_kparams(kparams);
return true;
}
bool htp_fusion_can_stack(const struct ggml_tensor * a) {
if (!a || !a->src[0] || !a->src[1]) return false;
switch (a->op) {
case GGML_OP_MUL_MAT:
if (!htp_fusion_enabled(HTP_FUSE_MUL_MAT_NX)) return false;
if (htp_is_hadamard(a)) return false;
if (a->src[0]->ne[2] != 1 || a->src[0]->ne[3] != 1) return false;
return ggml_is_quantized(a->src[0]->type);
case GGML_OP_MUL_MAT_ID:
if (!htp_fusion_enabled(HTP_FUSE_MUL_MAT_ID_NX)) return false;
return a->src[2] != nullptr && ggml_is_quantized(a->src[0]->type);
default:
return false;
}
}
bool htp_fusion_can_stack(const struct ggml_tensor * a, const struct ggml_tensor * b) {
if (!htp_fusion_can_stack(b) || a->op != b->op || a->src[1] != b->src[1]) {
return false;
}
if (a->op == GGML_OP_MUL_MAT_ID && a->src[2] != b->src[2]) {
return false;
}
const ggml_tensor * w0 = a->src[0];
const ggml_tensor * w1 = b->src[0];
return w0->type == w1->type && w0->ne[0] == w1->ne[0] && w0->ne[2] == w1->ne[2];
}
int htp_fusion_max(const struct ggml_cgraph * graph, int idx) {
const int n = graph->n_nodes;
const ggml_op op0 = graph->nodes[idx]->op;
if (op0 != GGML_OP_ADD && op0 != GGML_OP_NORM && op0 != GGML_OP_RMS_NORM) {
return 1;
}
enum ggml_op ops[HTP_OP_MAX_FUSED];
ops[0] = op0;
int f = idx + 1;
while (f < n && f < idx + HTP_OP_MAX_FUSED) {
const ggml_op op = graph->nodes[f]->op;
if (op != GGML_OP_ADD && op != GGML_OP_MUL && op != GGML_OP_SCALE &&
op != GGML_OP_NORM && op != GGML_OP_RMS_NORM) {
break;
}
ops[f - idx] = op;
f++;
}
f -= idx;
for (; f > 1; f--) {
if (ggml_can_fuse(graph, idx, ops, f)) {
break;
}
}
return f;
}
void htp_fusion_tag_graph(struct ggml_cgraph * graph) {
for (int i = 0; i < graph->n_nodes; i++) {
auto * extra = (htp_tensor_extra *) graph->nodes[i]->extra;
if (!extra) continue;
extra->flags &= ~HTP_TENSOR_FUSEABLE;
const ggml_op op = graph->nodes[i]->op;
if ((op == GGML_OP_RMS_NORM || op == GGML_OP_SCALE || op == GGML_OP_REPEAT ||
op == GGML_OP_UNARY || op == GGML_OP_ADD || op == GGML_OP_SUB ||
op == GGML_OP_MUL || op == GGML_OP_DIV || op == GGML_OP_MUL_MAT_ID ||
op == GGML_OP_SSM_CONV ||
(op == GGML_OP_MUL_MAT && !htp_is_hadamard(graph->nodes[i]))) &&
ggml_node_has_n_uses(graph, i, 1)) {
extra->flags |= HTP_TENSOR_FUSEABLE;
}
}
}
bool htp_fusion_try_fuse(const struct ggml_hexagon_session * sess, struct htp_opnode & prev, const struct htp_opnode & cur) {
if (!opt_opfusion) return false;
switch (cur.opcode) {
case HTP_OP_ADD:
return (htp_fusion_enabled(HTP_FUSE_ALLREDUCE_ADD) && fuse_allreduce_add(sess, prev, cur)) ||
(htp_fusion_enabled(HTP_FUSE_MUL_MAT_ADD) && fuse_mul_mat_add(sess, prev, cur)) ||
(htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur));
case HTP_OP_MUL:
return (htp_fusion_enabled(HTP_FUSE_RMS_NORM_MUL) && fuse_rms_norm_mul(sess, prev, cur)) ||
(htp_fusion_enabled(HTP_FUSE_UNARY_MUL_GLU) && fuse_unary_mul_glu(sess, prev, cur)) ||
(htp_fusion_enabled(HTP_FUSE_MUL_MUL_SCALAR) && fuse_mul_mul_scalar(sess, prev, cur)) ||
(htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur));
case HTP_OP_SUB:
case HTP_OP_DIV:
return htp_fusion_enabled(HTP_FUSE_BINARY_NX) && fuse_binary_nx(sess, prev, cur);
case HTP_OP_SCALE:
return htp_fusion_enabled(HTP_FUSE_RMS_NORM_SCALE) && fuse_rms_norm_scale(sess, prev, cur);
case HTP_OP_UNARY_SILU:
return htp_fusion_enabled(HTP_FUSE_SSM_CONV_SILU) && fuse_ssm_conv_silu(sess, prev, cur);
case HTP_OP_GET_ROWS:
return htp_fusion_enabled(HTP_FUSE_REPEAT_GET_ROWS) && fuse_repeat_get_rows(sess, prev, cur);
case HTP_OP_CPY:
return htp_fusion_enabled(HTP_FUSE_GDN_CPY) && fuse_gdn_cpy(sess, prev, cur);
case HTP_OP_MUL_MAT:
return htp_fusion_enabled(HTP_FUSE_MUL_MAT_NX) && fuse_mul_mat_nx(sess, prev, cur);
case HTP_OP_MUL_MAT_ID:
return htp_fusion_enabled(HTP_FUSE_MUL_MAT_ID_NX) && fuse_mul_mat_id_nx(sess, prev, cur);
default:
return false;
}
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef HTP_FUSION_H
#define HTP_FUSION_H
#include "htp-common.h"
enum htp_fusion_flags {
HTP_FUSE_ALLREDUCE_ADD = (1 << 1), // 2
HTP_FUSE_RMS_NORM_MUL = (1 << 2), // 4
HTP_FUSE_MUL_MAT_ADD = (1 << 3), // 8
HTP_FUSE_MUL_MAT_NX = (1 << 4), // 16
HTP_FUSE_MUL_MAT_ID_NX = (1 << 5), // 32
HTP_FUSE_GDN_CPY = (1 << 6), // 64
HTP_FUSE_REPEAT_GET_ROWS = (1 << 7), // 128
HTP_FUSE_UNARY_MUL_GLU = (1 << 8), // 256
HTP_FUSE_MUL_MUL_SCALAR = (1 << 9), // 512
HTP_FUSE_BINARY_NX = (1 << 10), // 1024
HTP_FUSE_RMS_NORM_SCALE = (1 << 11), // 2048
HTP_FUSE_SSM_CONV_SILU = (1 << 12), // 4096
};
static inline bool htp_fusion_enabled(int flag) {
if (opt_opfusion <= 0) return false;
if (opt_opfusion == 1) return true; // 1 enables all
return (opt_opfusion & flag) != 0;
}
bool htp_fusion_can_stack(const struct ggml_tensor * a);
bool htp_fusion_can_stack(const struct ggml_tensor * a, const struct ggml_tensor * b);
int htp_fusion_max(const struct ggml_cgraph * graph, int idx);
void htp_fusion_tag_graph(struct ggml_cgraph * graph);
bool htp_fusion_try_fuse(const struct ggml_hexagon_session * sess, struct htp_opnode & prev, const struct htp_opnode & cur);
#endif // HTP_FUSION_H

Some files were not shown because too many files have changed in this diff Show More