Compare commits

...
135 Commits
Author SHA1 Message Date
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
tkran2 d8d92973c8 vocab : add \p{M} to lookaheads in Tekken and GPT-4o pre-tokenizer regexes (#30845) 2026-10-20 10:37:46 +02:00
Georgi Gerganov 7879491cf4 tests : make MoE router selection independent of the backend noise (#30818)
* tests : make MoE router selection independent of the backend noise

The router scores are in the range [0, 1), so a random draw can leave the
last expert (or expert group) still selected and the first one dropped tied
within the noise between the backends. They then route a token to different
experts and the logits differ by 4e-3 instead of the usual 5e-7.

Beam is the only arch here with a real top-k (8 experts, 2 used) and a group
top-k (4 groups, 2 used), so it is the only one that hits this: 2 of 20 seeds
failed on an M2 Ultra, the other MoE archs select 2 of 2 and cannot tie.

Fill the selection bias with a ramp of step 2 to give every expert a score
range of its own, which turns the top-k into a function of the expert index
alone. The bias affects only the selection, the weights still come from the
unbiased probs, so the MoE output keeps its magnitude. Beam now matches the
CPU reference within 4e-7 for all of 160 seeds.

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

* tests : tell the CPU and the GPU of an Apple SoC apart in the device table

Both devices report the same description ("Apple M2 Ultra" here), so the
device column of the test table had two indistinguishable rows and a failing
row did not say whether the Metal device or the host CPU produced it.

Add the device name, which is also what -b takes, unless the description
already is the name.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-20 10:40:15 +03:00
Max Krasnyansky 8466395353 hexagon: update to snapdragon-toolchain v0.8 and enable hexagon v85 (#30854)
* hex-build: bump toolchain, enable v85

* hex-build: bump toolchain to v0.8 in the CI and build scripts

* hex-build: add a little python script for swiv needed on some iot devices

* hex-build: fix flake8 warnings

* hex-arch: update version cap to v85

* hex-inspect: flexible TOOLS_ROOT

* hex-build: update to snake_case naming

* hex-build: update leftover old toolchain refs

* hex-target: update build and run scripts to support ubuntu with adb transport
2026-10-19 23:49:59 -07:00
Michael Taylor dd002b91f5 sycl: fix compiler warnings - missing prototypes, dead code, extra semicolons (#30271) 2026-10-20 00:47:25 -04:00
aiqubits 1d88cb05f1 ui: hide the native clear button in SearchInput (#30719)
SearchInput already renders its own clear button.
Hide the browser's native search cancel button to avoid showing both controls.

Fixes #28627

Signed-off-by: aiqubits <aiqubits@hotmail.com>
2026-10-20 11:58:38 +08:00
Sam MurdochandSam 3bb63cbcbf sycl : fix get_rows on reordered quantized weights (#30430)
Co-authored-by: Sam <sam@garagellm.tail7dd858.ts.net>
2026-10-20 07:47:21 +08:00
bosh f711c8f303 mtmd : match the transformers Qwen-VL resize (#30736) 2026-10-20 01:14:40 +02:00
Mendy Berger 921257f500 ci, docs: add WASI CI and docs (#30754)
* ci, docs: add WASI CI and docs

* accept review feedback
2026-10-20 01:09:55 +02:00
Sigbjørn Skjæret 3ffb62935b ci : re-enable type/lint-check for skills (#30713) 2026-10-20 01:03:03 +02:00
7adf160c41 server: add --connect, allow p2p access to server outside LAN (#28277)
* init

* wip

* wire it up

* make it clear that it's experimental

* add props.connect_code

* nits

* fix windows build

* server: do not advertise a connect code without the tunnel

--connect-code on its own had no effect other than being served in
/props, so the Web UI showed a remote access code that no tunnel was
serving. Clear it when --connect is off, and say so once at startup.

The teardown comment claimed that terminate() closes the child stdout.
It does not: the log thread leaves fgets() only once every writer of
that pipe is gone, so a descendant of llama-connect would block the
join forever. Describe the real contract instead.

Also restore the --reasoning-preserve default in the generated docs.

* Update tools/connect/CMakeLists.txt

Co-authored-by: Adrien Gallouët <angt@huggingface.co>

* server: dial llama-connect on the bound port and a reachable host

Pass ctx_http.port so --port 0 and the GCP override work, pick the
dial host from --host (wildcards map to loopback, IPv6 is bracketed)
and refuse --connect when only unix sockets are bound.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
Co-authored-by: Adrien Gallouët <angt@huggingface.co>
2026-10-20 00:23:14 +02:00
A-Raphie ab7ed0d4b5 ui : fix attachment text preview clipped when a line is longer than the container (#30806)
In the attachment preview, SyntaxHighlightedCode's wrapper (overflow-auto) was not the element that ended up owning horizontal overflow: the inner code box kept its content clipped at the wrapper's width, so a single line longer than the preview pane was cut off with no scrollbar (scrollWidth == clientWidth while the code's own content measured tens of thousands of px).

Give the pre/code boxes their intrinsic width (width: max-content, min-width: 100%) so long-line overflow propagates to the scrolling wrapper. Verified in the running WebUI: with a 6000-char line, wrapper scrollWidth goes from 990 (clipped, unscrollable) to 50449 with a working horizontal scrollbar; short content still fills the container exactly as before (min-width: 100%).

Fixes #30796
2026-10-20 00:22:14 +02:00
Kashif RasulandSigbjørn Skjæret 4c3c5810f8 hybrid dna tokenizer fixes (#30825)
* vocab : support Carbon-14B HybridDNA tokenizer

Carbon-14B auto-tags untagged DNA runs and uses N-padded tail tokens
(tail=pad_n). Also strip whitespace inside <dna> tags and handle a stray
</dna> like the HF tokenizer.

* convert : fix special tokens and merges for older Carbon models

Carbon-500M/3B/8B don't register the Qwen added tokens and ship no
tokenizer.json, so take the token types, merges and special ids from the
base tokenizer.

* Update conversion/base.py

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

* Update src/llama-vocab.cpp

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

* Update src/llama-vocab.cpp

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

* Update src/llama-vocab.cpp

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

* Update conversion/base.py

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

* Update conversion/base.py

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

* Update conversion/base.py

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

* address review comments

* Update src/llama-vocab.cpp

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

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>
2026-10-19 23:01:40 +02:00
lhez 1b29621bac opencl: add cooperative-k dp4a gemm for q4_0, q4_k and q6_k (#30741)
* opencl: add cooperative-k dp4a GEMM for q4_0

* opencl: add cooperative-k dp4a GEMM for q4_k

* opencl: add cooperative-k dp4a GEMM for q6_k

* opencl: fix alignment
2026-10-19 12:30:39 -07:00
lhez 6fa4f6cd3e opencl: add get_rows for i32, cpy for i32 and f32 (#30756)
* opencl: support i32 GET_ROWS

* opencl: support i32 and f32 copy conversions
2026-10-19 12:22:34 -07:00
Aldehir Rojas 0ce8cd1141 jinja : implement unique filter (#30819)
* jinja : implement `unique` filter

* cont : inline key_of

* cont : fix alignment
2026-10-19 12:18:24 -05:00
Tarek Dakhran f174553e16 server : fix swa checkpoint invalidation logic (#30666)
* server : fix swa checkpoint invalidation logic

Assisted-by: Claude

* add testcase that fails with > n_past, but works with > n_prefix
2026-10-19 15:58:14 +02:00
Alessandro de Oliveira Faria (A.K.A.CABELO) 7951f601e8 vendor : update cpp-httplib to 0.62.0 (#30785) 2026-10-19 20:23:23 +08:00
Evan Huus 33d6135cbe moe-cache : replace MoE LRU cache with a 50/50 SLRU cache (#30637) 2026-10-19 13:28:50 +02:00
Georgi Gerganov 098ce526b5 metal : fix moe_reduce fusion race when dst aliases its inputs (#30795)
The fused moe_reduce kernel computes dst = sum(experts[e] * weights[e]) in a
single dispatch, reading experts and weights while writing dst. The graph
allocator computes tensor liveness on the unfused graph, where the inputs are
fully consumed by earlier nodes of the fused group, so it can legally recycle
the input memory as dst. In the fused kernel this becomes a
threadgroup-vs-threadgroup race that produces rare non-deterministic wrong
results (observed with test-llama-archs -a kolibri on the meta backend).

Reject the fusion when dst overlaps either input, same guard as the
mul_mat_add fusion. The unfused MUL + ADD path is always correct.

Assisted-by: pi:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))
2026-10-19 11:55:02 +03:00
Raul Torres f3fb2fcdbc cann : refactor #ifdef blocks to avoid unreachable code after return (#25273) 2026-10-19 11:17:54 +03:00
Aaron Teo fda00f18cd tests: add big-endian conversion tests (#30705)
* ci: add quantize to big-endian tests

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

* ci: remove incoherent data types from testing

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

* tests: add verified hashes

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

* tests: rename

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

* tests: add result table

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

* tests: swap hash and result column, add colours

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

* tests: switch to perplexity test

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

* tests: update output table

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

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-19 10:14:58 +02:00
Johannes Gäßler 4866db350d CUDA: fix warning for nonexistent extra semicolon (#30774) 2026-10-19 10:08:21 +02:00
Aaron Teo 42b70032b1 ggml-cpu: vectorize ggml_v_silu and ggml_v_expf (#30791)
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-19 09:59:49 +02:00
JoelandJohannes Gäßler 4c1004c492 cuda: cap MMQ Stream-K blocks at available work (#30553)
* cuda: cap Stream-K blocks at the number of k-blocks

Stream-K normally launches one CUDA block per SM. For small matrices,
this can exceed the total number of output-tile/k-block pairs, leaving
blocks with no work and adding launch and fixup overhead. Cap the grid
at that count to remove these excess blocks without changing the useful
rounded K ranges or numerical results.

Assisted-by: Codex

* cuda: cap Stream-K blocks at the number of k iterations

Cap the grid at the number of K_vram iterations across all output tiles, rounding up partial iterations. This removes extra CUDA blocks left by the k-block cap.

Partial iterations can change FP32 summation grouping. MUL_MAT and MUL_MAT_ID backend tests pass on RTX 5090.

* Update ggml/src/ggml-cuda/mmq.cuh

---------

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
2026-10-19 09:41:14 +02:00
fzowlandSigbjørn Skjæret 412a363c65 qwen3 : bidirectional attention and dense_2 output projection (#30607)
* qwen3 : bidirectional attention and dense_2 output projection

Assisted-by: Grok

* convert : read use_bidirectional_attention in base

Assisted-by: Grok

* Update conversion/base.py

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

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
2026-10-19 09:29:02 +03:00
Aleksander Grygier 2b57148b08 ui: Fix markdown tables and lists being cut off on phones (#30687)
* ui : keep markdown tables inside the chat column on phones

The phone rule made the table wrapper full-bleed with -1rem margins, so the
first and the last column sat under the screen edge. Keep the wrapper inside the
chat column and cap it at its width, so a wide table scrolls in place instead.

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

* test : show a table-only model answer in the chat message story

Adds a story with an answer that is mostly a table, so the table layout can be
checked at a phone width.

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

* ui : indent markdown lists by 2rem

1.5rem put the marker too close to the item text.

Assisted-by: llama-ui (ggml-org/Qwen3.8-Flash-Next:IQ4_NL)
2026-10-19 09:28:16 +03:00
ppAlexa 8f97d82da3 tools : reject truncated GGUF tensor data in split and export tools (#30497)
* The problem here in the repo is gguf-split can silently produce corrupted tensor output when the input GGUF file is truncated. The fix is to add tensor-data bounds checks and short-read checks in gguf-split.cpp and export-lora.cpp, and a regression test that checks truncated input is rejected and valid tensor data is preserved.#

* Bug : GGUF-split can silently produce corrupted tensor output when the input GGUF file is truncated. Fix : add short-read checks in gguf-split.cpp (in copy_file_to_file in split_strategy) and export-lora.cpp (in read_tensor_data of file_input) so that truncated input is rejected.
2026-10-19 09:27:46 +03:00
Georgi Gerganov 27af58168a tests : update Metal fusion baseline for beam (#30764)
* tests: add beam fusion rows to MTL baseline

Regenerate tests/fusion/MTL.csv with test-fusion --record. The newly
added beam architecture has no baseline rows, so test-fusion fails with
2 failing rows (MUL+ADD x6, RMS_NORM+MUL x25). All other arch counts are
unchanged.

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

* skills: add update-fusion-table skill

Add a skill for regenerating a per-device test-fusion baseline. It keeps
the workflow from tests/fusion/README.md and the parts that are easy to
get wrong: diff the recorded file before applying it, the distinct
meanings of a FAIL row, and never recording a partial run.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (MacBook, M5 Max, 128GB)
2026-10-19 09:11:38 +03:00
Georgi Gerganov 7db0d3d781 pi : turn .pi/gg into a system prompt addendum (#30762)
.pi/gg/SYSTEM.md repeated the full agent ruleset. Move it to .pi/gg/APPEND_SYSTEM.md and keep only the llama.cpp-specific rules, so it can be appended to a main system prompt with --append-system-prompt.

Drop the .pi/SYSTEM.md symlink setup from wc2wt.sh and pr2wt.sh. Worktrees no longer need it now that the folder holds an addendum instead of a full prompt.

Assisted-by: pi:llama.cpp/ggml-org/Qwen3.8-Flash-Next-GGUF (RTX 5090 32GB, DDR5 128GB)
2026-10-19 09:11:13 +03:00
Paul MairhoferandJohannes Gäßler 99db719bdf CUDA: fix shared-memory WAR hazard in flash_attn_ext_vec (#30573)
* CUDA: fix KQ write-after-read race in flash_attn_ext_vec

Assisted-by: ChatGPT

* CUDA: apply review suggestions for KQ sync comments

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

---------

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
2026-10-19 13:25:04 +08:00
Max Krasnyansky 31288c9c66 hexagon: FLASH_ATTN_EXT improvements for longer context and SWA models (#30731)
* hex-fa: support for masked block skipping to reduce DMA fetching

* hex-fa: finetuning the hmx->hvx crossover thresholds (boosting gemma-4)

* hex-fa: update hmx gate to account for mdev.count
2026-10-18 18:08:11 -07:00
Jhen-Jie Hong fe8841a78a hexagon: allow multistream FA (#30218) 2026-10-18 18:07:53 -07:00
Aman Gupta ff5a233767 cuda : fuse qwen4exp's SCALE -> SIGMOID -> SCALE -> hc_post chain (#30602)
Port of the Vulkan HC_POST_GATE fusion (#29520). hc_combine builds the
hc_post weights as 2*sigmoid(inject/hc); dsv4_hc_post_f32 now takes a
gated template flag and computes scale_out*sigmoid(scale_in*p) from the
raw weights, so the chain runs as one kernel instead of four.
2026-10-19 01:48:47 +08:00
Andrea Rossetti 0878f7c304 ggml: support non-contiguous SUM (CPU, CUDA, Vulkan, WebGPU) (#28851)
* ggml: support non-contiguous SUM (CPU, CUDA)

* tests: add non-contiguous SUM cases

* tests: SUM over non-contiguous views

* vulkan: support non-contiguous SUM

* webgpu: support non-contiguous SUM
2026-10-18 18:57:36 +02:00
thelittlefireman 5aeac37a35 ggml-cuda: fattn do not check oob if k_VKQ_max = (k_VKQ_max - nbatch_fa) (#30572) 2026-10-18 10:59:06 +02:00
Todd Malsbary 547208dca2 Restore ESIMD kernels on FFN path (#30239)
* Reenable ESIMD on the FFN path

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Extend ESIMD fusion on FFN path to include Q8_0

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Remove comment explicitly referencing ESIMD in test-backend-ops

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

---------

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>
2026-10-18 00:02:38 -04:00
Todor BoinovskiandMax Krasnyansky 848bbda904 hexagon: add support for hadamard MUL_MAT and GET_ROW for flat and tiled Q1_0 and Q2_0 (#30627)
* hex-hadamard: add support for hadamard ops

* hex-mm: reduce pkt counts in the q8_1 quantizer and vec_dot_q1

* hex-q1/q2: add support for flat and tiled GET_ROWS for Q1_0 and Q2_0

---------

Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
2026-10-17 19:17:33 -07:00
Todor BoinovskiandMax Krasnyansky 797de63b2f hexagon: add support for Q1_0 and Q2_0 (#30122)
* hexagon: add support for q1_0 quant

* hexagon: enable Q1_0 weights in the 2D HMX MUL_MAT path

* hex-q1: cleanup gates, generalize tiling and remove in-loop divs

* hex-tiling: document tiled formats in developer.md and update htp-tensor.h

* hex-q2: add support for Q2_0

* hex-q2: remove stale asserts and speacial cases

* hex-mm: fix MUL_MAT_NX assumption about square tiling, Q1/Q2 have non-square tiles

* hex-q1/2: minor optimizations (unrolls/un-unrolls/...)

* hex-q1: optimize Q1_0 layout to reduce unpack/vrmpy overhead

* hex-q1/q2_0: further optimize vec_dots

---------

Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
2026-10-17 13:36:38 -07:00
thelittlefireman 9d1878a435 ggml-cuda: HIP: use __builtin_amdgcn_perm for q1_0 unpacking (#29927) 2026-10-17 19:04:39 +02:00
c0fd786264 ui : Discover & Download Models from HF Hub (#29584)
* common : resolve <quant>-<sidecar> download tags and list cached sidecars

A Q4_0-mtp style tag now resolves the sidecar file when no model file matches it, so a solo draft or mmproj download actually pulls the file. Cached sidecar files list as their own entries so the state survives a restart, and removing such a tag deletes only the sidecar.

Assisted-by: pi:zai-org/GLM-5.3

* ui : optional sanitized raw HTML in markdown

Add an allowHtml prop to MarkdownContent: raw HTML found in the markdown is
rendered after DOMPurify sanitization instead of being escaped as literal
text. Default stays escaped.

Assisted-by: pi:GLM-5.3-Flash

* ui : add the discover models components

The Discover surface: the curated-catalog list with its search and
skeletons, the details pane with its readme, metadata, Hub stats and
download options, and the standalone download progress bar that replaces
the plain one. The quant download button sizes its select with a new xs
trigger, so the select gains that size and the muted box look. The store
stays incomplete when a repo fails, so the next mount retries it.

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash

* ui : put the discover view in the models manager

The manager dialog gains its second view: the Discover Models call to
action fades the table out, slides the title with its compass mark, and
fades the discover surface in; the arrow button returns to the table.

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash

# Conflicts:
#	tools/ui/src/lib/components/ui/button/button.svelte

* ui : add the Enable Discover Models setting

The flag gates browsing and downloading HuggingFace GGUF models, so it is
declared on the discover layer; the repo-tree lookup in the manager layer
above reads it from here.

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash

* ui : resume model downloads from the pause point

A stopped download keeps its partial file instead of deleting it, so
re-posting the tag resumes from disk. The first progress record now
carries the resume offset, the paused snapshot survives into the
resumed entry, and a pause settles as soon as it is requested.

Assisted-by: pi

* ui : lead the discover list with downloads

The discover sidebar lists running downloads above the suggested models,
and steps aside while a query is active. A paused download stays in the
manager table, where it is resumed or dropped.

Assisted-by: pi

* ui : make resuming a paused download work

Re-posting the tag of a paused download hit the router's "already
exists" check, because a paused download parks its entry as DOWNLOADED
until the next reload: a download entry that is not running is now
replaced and resumed. The UI keeps the paused row when the request still
fails, instead of dropping it and looking finished.

Assisted-by: pi

* ui : remember unfinished model downloads

A partial file is named after its content hash, so nothing on disk says
which model it belongs to once the router restarts. The router now keeps
the tags of the downloads it started, clears a tag when the download
finishes or is deleted, and lists what is left as still downloading
without starting anything. The UI adopts those tags as paused on every
list fetch, so a refresh or a fresh open shows an incomplete download and
can resume it from the files already on disk.

Assisted-by: pi

* ui : show a paused download's state on its chip

A paused quant chip reads as paused at rest, the way a running one shows
its spinner, and offers the resume action on hover. The selector's
download row gets a View in Discover action beside its cancel.

Assisted-by: pi

* ui : collapse the download options by default

The copyable download command starts collapsed so the option rows
lead; expanding it stays one click away.

Assisted-by: pi

* ui : keep decision models out of the hub lists

Decision models answer with calibrated probabilities instead of text,
so llama-ui cannot run them: the hub data layer drops them from the
catalog and every search, keyed by the decision-model tag or a
text-classification pipeline.

Assisted-by: pi

* ui : read download tags as quant and sidecar

A `<repo>:<quant>-<sidecar>` tag now parses into its quant and sidecar
instead of one opaque quant token, and ModelsService.draftSidecarsByRepo
collects the router listing's sidecar-only entries per repo, replacing
the inline copy in the models store.

Assisted-by: pi

* ui : cluster draft downloads under their main model

Tracked downloads group by repo: the main weights lead and the draft
sidecars ride inside their item as compact sub-lines, on the selector,
the discover list and the manager table. The main model's controls
pause, resume and cancel the whole cluster, and the manager's download
rows select into the configuration pane.

Assisted-by: pi

* ui : act on tracked downloads from the table rows

The table passes its clusters' draft sub-lines to the model rows. A
row's actions cascade pause and resume, an in-flight row offers Cancel
download while a paused one keeps Delete from disk, and the confirm
dialog's copy follows the state.

Assisted-by: pi

* ui : give downloads a configuration pane of their own

Selecting a tracked download opens the pane in a download mode: live
progress with pause, resume and cancel, and See in Discover, instead of
metadata and load actions that a model without files cannot offer.

Assisted-by: pi

* ui : make the discover details self-fetching and responsive

ModelsDiscoverDetailsContainer owns the details, tree and readme
fetches so hosts outside the discover view can render the pane, and the
details layout adapts to narrow panes with container queries instead of
viewport breakpoints.

Assisted-by: pi

* ui : download a matching draft with the main model

A main quant download now pulls the quant-matched draft of the most
preferred kind the repo ships (DFlash before MTP), fire-and-forget, so
speculative decoding is ready when the main lands. Manual draft chips
stay available; the options row also collapses to one column in narrow
panes.

Assisted-by: pi

* ui : keep discover off by default and couple it with the hub setting

Discover Models starts unchecked. Enabling it turns the Hugging Face
Hub setting on and disabling it turns it back off, so the checkbox
never lies; with discover off the hub setting stays available on its
own for the display data. Stored and reset state are corrected the same
way on load.

Assisted-by: pi

* ui : open the manage models dialog on the manager view

The dialog now opens on the manager instead of returning to the last
view, discover entries still land on the discover view while the
setting allows it, and the discover surfaces follow the setting's
state.

Assisted-by: pi

* ui : give the discover search room for its focus ring

The sticky search bar insets from the panel edges so the focus ring
stays inside the scroll container, and rows scroll behind an opaque bar
instead of through it.

Assisted-by: pi

* common: settle sidecar names on the listing and drop unfinished partials

An infix or trailing sidecar token is ambiguous: draft heads use it
(Model-MTP-BF16.gguf), so do full models with embedded heads
(Model-Q8D-MTP.gguf). Such a name is a sidecar only next to token-free
main weights of the same family, while mmproj or imatrix anywhere in the
name, or a leading token, decides alone (mmproj_f16.gguf).

A quant tag always carries its bit count, so a sidecar named after no
quant (mtp-Model-it.gguf) gets the bare tag: the listing, the plan and
remove all use it. An imatrix tag resolves into a plan slot of its own,
downloaded but never wired into the params.

remove resolves the tag the way a download does and deletes the partial
files of every planned blob, so cancelling a running download or
dropping one left unfinished by an earlier run leaves nothing on disk.
The router passes its token and releases its lock around the call, and
records the unfinished downloads with an atomic write.

llama download succeeds on a sidecar-only plan and prints its paths.

* ui : move dialog views and draft states into enums and constants

The models dialog's views become the ModelsDialogView enum, the draft
subline's states join ModelRowDownloadState, the view fade and the
copy-feedback reset move to ui constants, and the dialog's compass
marks come from DISCOVER_ICON.

Assisted-by: pi

* ui : drop the unused discover surface

The dead openDiscoverModels twin, the commented-out command divider,
the details pane's unused onClose and its header's close button, and a
redundant type cast leave; the command's copy feedback joins the shared
reset constant.

Assisted-by: pi

* ui : trim the download option comments and highlight the chat template

The comment verbosity the options panel and its chips carried shrinks
to the non-obvious why, and the chat template dialog renders through
SyntaxHighlightedCode as jinja instead of a plain pre block.

Assisted-by: pi

* common: only a .gguf file is a sidecar, and mmproj/imatrix as whole words

A non-GGUF file named like a sidecar (quantization-mmproj-Q8_0.json) is
picked as the mmproj, and a sidecar word inside a longer word of a model
name (Phi-2-OmniMatrix.i1-Q4_K_M.gguf) drops the model.

* ui: dedupe draft sidecar badges per quant

The draft sidecar badges deduped by kind and repo without the quant, so
with a draft cached at two quants only one badge survived, and a delete
of the other main quant found no badge to remove its sidecar with.
Also restores the getDownloadEntries doc lost when the clusters method
landed above it.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: adopt the incomplete download status and drop the paused-ids registry

The store inferred an unfinished download from a downloading entry it
held no local state for, which misread a download running in another
tab as paused and offered a resume the server rejects. The listing
carries the "incomplete" status now, so the adoption keys off it
directly.

The paused ids no longer need to survive in localStorage: the server
remembers them across restarts, and the progress snapshot was stale
after a reload anyway. The stale localStorage key is removed.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: quant-match the pause and cancel cascades

Pause, resume and cancel cascaded to every draft sidecar of the repo
while delete only took the quant-matched ones, so canceling a Q4_0
download also killed a Q8_0 draft that belongs to another quant's
main. Every cascade acts on the quant-matched drafts now, matching
what a delete takes with it.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: collapse the duplicated draft subline markup

The downloaded and in-flight branches repeated the identical connector,
label and badge markup; one block with a conditional tail keeps the two
states in sync.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: scrub positioned styles out of sanitized model card html

The style attribute stays allowed for the KaTeX output, which let a
third party card draw a fixed overlay over the app. The sanitize hook
drops position:fixed and position:absolute values and leaves the KaTeX
layout styles alone.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: run the discover mount fetch once and reuse the shared repoOf helper

fetch() toggles its loading flag synchronously inside the mount effect,
which re-ran the fetch on every toggle and retried failed catalog repos
endlessly; the effect now untracks the call. The local repoOf copy and
the duplicate services import give way to the shared helpers.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: narrow the spec-type map to the draft sidecars

SPEC_TYPE carried empty entries for the aux sidecars it is never
indexed with; the map and the command component now type the badge as
a draft sidecar through a draftBadgeOf helper instead of a cast.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: name the decision-model tags and markdown cache keys

The decision-model hub tag and pipeline tag move into the huggingface
constants next to the other pipeline tags, and the markdown transform
cache prefixes get names instead of bare one-letter tokens.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: read the discover setting through one truthiness check

Three places read the setting with !== false and one with Boolean();
all four read it the same way now.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* ui: compare the models dialog view against its enum

The template compared the view state against the 'manage' literal in
three spots while the script used the enum.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* nit: Remove `+` before draft sidecar badge

* server: list unfinished downloads as incomplete

A download that never finished was reported as "downloading", the same
status a live download carries, so a client could not tell a running
download from a dead one. The tags remembered in unfinished-downloads
now list under a dedicated "incomplete" status, without an instance.

Assisted-by: pi (zai-org/GLM-5.3-Flash)

* server: download a model with its sidecars as -hf does

Resolution goes back to master. POST /models takes the sidecar kinds to
pull with the model (mmproj by default) and the download child gets them
as -hf flags, so the server picks the files. /models lists real models
only, each with the sidecars it has on disk, and incomplete downloads
keep their sidecars across restarts. A stopped download keeps its
partial files for the next attempt, and DELETE purges them through the
same plan the download follows.

* ui: download sidecars as parameters of their model

The download options keep their chips, but a chip no longer posts a
sidecar tag: the store maps it to its model and the sidecar kinds to
pull, the quant chip takes its companion draft in the same download,
and draft badges come from the sidecars each cached model lists.

The server adds the sidecars a cached model lacks when it is posted
again, and DELETE removes a single sidecar of a cached model.

* ui : mirror the server's sidecar lookup in draft badges

The server resolves a sidecar by the case-sensitive `<kind>-` token in the same
directory as the weights. The UI matched the token loosely, so a badge could
offer a draft the server never pulls.

Assisted-by: DeepSeek-V4.1-Flash

* ui : post the model's sidecar kinds with its download

The store keeps one download per model: the chip's kinds ride along with the
model's own sidecars, and a download row no longer expands into one row per
sidecar. Draft badges carry no quant of their own yet, so a sidecar of a cached
model is never labelled with the model's quant.

Assisted-by: DeepSeek-V4.1-Flash

* ui : join a quant's draft into its download chip

One chip per quant, its draft badged inside it and its size covering both
files. The draft is the file the server would pick: the same directory, the
exact quant, else the closest bit depth, so a 4-bit model can pair with a
16-bit head.

Assisted-by: DeepSeek-V4.1-Flash

* ui : drop the draft download rows from the manager and selector

A download is one row per model again: the drafts ride with it, and the rows
read the sidecars on disk off the listing instead of pairing sidecar entries
with their model. Pause, resume and cancel act on the model's own download.

Assisted-by: DeepSeek-V4.1-Flash

* ui : keep the discover download list one row per model

The list leads with one row per downloading model, its sidecars as sub-lines.
A row badges only the draft kinds the server can resolve, so a badge never
outlives the file its lookup would pick.

Assisted-by: DeepSeek-V4.1-Flash

* ui : keep the discover chips readable in dark mode

The metadata chips, the chat template button and the View on Hugging Face link
carried a faint border and no surface over the dialog background, so they were
near invisible. They take the tertiary button surface now, and their icons size
with size-* so the variant's base rule leaves them alone.

Assisted-by: DeepSeek-V4.1-Flash

* ui : carry the draft through the download options

One chip per main quant, its draft badged on it and the chip's downloaded state
covering both files. The drafts sit behind an opt-in checkbox with an inline
type pick, and each row opens its own command: it pins the row's quant and only
names the draft type, so -hfd is gone.

Assisted-by: DeepSeek-V4.1-Flash

* ui : line the details skeleton up with the loaded pane

Same root padding and container context as the loaded view, so the container
queries resolve alike. The download options card pads its blocks instead of
itself, the readme gets its card, and a description line stands in for the
catalog paragraph.

Assisted-by: DeepSeek-V4.1-Flash

* common : drop the partials of a removed sidecar

Removing a sidecar alone left the .downloadInProgress files of the sidecars a
download had planned in blobs/. The removal purges them through the same plan
the download follows, and the test covers a finished draft beside an unfinished
one, whose partial goes while the model keeps its files.

Assisted-by: DeepSeek-V4.1-Flash

* server : name why a download is refused

A model that is already downloading, or that is running, answered "already
exists". It now says which: "is already downloading", or "is running, unload it
first", both before the resumable check. The router test covers the two.

Assisted-by: DeepSeek-V4.1-Flash

* ui : post a download once per click

Two quick clicks on the same chip posted twice, and the second answer was
"already exists". The store tracks the model as pending, returns early while it
is pending or in flight, and reads it as in flight before the server answers, so
the chip shows the download at once. The new client test pins the flows: one
POST per double click, the chip reading downloaded once the list reports the
model, a resume keeping its sidecars, a cancel dropping the whole download, and
a reload restoring the paused state.

Assisted-by: DeepSeek-V4.1-Flash

* ui : keep a plain download row the height of the others

The sub-lines wrapper of a download row was rendered even with nothing in it, so
its padding made the row taller than a plain one and the overlaid progress bar
sat in that empty strip. It renders only with sub-lines now, in the selector and
in the discover list, and the selector row takes the phone padding its
neighbours have.

Assisted-by: DeepSeek-V4.1-Flash

* ui : badge a repo's draft once

The args of a loaded model and the listing of a cached one report the same draft
in different words, one with the file's quant and one with the kind alone, so
the merge kept both and a repo row showed the same kind twice. The badges merge
per kind now, the args one winning as it knows the quant, and the test pins both
sources of a real repo.

Assisted-by: DeepSeek-V4.1-Flash

* server : report the sidecars of a preset or a download on its way

A model the preset also names is registered as a preset, so its drafts were
never reported and no row could badge them. A download on its way reported none
either, though the record of the kinds it pulls is right there: those win over
the plan now, the plan resolves through the preset's hf-repo, and a models dir
stays out of it.

Assisted-by: DeepSeek-V4.1-Flash

* ui : badge a download's drafts and open its model from the row

A draft still coming down badges the model id now, the way a landed one does,
while an on-disk draft keeps its checked sub-line. The row itself opens the
model, the status slot beside it pauses or resumes, and the manager resolves a
focus on a download row so its panel opens.

Assisted-by: DeepSeek-V4.1-Flash

* common : report a removed sidecar that only had a partial

Removing a sidecar alone dropped the result of the partial purge, so a sidecar
that only ever landed as a .downloadInProgress blob answered false and the UI
had no reason to refetch. The purge result is folded into the return now, and
the test covers a partial-only removal.

Assisted-by: DeepSeek-V4.1-Flash

* ui : ignore a pause while the download POST is still on its way

The chip reads as downloading before the server answers, so a quick second click
unloaded a model that had not started and left the store neither in flight nor
paused. A pause returns early while the POST is pending, before it touches the
feed or the paused marker. The client test holds the POST open and pins it.

Assisted-by: DeepSeek-V4.1-Flash

* ui : offer a missing draft as its own chip

A model already on disk without its draft had no way to add it: the quant chip
carried the draft, so it read as a download of both. The quant chip keeps to its
own state now, its joined draft shows only while it rides along or is on disk,
and a downloaded quant missing its draft gets a chip of its own - badge, size,
download - that posts the model with that kind, so the server keeps the files
already there and pulls the draft.

Assisted-by: DeepSeek-V4.1-Flash

* server : list a paused sidecar download by the files on disk

A model on disk whose sidecar download stops keeps its unfinished record,
and the listing reported the kinds that record plans as if they were on
disk: the UI checked a draft that never landed and offered no way to add it.
The planned kinds now stand in only while the download runs.

* chore: Clean up comments

* server : list sidecars only for a model that resolves from a HF repo

A preset on a local file has no HF repo, yet the listing resolved its name
as one: the plan threw on the invalid repo format and GET /models failed
for every model. The sidecars now come from the repo a preset names or a
cached model is listed under, and a local file lists without them.

* common : fix download cancel regression

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

* common : preserve model partials when removing sidecars

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

* server : one sidecar table and an unfinished downloads class

Address review from @ggerganov.

The sidecar kinds live in one table in common/download, mapping each name to
its download option and plan file; the server validates, builds its options
and lists cached sidecars through it instead of its own lists.

The unfinished downloads move into server_unfinished_downloads, a record with
its own mutex and a struct per entry, and server_models exposes them through
unfinished_downloads_get and unfinished_downloads_remove.

* common: one sidecar table, and removals that keep shared files

The resolution, the cached listing and the removal follow a single
table of sidecar kinds, so a kind is declared once with its file
keyword, whether it is a draft, its option and its plan file. The
server reads the same table for the kinds it accepts, the default
sidecars and the child flags.

Removing a tag keeps the files another cached tag still loads, such as
an mmproj named after the removed quant. A sidecar removed on its own
goes for every tag that resolves it.

An unfinished download records the exact paths of its partials when
it starts. A removal deletes those without asking the hub, and keeps a
partial another recorded download also writes. The record is taken
once the child is spawned, and the files go under the models lock, so
a download of the same tag cannot start while they are removed.

* server: drop stale partials and refuse a tag that resolves nothing

Recording a download again deletes the partials of the earlier record
that the new plan no longer writes, such as those of an older hub
revision or of a sidecar the new request leaves out.

A posted tag that resolves no file of the repo is refused before the
download starts. Removing a model follows a single path, whether the
reload erased its entry during the wait or not.

* server: fix the CI race between the router download tests

The pytest workers run in parallel on one shared cache, so a download
test could find the file another test had just fetched and see no
progress, or start from the unfinished downloads record another test
wrote. Each download test now runs on an empty cache of its own.

* server: refuse a DELETE of an unknown name and decide downloads in load()

A DELETE of a name that is neither a model nor a recorded download
went to common_download_remove, and without a tag it wiped the whole
repo cache, including the files of a loaded quant or of a download on
its way. Such a name is now not found, as it was before.

A POST that downloads over an existing entry checked its state outside
the lock, so a load of the same model in between got its instance
killed. The checks now run in load(), under the lock, right before the
spawn. The fake load time of the tests applies to model loads only,
since a download worker loads none.

* server: add incomplete_sidecars to the /models API

A sidecar added to a cached model and then paused kept its record, but
the listing showed only the complete model, so a fresh UI lost the
pause. The model entry now carries incomplete_sidecars, the kinds its
unfinished download has not written yet; a POST with the kind resumes
one and a DELETE with it drops its partial.

* common : drop a kept partial when a resume gets 416

Now that partials survive between attempts, an old or complete
partial makes every retry fail with the same 416.

Delete it so the next attempt can start over from zero.

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

* common: drop a kept partial when a resume gets 200

A server that ignores the range answers 200 to a resume, and the kept
partial then fails every attempt the same way a 416 does. Drop it as
well, so the next attempt starts over without a range.

* server: refuse a download that shares a partial with one on its way

Two downloads of quants that share a file, the mmproj or an mtp picked
by the closest bits, appended to the same partial and renamed a file
with interleaved bytes. A download whose plan writes a partial that a
running download also writes is now refused until that one is done.

* common : check the saved etag before resuming a partial download

Before resuming, compare the etag saved with the partial to the one
the server now serves. If it is missing or different, start over,
so a resumed download never mixes bytes from two versions of the file.

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

---------

Signed-off-by: Adrien Gallouët <angt@huggingface.co>
Co-authored-by: Pascal <admin@serveurperso.com>
Co-authored-by: Adrien Gallouët <angt@huggingface.co>
2026-10-17 17:20:51 +02:00
Aman Guptaandthelittlefireman 2744e48d1e CUDA: faster MMVQ for low-bit types (#30517)
* CUDA: faster MMVQ for low-bit types

* ggml-cuda: hip: small nwarps GCN fix on Q3_K(ncols=2,3), Q6_K(ncols=4)

---------

Co-authored-by: thelittlefireman <thelittlefireman3@gmail.com>
2026-10-17 15:35:30 +02:00
Pascal 10b8b75534 tests: give the random model generator a per-tensor filler table (#30674)
* tests: give the random model generator a per-tensor filler table

Muse Spark makes test-llama-context fail on state_load, cp_h and cp_d on about
six percent of the random seeds, with an NMSE of 1.0e-05 to 1.4e-05 against a
threshold of 1.0e-05. The sliding window is what exposes it: a restored state
keeps the window alone, so the attention of an SWA layer reduces over a
different cell layout and the continuation is not bit exact. Every SWA model
carries that, at 1e-08. Muse Spark carried it at 1e-05.

The generator is what turns 1e-08 into 1e-05. A norm preserving residual gate
holds alpha^2 + beta^2 == 1 and the conversion resolves both weights from one
raw weight, while the generator draws every tensor on its own. The pair then
scaled the stream and the branch independently, the stream lost an arbitrary
factor at each layer, and the model became an amplifier. The weights the test
ran on were not a Muse Spark.

Per-tensor rules lived inside the write path, where a flag picked the values of
a tensor inside each of the two branches that store them, so a new rule meant a
new flag threaded through both. The file asks to be rid of that in a TODO of its
own. Give each rule a filler, pick one from a table keyed on a substring of the
tensor name, and keep a single write path behind it. Mamba moves into the table
and Muse Spark joins it: the gate filler draws the raw weight once per block and
resolves alpha and beta the way the conversion does.

Over a thousand seeds on a CPU build the worst case drops from 1.36e-05 to
2.48e-06 and the median from 4.10e-06 to 5.48e-07, with no seed above the
threshold. The other 131 models come out identical to the bit, since no other
architecture carries these tensors. ctest is green on a CPU build and on a CUDA
build.

* tests: draw every tensor filler from fill_normal

The fillers repeated the generator and the distribution each time. The
normal draw now takes its key and the other fillers transform its
values, so the generator lives in one place. The generated models are
identical to the bit.
2026-10-17 16:07:55 +03:00
Adrien Gallouët 9d1e03616e cuda : fix -Wtautological-compare in fattn vec dispatch (#30519)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
2026-10-17 14:36:17 +02:00
Sigbjørn Skjæret f2f667b02d cuda : add grouped experts top-k fusion (#29181)
* fuse grouped experts

* add tests

* hip fix

* add to graph_optimize

* apply suggestion from am17an

* hip fix

* fix hip fix

* fix fix fix

* gate on hip version instead
2026-10-17 13:55:33 +02:00
Xue ZhenliangandSigbjørn Skjæret 62318cd57d model : add Kolibri-1 (Aleph-Alpha) support (#30673)
* model : add Kolibri-1 (Aleph-Alpha) support

Kolibri-1 is a bilingual German/English MoE reasoning model with 78B
parameters and 3.46B active per token: 50 layers, 384 routed experts plus
one shared expert.

Attention interleaves four sliding layers at a 513 window with one full
layer. The sliding layers rotate with NeoX RoPE, the full layers are NoPE.
GQA runs 48 query heads against 4 KV heads at head_dim 128 with a
QK-RMSNorm, and every block carries four RMSNorms, so the attention and the
MLP output are normalized before they rejoin the residual stream. Routing
picks the experts from logits + expert_bias and weights them with the
sigmoid of the unbiased logits, without renorm.

The converter reuses AfmoeModel for the per-expert checkpoint layout and
the expert selection bias, and writes the shared expert and the per layer
sliding window pattern. The released FP8 checkpoint needs no special code,
it goes through the generic weight_scale_inv path. Three tensor names are
added to the mapping (post_attn_norm, post_ffn_norm,
moe.router.expert_bias) and the 128k vocabulary registers the qwen2
pre-tokenizer by hash. The arch is registered in test-llama-archs, with GQA
and the sliding window pattern.

Assisted-by: DeepSeek Harness (DeepSeek V4.1 Flash)

* Update src/models/kolibri1.cpp

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

* Add fusion records for kolibri1

* Update src/models/kolibri1.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-17 13:00:17 +02:00
Sandro Hanea 83f9389419 ci : added ABI reports as github artifact (#30619) 2026-10-17 11:41:53 +02:00
Gabe Goodhart 1268edac57 model : Granite switch decision 256 options (#30693)
* feat(convert): Attempt to come up with 256 single-token, semantic option markers

Each switch model needs to identify a collection of single-token option
markers that can be mapped to their respective logits for the output
probability distribution. The simple upper/lower/numbers set only yields 66
unique markers, but the model can support up to 255. The challenge is that
which combinations of characters result in single-token markers is model-
specific, so we do a discovery exercise at conversion time by looking
through the specific model's vocab for single-token sequences of 1+ plain
letter strings (eg `A`, `AA`, `BB`, etc).

Branch: GraniteSwitchDecisionFullOptions
AI-usage: none
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>

* docs: Note granite-switch with 256 option markers

Branch: GraniteSwitchDecisionFullOptions
AI-usage: none
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>

* style: Remove extra newline

Branch: GraniteSwitchDecisionFullOptions
AI-usage: none
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>

* feat(convert): Use the computed labels in the template so c++ side is source of truth

Branch: GraniteSwitchDecisionFullOptions
AI-usage: full (Claude + Sonnet 5.5)
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>

* feat: Use LEV case for GRANITE_SWITCH w/ computed labels

Branch: GraniteSwitchDecisionFullOptions
AI-usage: full (Claude + Sonnet 5.5)
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>

* docs: Consolidate g-s into 255 bucket

Branch: GraniteSwitchDecisionFullOptions
AI-usage: full (Claude + Sonnet 5.5)
Signed-off-by: Gabe Goodhart <ghart@us.ibm.com>
2026-10-17 09:14:32 +02:00
Marcus f74e2fa6a3 sycl: fix prompt-processing regression after Q4_0/Q8_0 weight reorder (#30374) 2026-10-16 22:03:41 -04:00
Xuan-Son Nguyen ea0c4c2c85 skills: add slice-model (#30677)
* skills: add slice-model

* exclude from py check

* add to agents.md
2026-10-17 02:11:56 +02:00
Zack Li 6420cf5dd5 ci: update device matrix for Snapdragon jobs by removing SM8750 (#30620)
* ci: update device matrix for Snapdragon jobs by removing SM8750

* ci: increase timeout for QDC device tests and update job timeout parameters
2026-10-16 16:25:05 -07:00
wes 27c7a769da hexagon: vectorize softplus with HVX (#30435)
softplus_f32 and tile_softplus_f32 ran a scalar logf(1.0f + expf(x)) loop.
Replace both with a shared hvx_softplus_f32_aa() helper built from the
backend's existing hvx_vec_exp_f32 / hvx_vec_log_f32 primitives, so no new
numerical approximation is introduced.

hvx_vec_exp_f32 clamps its input to (-88, 88) before exponentiating, so a
naive log(1 + exp(x)) composition would saturate to ~88.72 for large x
instead of returning x. The Q6_V_vmux_QVV select keeps the scalar
reference's x > 20 guard (ggml_compute_softplus_f32 in ggml-impl.h),
which is what makes the vector path match the CPU result.

Assisted-by: Claude
2026-10-16 15:20:43 -07:00
Shawn GuandLi He 2f2763db6e opencl: Add MxFP4 MoE bin kernel for Adreno X1 devices (#30691)
* opencl: add bin kernel `gemm_moe_mxfp4_q8_1_dp4a_ila_ldck` for X1

* opencl: fix whitespace

* opencl: undo unnecessary change

* opencl: enable dp4a clc for X1E

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
2026-10-16 15:15:04 -07:00
464 changed files with 41482 additions and 18154 deletions
+2
View File
@@ -5,6 +5,8 @@ exclude =
# Do not traverse examples and tools
examples,
tools,
# Do not traverse .venv directories
.venv*,
# Do not include package initializers
__init__.py,
# No need to traverse our git directory
@@ -35,7 +35,7 @@ jobs:
android-ndk-snapdragon:
runs-on: ubuntu-24.04 # previously ubuntu-latest
container:
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.7'
image: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
defaults:
run:
shell: bash
@@ -69,7 +69,7 @@ jobs:
linux-iot-snapdragon:
runs-on: ubuntu-24.04 # previously ubuntu-latest
container:
image: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.7'
image: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
defaults:
run:
shell: bash
@@ -104,11 +104,11 @@ jobs:
name: Test on QDC Device (${{ matrix.device }})
needs: [android-ndk-snapdragon, linux-iot-snapdragon]
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
device: [SM8750, SM8850, QCS9075M]
device: [SM8850, QCS9075M]
permissions:
actions: read
@@ -155,7 +155,7 @@ jobs:
--pkg-dir pkg-snapdragon/llama.cpp \
--model-url "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_0.gguf" \
--device ${{ matrix.device }} \
${{ startsWith(matrix.device, 'QCS') && '--retries 2 --retry-delay 300' || '' }}
${{ startsWith(matrix.device, 'QCS') && '--job-timeout 5400 --retries 2 --retry-delay 300' || '' }}
env:
QDC_API_KEY: ${{ secrets.QDC_API_KEY }}
+10 -1
View File
@@ -20,7 +20,11 @@ on:
paths: [
'.github/workflows/build-ibm.yml',
'ggml/src/ggml-cpu/**',
'ggml/src/ggml-zdnn/**'
'ggml/src/ggml-zdnn/**',
'ggml/src/ggml-quants.c',
'src/llama-quant.cpp',
'gguf-py/gguf/scripts/gguf_convert_endian.py',
'tests/test-quantize-be.sh'
]
cache-mode: none
@@ -105,6 +109,11 @@ jobs:
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
- name: Test quantize (s390x)
id: quantize_test_s390x
run: |
./tests/test-quantize-be.sh ./build/bin/llama-quantize ./build/bin/llama-perplexity 2>&1
ubuntu-26-zdnn-s390x:
name: ubuntu-26-zdnn-s390x
runs-on: ubuntu-24.04-s390x
+142
View File
@@ -0,0 +1,142 @@
name: CI (wasi)
on:
workflow_dispatch: # allows manual triggering
push:
branches:
- master
paths: [
'.github/workflows/build-wasi.yml',
'**/CMakeLists.txt',
'**/.cmake',
'**/*.h',
'**/*.hpp',
'**/*.c',
'**/*.cpp',
'**/*.wgsl',
'**/*.tmpl',
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
]
pull_request:
types: [opened, synchronize, reopened]
paths: [
'.github/workflows/build-wasi.yml',
'**/CMakeLists.txt',
'**/.cmake',
'**/*.h',
'**/*.hpp',
'**/*.c',
'**/*.cpp',
'**/*.wgsl',
'**/*.tmpl',
'ggml/src/ggml-webgpu/wgsl-shaders/embed_wgsl.py'
]
cache-mode: none
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
env:
WASI_SDK_VERSION: 34
DAWN_VERSION: v20260908.214631
DAWN_COMMIT: 94c3c9cc0d5fb2e85aebb370fa8d37b71aa34655
WASI_WEBGPU_HEADERS_COMMIT: 917765edd7cb8f8cf95bda1217514c78f71b582b
jobs:
ubuntu-webgpu:
runs-on: ubuntu-24.04-arm
steps:
- name: Clone
id: checkout
uses: actions/checkout@v6
- name: ccache
uses: ggml-org/ccache-action@v1.2.24
with:
restore: false
save: false
- name: Install wasi-sdk
uses: bytecodealliance/setup-wasi-sdk-action@77d09f3df1e8da344d8403cf8631af8977938b4c # v1.0.0
with:
version: ${{ env.WASI_SDK_VERSION }}
add-to-path: false
- name: wasi:webgpu Dependency
run: |
mkdir -p wasi-webgpu/dawn-headers
curl -fL "https://github.com/google/dawn/releases/download/${DAWN_VERSION}/dawn-headers-${DAWN_COMMIT}.tar.gz" |
tar -xz -C wasi-webgpu/dawn-headers --strip-components=1
git init -q wasi-webgpu/wasi-webgpu-headers
cd wasi-webgpu/wasi-webgpu-headers
git fetch -q --depth 1 https://github.com/wasi-gfx/wasi-webgpu-headers.git ${WASI_WEBGPU_HEADERS_COMMIT}
git checkout -q FETCH_HEAD
git submodule update -q --init --depth 1
cd ../..
# ggml-webgpu does find_package(Dawn) and links dawn::webgpu_dawn. There is no Dawn on WASI,
# so this target instead compiles wasi-webgpu-headers (webgpu.h over wasi:webgpu imports)
# and exposes Dawn's webgpu_cpp.h on top of it.
cat > wasi-webgpu/DawnConfig.cmake << 'EOF'
set(_wasi_webgpu "${CMAKE_CURRENT_LIST_DIR}/wasi-webgpu-headers")
add_library(dawn::webgpu_dawn INTERFACE IMPORTED)
set_target_properties(dawn::webgpu_dawn PROPERTIES
INTERFACE_SOURCES "${_wasi_webgpu}/webgpu.c;${_wasi_webgpu}/async_futures.c;${_wasi_webgpu}/imports.c;${_wasi_webgpu}/imports_component_type.o"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/dawn-headers/include/webgpu_upstream;${_wasi_webgpu}")
EOF
- name: ccache-buckets-restore
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CI }}
with:
key: webgpu-ubuntu-24.04-arm-wasi
folder: llama.cpp
hf_bucket: ggml-org/cache
# llama-common does not build for WASI, so the executables are linked by hand
- name: Build
id: cmake_build
run: |
WASI_FLAGS="-msimd128 -D_WASI_EMULATED_SIGNAL -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
cmake -B build-wasi \
-G "Ninja" \
-DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk-p3.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS="${WASI_FLAGS}" \
-DCMAKE_CXX_FLAGS="${WASI_FLAGS}" \
-DCMAKE_HAVE_LIBC_PTHREAD=1 \
-DBUILD_SHARED_LIBS=OFF \
-DGGML_NATIVE=OFF \
-DGGML_OPENMP=OFF \
-DGGML_WEBGPU=ON \
-DDawn_DIR=$PWD/wasi-webgpu \
-DLLAMA_BUILD_COMMON=OFF
time cmake --build build-wasi --config Release --target llama -j $(nproc)
LIBS="build-wasi/ggml/src/libggml.a build-wasi/ggml/src/ggml-webgpu/libggml-webgpu.a build-wasi/ggml/src/libggml-cpu.a build-wasi/ggml/src/libggml-base.a"
LDFLAGS="-lunwind -ldl -Wl,-z,stack-size=8388608"
CXX="${WASI_SDK_PATH}/bin/clang++ --target=wasm32-wasip3 -O3 -std=c++17 ${WASI_FLAGS} -I include -I ggml/include"
$CXX examples/simple/simple.cpp build-wasi/src/libllama.a $LIBS $LDFLAGS -o build-wasi/llama-simple.wasm
$CXX tests/test-backend-ops.cpp $LIBS $LDFLAGS -o build-wasi/test-backend-ops.wasm
- name: ccache-buckets-save
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: ./.github/actions/ccache-buckets
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_CACHE_OUTPUT }}
with:
key: webgpu-ubuntu-24.04-arm-wasi
folder: llama.cpp
evict-old-files: 1d
hf_bucket: ggml-org/cache
save: true
+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
+9
View File
@@ -61,8 +61,17 @@ jobs:
RELEASE_BRANCH: ${{ github.ref_name }}
SKIP_APIABI_CHECK: ${{ github.event.inputs.skip_apiabi_check }}
APIABI_COMPARE_TAG: ${{ github.event.inputs.apiabi_compare_tag }}
APIABI_REPORT_DIR: ${{ runner.temp }}/apiabi-reports
REQUIRE_DOCKER: ${{ github.event.inputs.require_docker }}
- name: Upload API/ABI reports
if: ${{ always() && github.event.inputs.skip_apiabi_check != 'true' }}
uses: actions/upload-artifact@v7
with:
name: apiabi-reports
path: ${{ runner.temp }}/apiabi-reports
if-no-files-found: ignore
- name: Create release tag
if: ${{ github.event.inputs.dry_run == 'false' }}
run: |
+4 -3
View File
@@ -35,7 +35,7 @@ permissions:
env:
GH_TOKEN: ${{ github.token }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON"
CMAKE_ARGS: "-DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DLLAMA_CONNECT=ON"
# note: run this workflow one at a time for better cache reuse
concurrency:
@@ -547,7 +547,7 @@ jobs:
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: ubuntu-latest
container: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.7'
container: 'ghcr.io/snapdragon-toolchain/arm64-android:v0.8'
defaults:
run:
@@ -600,7 +600,7 @@ jobs:
if: ${{ needs.check-release.outputs.should_release == 'true' }}
runs-on: ubuntu-latest
container: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.7'
container: 'ghcr.io/snapdragon-toolchain/arm64-linux:v0.8'
defaults:
run:
@@ -1478,6 +1478,7 @@ jobs:
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DGGML_NATIVE=OFF \
-DLLAMA_CONNECT=ON \
-DGGML_SYCL_F16=${{ matrix.fp16 }}
time cmake --build build --config Release -j $(nproc)
+6
View File
@@ -0,0 +1,6 @@
Project-specific instructions:
## Coding
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
-44
View File
@@ -1,44 +0,0 @@
You are a coding agent. Here are some very important rules that you must follow:
General:
- Be very precise and concise when writing code, comments, explanations, etc.
- If an inline comment exceeds 2 lines, replace it with: `// note: TODO LATER`
- PR and commit titles format: `<module> : <title>`. Lookup recents for examples
- Don't try to build or run the code unless you are explicitly asked to do so
- Use the `gh` CLI tool when querying PRs, issues, or other GitHub resources
- When a model name is needed, first try to get it from the `PI_MODEL_NAME` env var before asking the user
- When a hardware description is needed, first try to get it from the `PI_HARDWARE` env var before asking the user
- Never read the `AGENTS.md` file
Coding:
- Never write inline comments in the code unless the user tells you to
- When in doubt, always refer to the CONTRIBUTING.md file of the project
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
- When referencing issues or PRs in comments, use the format:
- C/C++ code: `// ref: <url>`
- Other (CMake, etc.): `# ref: <url>`
Pull requests (PRs):
- New branch names are prefixed with "gg/"
- Before opening a pull request, ask the user to confirm the description
- Don't explicitly wrap lines in the PR description (each paragraph and bullet is a single line)
- When creating a pull request, look for the repository's PR template and follow it
- For the AI usage disclosure section, write "YES", then these 4 bullets:
- Harness: `pi:llama.cpp`
- Hardware: `<hardware>`
- Model: `<model>`, linked to its Hugging Face page - the link text is the plain model name
- Stats: `<stats>`
- Run `~/.pi/stats.sh "$PI_SESSION_FILE"` to get `<stats>` and write it as
`◷ <time> · ↑ <input tokens> · ↓ <output tokens> · ≡ <messages> · ⌨ <user messages>`,
e.g. `◷ 12.3m · ↑ 120k · ↓ 45k · ≡ 87 · ⌨ 6`
- Write these symbols as literal unicode in the PR description, never `:shortcodes:`, and keep
commit trailers ASCII
- If `PI_MODEL_NAME` or `PI_HARDWARE` is not set, ask the user and use their answer for `<model>` or `<hardware>`
- Always create the pull requests in draft mode
- Never reply to review comments or post comments on issues/PRs without explicit permission from the user
Commits:
- On every commit that you make, include an `Assisted-by: pi:llama.cpp/<model> (<hardware>)` tag
- Do not explicitly set the git author in commits - rely on the default git config
- Always use `--no-gpg-sign` when committing
- Never `git push` without explicit confirmation from the user
+3
View File
@@ -246,3 +246,6 @@ Chat template and parser:
- [PEG parser](docs/development/parsing.md) - alternative to regex that llama.cpp uses to parse model's output
- [Auto parser](docs/autoparser.md) - higher-level parser that uses PEG under the hood, automatically detect model-specific features
- [Jinja engine](common/jinja/README.md)
Testing:
- [Slice model](skills/slice-model/SKILL.md) - build a small slice (few layers/experts) of a big HF model without downloading the full weights, to test conversion and inference
+1
View File
@@ -136,6 +136,7 @@ option(LLAMA_BUILD_SERVER "llama: build server example"
option(LLAMA_BUILD_APP "llama: build the unified binary" ${LLAMA_STANDALONE})
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" OFF)
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available" ON)
option(LLAMA_CONNECT "llama: include the llama-connect tool (downloads a prebuilt binary)" OFF)
option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT})
option(LLAMA_TESTS_INSTALL "llama: install tests" ON)
+8 -4
View File
@@ -447,10 +447,14 @@ function gg_run_qwen3_0_6b {
(time ./bin/llama-imatrix --model ${model_f16} -f ${wiki_test} -ngl 99 -c 1024 -b 512 --chunks 2 ) 2>&1 | tee -a $OUT/${ci}-imatrix.log
(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
# TODO: these tests are extremely slow on WebGPU - re-enable when fixed
# https://github.com/ggml-org/llama.cpp/actions/runs/39639985732
if [ -z ${GG_BUILD_WEBGPU} ]; then
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa off --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 10 -c 1024 -fa on --no-op-offload) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa off ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
(time ./bin/test-llama-context --model ${model_q4_0} -ngl 99 -c 1024 -fa on ) 2>&1 | tee -a $OUT/${ci}-test-llama-context.log
fi
function check_ppl {
qnt="$1"
+1 -1
View File
@@ -2,7 +2,7 @@ include("ggml/cmake/common.cmake")
function(llama_add_compile_flags)
if (LLAMA_FATAL_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM")
list(APPEND C_FLAGS -Werror)
list(APPEND CXX_FLAGS -Werror)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
+20
View File
@@ -3494,6 +3494,26 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.ui = value;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_UI"));
add_opt(common_arg(
{"--connect"},
string_format("(experimental) open a peer-to-peer tunnel for server using llama-connect (default: %s)", params.server_connect ? "enabled" : "disabled"),
[](common_params & params) {
params.server_connect = true;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CONNECT"));
add_opt(common_arg(
{"--connect-code"}, "CODE",
"manually specify a 40-character code for --connect (default: generate a new one for each run)",
[](common_params & params, const std::string & value) {
std::string val = value;
string_replace_all(val, "-", "");
string_replace_all(val, " ", "");
if (val.size() != 40) {
throw std::invalid_argument(string_format("error: invalid connect code '%s', must be 40 characters\n", value.c_str()));
}
params.server_connect_code = val;
}
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CONNECT_CODE"));
add_opt(common_arg(
{"--embedding", "--embeddings"},
string_format("restrict to only support embedding use case; use only with dedicated embedding models (default: %s)", params.embedding ? "enabled" : "disabled"),
+4
View File
@@ -1175,6 +1175,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 +1235,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;
}
+6
View File
@@ -642,6 +642,10 @@ struct common_params {
int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
bool cache_ram_explicit = false; // set by --cache-ram, decision models disable the cache otherwise
// llama-connect params
bool server_connect = false;
std::string server_connect_code = "";
std::string public_path = ""; // NOLINT
std::string api_prefix = ""; // NOLINT
std::string chat_template = ""; // NOLINT
@@ -971,6 +975,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
};
@@ -980,6 +985,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);
+177 -59
View File
@@ -39,6 +39,9 @@
// downloader
//
// the suffix of the file a download writes until it completes
static const std::string PARTIAL_SUFFIX = ".downloadInProgress";
static void write_etag(const std::string & path, const std::string & etag) {
const std::string etag_path = path + ".etag";
fs_write_atomic(std::filesystem::u8path(etag_path), etag);
@@ -186,6 +189,13 @@ static bool common_pull_file(httplib::Client & cli,
[&](const httplib::Response &response) {
if (p.downloaded > 0 && response.status != 206) {
LOG_WRN("%s: server did not respond with 206 Partial Content for a resume request. Status: %d\n", func, response.status);
if (response.status == 416 || response.status == 200) {
// the kept partial cannot be resumed: 416 for a complete or stale one,
// 200 from a server that ignores the range; drop it, so the next
// attempt starts over instead of wedging on the same answer
ofs.close();
remove(path_tmp.c_str());
}
return false;
}
if (p.downloaded == 0 && response.status != 200) {
@@ -214,7 +224,7 @@ static bool common_pull_file(httplib::Client & cli,
if (progress_step >= p.total / 1000 || p.downloaded == p.total) {
if (callback) {
callback->on_update(p);
if (callback->is_cancelled()) {
if (callback->is_cancelled() && (p.total == 0 || p.downloaded != p.total)) {
return false;
}
}
@@ -331,10 +341,24 @@ static int common_download_file_single_online(const std::string & url,
}
bool success = false;
const std::string path_temporary = path + ".downloadInProgress";
const std::string path_temporary = path + PARTIAL_SUFFIX;
int delay = retry_delay_seconds;
if (!skip_etag && !etag.empty()) {
if (std::filesystem::exists(path_temporary) && read_etag(path_temporary) != etag) {
LOG_WRN("%s: partial file does not match the server etag, discarding it: %s\n", __func__, path_temporary.c_str());
if (remove(path_temporary.c_str()) != 0) {
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
return -1;
}
}
write_etag(path_temporary, etag);
}
if (opts.callback) {
// report the resume offset, so a resumed file does not start at 0
p.downloaded = supports_ranges && std::filesystem::exists(path_temporary) ?
std::filesystem::file_size(path_temporary) : 0;
opts.callback->on_start(p);
}
@@ -372,6 +396,7 @@ static int common_download_file_single_online(const std::string & url,
}
if (!etag.empty() && !skip_etag) {
write_etag(path, etag);
remove((path_temporary + ".etag").c_str());
}
success = true;
break;
@@ -381,12 +406,7 @@ static int common_download_file_single_online(const std::string & url,
if (opts.callback) {
opts.callback->on_done(p, success);
}
if (opts.callback && opts.callback->is_cancelled() &&
std::filesystem::exists(path_temporary)) {
if (remove(path_temporary.c_str()) != 0) {
LOG_ERR("%s: unable to delete temporary file: %s\n", __func__, path_temporary.c_str());
}
}
// a stopped download keeps path_temporary, so the next attempt resumes from it
if (!success) {
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
return -1; // max attempts reached
@@ -594,33 +614,65 @@ static hf_cache::hf_file find_best_sibling(const hf_cache::hf_files & files,
return best;
}
static hf_cache::hf_file find_best_mmproj(const hf_cache::hf_files & files,
const std::string & model) {
return find_best_sibling(files, model, "mmproj");
static const std::vector<common_download_sidecar> sidecars = {
{ "mmproj", "mmproj", false, &common_download_opts::download_mmproj, &common_download_hf_plan::mmproj },
{ "mtp", "mtp-", true, &common_download_opts::download_mtp, &common_download_hf_plan::mtp },
{ "dflash", "dflash-", true, &common_download_opts::download_dflash, &common_download_hf_plan::dflash },
{ "eagle3", "eagle3-", true, &common_download_opts::download_eagle3, &common_download_hf_plan::eagle3 },
{ "dspark", "dspark-", true, &common_download_opts::download_dspark, &common_download_hf_plan::dspark },
};
const std::vector<common_download_sidecar> & common_download_sidecars() {
return sidecars;
}
static hf_cache::hf_file find_best_mtp(const hf_cache::hf_files & files,
const std::string & model,
const std::string & tag = "") {
return find_best_sibling(files, model, "mtp-", tag);
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind) {
for (const auto & s : sidecars) {
if (kind == s.name) {
opts.*s.opt = true;
return true;
}
}
return false;
}
static hf_cache::hf_file find_best_eagle3(const hf_cache::hf_files & files,
const std::string & model,
const std::string & tag = "") {
return find_best_sibling(files, model, "eagle3-", tag);
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan) {
std::vector<std::string> out;
for (const auto & s : sidecars) {
if (!(plan.*s.file).path.empty()) {
out.emplace_back(s.name);
}
}
return out;
}
static hf_cache::hf_file find_best_dflash(const hf_cache::hf_files & files,
const std::string & model,
const std::string & tag = "") {
return find_best_sibling(files, model, "dflash-", tag);
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan) {
std::vector<common_download_partial> out;
// a file in the cache resolves to its snapshot entry, a file still to download to its blob
auto add = [&](const std::string & kind, const hf_cache::hf_file & f) {
if (!f.local_path.empty() && f.local_path != f.final_path) {
out.push_back({ kind, f.local_path + PARTIAL_SUFFIX });
}
};
for (const auto & f : plan.model_files) {
add("", f);
}
for (const auto & s : sidecars) {
add(s.name, plan.*s.file);
}
return out;
}
static hf_cache::hf_file find_best_dspark(const hf_cache::hf_files & files,
const std::string & model,
const std::string & tag = "") {
return find_best_sibling(files, model, "dspark-", tag);
// true when `name` carries the keyword of a sidecar kind
static bool name_is_sidecar(const std::string & name) {
for (const auto & s : sidecars) {
if (name.find(s.keyword) != std::string::npos) {
return true;
}
}
return false;
}
static bool gguf_filename_is_model(const std::string & filepath) {
@@ -633,12 +685,7 @@ static bool gguf_filename_is_model(const std::string & filepath) {
filename = filename.substr(pos + 1);
}
return filename.find("mmproj") == std::string::npos &&
filename.find("imatrix") == std::string::npos &&
filename.find("mtp-") == std::string::npos &&
filename.find("eagle3-") == std::string::npos &&
filename.find("dflash-") == std::string::npos &&
filename.find("dspark-") == std::string::npos;
return filename.find("imatrix") == std::string::npos && !name_is_sidecar(filename);
}
static hf_cache::hf_file find_best_model(const hf_cache::hf_files & files,
@@ -730,8 +777,11 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
}
} else {
primary = find_best_model(all, tag);
// a requested sidecar can resolve on its own, without a full model of the same tag
if (primary.path.empty() && !opts.download_mtp && !opts.download_dflash && !opts.download_eagle3 && !opts.download_dspark) {
// a requested draft can resolve on its own, without a full model of the same tag
const bool want_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
return s.draft && opts.*s.opt;
});
if (primary.path.empty() && !want_draft) {
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
list_available_gguf_files(all);
return plan;
@@ -743,24 +793,22 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
plan.model_files = get_split_files(all, primary);
}
if (opts.download_mmproj && !primary.path.empty()) {
plan.mmproj = find_best_mmproj(all, primary.path);
}
if (opts.download_mtp) {
plan.mtp = find_best_mtp(all, primary.path, tag);
}
if (opts.download_dflash) {
plan.dflash = find_best_dflash(all, primary.path, tag);
}
if (opts.download_eagle3) {
plan.eagle3 = find_best_eagle3(all, primary.path, tag);
}
if (opts.download_dspark) {
plan.dspark = find_best_dspark(all, primary.path, tag);
// a draft resolves at the tag, the mmproj pairs with the model
for (const auto & s : sidecars) {
if (!(opts.*s.opt)) {
continue;
}
if (s.draft) {
plan.*s.file = find_best_sibling(all, primary.path, s.keyword, tag);
} else if (!primary.path.empty()) {
plan.*s.file = find_best_sibling(all, primary.path, s.keyword);
}
}
if (primary.path.empty() &&
plan.mtp.local_path.empty() && plan.dflash.local_path.empty() && plan.eagle3.local_path.empty() && plan.dspark.local_path.empty()) {
const bool has_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
return s.draft && !(plan.*s.file).local_path.empty();
});
if (primary.path.empty() && !has_draft) {
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
list_available_gguf_files(all);
}
@@ -926,6 +974,11 @@ std::string common_docker_resolve_model(const std::string & docker) {
}
}
// a cached file that lists a model under its tag: the first split of a model, not a sidecar
static bool is_listed_model(const gguf_split_info & split) {
return split.index == 1 && !split.tag.empty() && !name_is_sidecar(split.prefix);
}
std::vector<common_cached_model_info> common_list_cached_models() {
std::unordered_set<std::string> seen;
std::vector<common_cached_model_info> result;
@@ -934,12 +987,7 @@ std::vector<common_cached_model_info> common_list_cached_models() {
for (const auto & f : files) {
auto split = get_gguf_split_info(f.path);
if (split.index != 1 || split.tag.empty() ||
split.prefix.find("mmproj") != std::string::npos ||
split.prefix.find("mtp-") != std::string::npos ||
split.prefix.find("eagle3-") != std::string::npos ||
split.prefix.find("dflash-") != std::string::npos ||
split.prefix.find("dspark-") != std::string::npos) {
if (!is_listed_model(split)) {
continue;
}
if (seen.insert(f.repo_id + ":" + split.tag).second) {
@@ -970,6 +1018,44 @@ std::string common_download_resolve_path(const std::string & hf_repo_with_tag, c
return find_best_model(files, tag).local_path;
}
// the files -hf loads from the cache, the model with every sidecar, for each cached
// tag of `repo_id` other than `tag`
static std::unordered_set<std::string> files_in_use(const std::string & repo_id, const std::string & tag) {
common_download_opts opts;
opts.offline = true;
for (const auto & s : sidecars) {
opts.*s.opt = true;
}
std::unordered_set<std::string> tags;
std::unordered_set<std::string> used;
for (const auto & f : hf_cache::get_cached_files(repo_id)) {
auto split = get_gguf_split_info(f.path);
if (!is_listed_model(split) || split.tag == tag || !tags.insert(split.tag).second) {
continue;
}
common_params_model model;
model.hf_repo = repo_id + ":" + split.tag;
auto plan = common_download_get_hf_plan(model, opts);
for (const auto & m : plan.model_files) {
used.insert(m.local_path);
}
for (const auto & s : sidecars) {
const auto & sf = plan.*s.file;
if (!sf.local_path.empty()) {
used.insert(sf.local_path);
}
}
}
return used;
}
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove);
bool common_download_remove(const std::string & hf_repo_with_tag) {
namespace fs = std::filesystem;
@@ -989,11 +1075,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
return false;
}
// collect snapshot entries whose tag matches
// collect snapshot entries whose tag matches, but a file another tag loads stays
const auto used = files_in_use(repo_id, tag_upper);
std::vector<fs::path> to_remove;
for (const auto & f : files) {
auto split = get_gguf_split_info(f.path);
if (split.tag == tag_upper) {
if (split.tag == tag_upper && used.find(f.local_path) == used.end()) {
to_remove.emplace_back(f.local_path);
}
}
@@ -1002,6 +1090,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
return false;
}
return remove_snapshot_entries(repo_id, to_remove);
}
// remove snapshot entries of `repo_id`, then the blobs no remaining entry references
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove) {
namespace fs = std::filesystem;
// resolve blob paths from symlinks before deleting snapshot entries
std::vector<fs::path> blobs_to_check;
for (const auto & p : to_remove) {
@@ -1053,3 +1148,26 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
return true;
}
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts) {
common_params_model model;
model.hf_repo = hf_repo_with_tag;
common_download_opts cached = opts;
cached.offline = true;
auto plan = common_download_get_hf_plan(model, cached);
std::vector<std::filesystem::path> to_remove;
for (const auto & s : sidecars) {
const auto & f = plan.*s.file;
if (!f.local_path.empty()) {
to_remove.emplace_back(std::filesystem::u8path(f.local_path));
}
}
if (to_remove.empty()) {
return false;
}
return remove_snapshot_entries(common_download_split_repo_tag(hf_repo_with_tag).first, to_remove);
}
+38 -1
View File
@@ -104,10 +104,17 @@ std::string common_docker_resolve_model(const std::string & docker);
// Remove a cached model from disk
// input format: "user/model" or "user/model:tag"
// - if tag is omitted, removes the entire repo cache directory
// - if tag is present, removes only files matching that tag (and orphaned blobs)
// - if tag is present, removes only files matching that tag (and orphaned blobs),
// except the files another cached tag of the repo still loads
// returns true if anything was removed
bool common_download_remove(const std::string & hf_repo_with_tag);
// remove the cached sidecar files `opts` selects for "user/model:tag", as the cached
// plan resolves them, and leave the model files in place; a sidecar file another tag
// shares goes for every tag that resolves it
// returns true if anything was removed
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts);
struct common_download_hf_plan {
hf_cache::hf_file primary;
hf_cache::hf_files model_files;
@@ -119,3 +126,33 @@ struct common_download_hf_plan {
hf_cache::hf_file preset; // if set, only this file is downloaded
};
common_download_hf_plan common_download_get_hf_plan(const common_params_model & model, const common_download_opts & opts);
// a sidecar kind a download can pull next to its model: mmproj, and the draft heads
// of --spec-type draft-<name>
struct common_download_sidecar {
const char * name; // the kind, as the API names it
const char * keyword; // the part of a file name that marks a file of this kind
bool draft; // a draft head, resolved at the tag even without a model
bool common_download_opts::* opt; // the option that requests it
hf_cache::hf_file common_download_hf_plan::* file; // the plan file it resolves to
};
// every sidecar kind, the single list the resolution, the listing and the removal follow
const std::vector<common_download_sidecar> & common_download_sidecars();
// select the sidecar `kind` in `opts`; returns false for an unknown kind
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind);
// the sidecar kinds `plan` resolves a file for
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan);
// a file `plan` still has to download, at the path its partial takes while it is
// incomplete; kind is the sidecar it belongs to, empty for the model files
struct common_download_partial {
std::string kind;
std::string path;
};
// the partials the download of `plan` writes, for the files not in the cache yet
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan);
+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);
}
+36 -6
View File
@@ -425,7 +425,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();
@@ -1028,10 +1033,6 @@ const func_builtins & value_bool_t::get_builtins() const {
return builtins;
}
[[noreturn]] static value array_unique_not_implemented(const func_args &) {
throw not_implemented_exception("Array unique builtin not implemented");
}
const func_builtins & value_array_t::get_builtins() const {
static const func_builtins builtins = {
{"default", default_value},
@@ -1257,7 +1258,36 @@ const func_builtins & value_array_t::get_builtins() const {
}
return result;
}},
{"unique", array_unique_not_implemented},
{"unique", [](const func_args & args) -> value {
args.ensure_count(1, 3);
args.ensure_vals<value_array>();
value val = args.get_pos(0);
value val_case = args.get_kwarg_or_pos("case_sensitive", 1);
value attribute = args.get_kwarg_or_pos("attribute", 2);
// FIXME: unique is currently always case sensitive
//const bool case_sensitive = val_case->as_bool(); // undefined == false
value undef = mk_val<value_undefined>();
std::vector<value> result;
std::vector<value> seen;
for (const auto & item : val->as_array()) {
value key = item;
if (!attribute->is_undefined()) {
key = get_attribute(item, attribute, undef);
}
bool dup = false;
for (const auto & s : seen) {
if (value_compare(s, key, value_compare_op::eq)) {
dup = true;
break;
}
}
if (!dup) {
seen.push_back(key);
result.push_back(item);
}
}
return mk_val<value_array>(std::move(result));
}},
};
return builtins;
}
+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
+11
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",
@@ -146,6 +148,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"JinaBertModel": "bert",
"JinaEmbeddingsV5Model": "bert",
"K2HorizonForCausalLM": "k2_horizon",
"Kolibri1ForCausalLM": "kolibri1",
"KORMoForCausalLM": "qwen",
"KimiK25ForConditionalGeneration": "deepseek",
"KimiK3ForConditionalGeneration": "kimi_k3",
@@ -193,13 +196,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",
@@ -225,6 +231,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
"MuseSparkForConditionalGeneration": "muse_spark",
"OpenELMForCausalLM": "openelm",
"OpenJevModel": "qwen",
"H2OLightningModel": "h2o_lightning",
"OrionForCausalLM": "orion",
"PLMForCausalLM": "plm",
"PLaMo2ForCausalLM": "plamo",
@@ -355,13 +362,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",
@@ -374,6 +384,7 @@ MMPROJ_MODEL_MAP: dict[str, str] = {
"Qwen3VLForConditionalGeneration": "qwen3vl",
"Qwen3VLMoeForConditionalGeneration": "qwen3vl",
"OpenJevModel": "qwen3vl",
"H2OLightningModel": "h2o_lightning",
"Qwen3_5ForConditionalGeneration": "qwen3vl",
"Qwen3_5MoeForConditionalGeneration": "qwen3vl",
"Qwen4ExpForConditionalGeneration": "qwen4exp",
+46 -13
View File
@@ -10,6 +10,7 @@ import json
import os
import re
import sys
import tempfile
from enum import IntEnum
from pathlib import Path
from hashlib import sha256
@@ -1625,7 +1626,7 @@ class TextModel(ModelBase):
self.gguf_writer.add_head_count_kv(n_head_kv)
logger.info(f"gguf: key-value head count = {n_head_kv}")
if self.hparams.get("is_causal") is False:
if self.hparams.get("is_causal") is False or self.hparams.get("use_bidirectional_attention") is True:
self.gguf_writer.add_causal_attention(False)
logger.info("gguf: causal attention = False")
@@ -1700,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":
@@ -1836,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"
@@ -1872,6 +1876,9 @@ class TextModel(ModelBase):
if chkhsh == "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc":
# ref: https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B
res = "gpt-4o"
if chkhsh == "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78":
# ref: https://huggingface.co/Aleph-Alpha/Kolibri-1
res = "qwen2"
if chkhsh == "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df":
# ref: https://huggingface.co/aari1995/German_Semantic_V3
res = "jina-v2-de"
@@ -1935,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"
@@ -2166,20 +2176,34 @@ class TextModel(ModelBase):
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
special_vocab.add_to_gguf(self.gguf_writer)
def _is_hybriddna(self) -> bool:
tokenizer_config_file = self.dir_model / "tokenizer_config.json"
if not tokenizer_config_file.is_file():
return False
with open(tokenizer_config_file, "r", encoding="utf-8") as f:
return json.load(f).get("tokenizer_class") == "HybridDNATokenizer"
def _set_vocab_hybriddna(self):
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(self.dir_model, trust_remote_code=True)
vocab_size = self.hparams.get("vocab_size", len(tokenizer.vocab)) # ty: ignore[unresolved-attribute]
assert max(tokenizer.vocab.values()) < vocab_size # ty: ignore[unresolved-attribute]
vocab = tokenizer.get_vocab()
vocab_size = self.hparams.get("vocab_size", len(vocab))
assert max(vocab.values()) < vocab_size
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in tokenizer.vocab.items()} # ty: ignore[unresolved-attribute]
# k-mers can share text with a base-vocab BPE token (e.g. CCCCCC) and get
# dropped by get_vocab(); a reserved marker suffix (U+E000) keeps each
# k-mer's own id (llama.cpp strips it on detokenization)
for kmer in tokenizer.kmers: # ty: ignore[unresolved-attribute]
reverse_vocab = {id_: encoded_tok for encoded_tok, id_ in vocab.items()}
# k-mers can share text with a base-vocab BPE token (e.g. CCCCCC) and get dropped by get_vocab()
# a reserved marker suffix (U+E000) keeps each k-mer's own id (llama.cpp strips it on detokenization)
# tail="pad_n" (Carbon-14B) adds N-padded tail tokens ("CT" -> "CTNNNN") that get the same marker
# llama.cpp detects them to pad with N and strips the padding on detokenization, tail="char" is unsupported
tail = getattr(tokenizer, "tail", "pad_a")
if tokenizer.k != 6 or tail not in ("pad_a", "pad_n"):
raise NotImplementedError(f"HybridDNATokenizer with k={tokenizer.k}, tail={tail!r}")
for kmer in [*tokenizer.kmers, *getattr(tokenizer, "npad_tokens", [])]:
reverse_vocab[tokenizer.dna_token_to_id[kmer]] = kmer + "\ue000" # ty: ignore[unresolved-attribute]
added_vocab = tokenizer.get_added_vocab() # ty: ignore[unresolved-attribute]
added_tokens_decoder = tokenizer.added_tokens_decoder # ty: ignore[unresolved-attribute]
# older HybridDNATokenizer versions (Carbon-500M/3B/8B) do not register the base tokenizer's added tokens (<|im_start|>, <think>, ...) as their own
# but they are still encoded as special tokens: take their types from the base tokenizer
base_tokenizer = getattr(tokenizer, "_base_tokenizer", tokenizer)
added_tokens_decoder = {**base_tokenizer.added_tokens_decoder, **tokenizer.added_tokens_decoder} # ty: ignore[unresolved-attribute]
tokens: list[str] = []
toktypes: list[int] = []
@@ -2189,7 +2213,7 @@ class TextModel(ModelBase):
toktypes.append(gguf.TokenType.UNUSED)
else:
token: str = reverse_vocab[i]
if token in added_vocab:
if i in added_tokens_decoder:
if added_tokens_decoder[i].special or self.does_token_look_special(token):
toktypes.append(gguf.TokenType.CONTROL)
else:
@@ -2201,10 +2225,19 @@ class TextModel(ModelBase):
tokpre = self.get_vocab_base_pre(tokenizer)
self.gguf_writer.add_tokenizer_model("hybriddna")
self.gguf_writer.add_tokenizer_pre(tokpre)
self.gguf_writer.add_dna_auto_tags(getattr(tokenizer, "auto_dna_tags", False))
self.gguf_writer.add_token_list(tokens)
self.gguf_writer.add_token_types(toktypes)
special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True)
if not special_vocab.merges:
# Carbon-500M/3B/8B ship only tokenizer.py, no tokenizer.json
# take the merges and special token ids from the base BPE tokenizer, keeping the model's own chat template (if any)
with tempfile.TemporaryDirectory() as tmp:
base_tokenizer.save_pretrained(tmp)
base_special_vocab = gguf.SpecialVocab(Path(tmp), load_merges=True)
base_special_vocab.chat_template = special_vocab.chat_template
special_vocab = base_special_vocab
special_vocab.add_to_gguf(self.gguf_writer)
def _set_vocab_qwen(self):
@@ -3116,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
+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:
+4 -3
View File
@@ -296,19 +296,20 @@ class GraniteSwitchModel(GraniteMoeModel):
def set_vocab(self):
super().set_vocab()
if self._has_decision_adapter():
self.gguf_writer.add_chat_template([{"name": "systemone", "template": self._systemone_template()}])
systemone_template = self._systemone_template()
logger.info("System One Template: %s", systemone_template)
self.gguf_writer.add_chat_template([{"name": "systemone", "template": systemone_template}])
def _systemone_template(self) -> str:
# prompt of the decision aLoRA: a state turn, then a question turn that opens with the control token
# (it replaces the first token of "### DECISION"), the answer is the label token that comes next
description = jinja_str_or_json("o.description")
option = (
"{% if type != 'noul' %}{{ letters[loop.index0] }}: {% if o.description %}" + description + "{% endif %}"
"{% if type != 'noul' %}{{ o.label }}: {% if o.description %}" + description + "{% endif %}"
"{% elif o.key == 'true' %}A: {% if o.description %}" + description + "{% else %}The statement is true.{% endif %}"
"{% else %}B: {% if o.description %}" + description + "{% else %}The statement is false.{% endif %}{% endif %}"
)
return (
"{% set letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' %}"
"<|start_of_role|>user<|end_of_role|>State:\n" + jinja_str_or_json("state") + "<|end_of_text|>\n"
"<|start_of_role|>user<|end_of_role|><|decision|> DECISION\nQuestion:\n" + jinja_str_or_json("instructions")
+ "{% if type == 'score' %} Rate along the ordered levels below (lowest first).{% endif %}"
+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
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
from .afmoe import AfmoeModel
from .base import ModelBase, gguf
@ModelBase.register("Kolibri1ForCausalLM")
@ModelBase.example("Aleph-Alpha/Kolibri-1")
class Kolibri1Model(AfmoeModel):
model_arch = gguf.MODEL_ARCH.KOLIBRI1
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# AfmoeModel reads these keys, this config stores the same values under other names
self.hparams["num_shared_experts"] = 1
self.hparams["route_norm"] = self.hparams["norm_topk_prob"]
self.hparams["intermediate_size"] = self.hparams["moe_intermediate_size"]
def set_gguf_parameters(self):
super().set_gguf_parameters()
self.gguf_writer.add_expert_shared_feed_forward_length(self.hparams["shared_expert_intermediate_size"])
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in self.hparams["layer_types"]])
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
+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:
+3 -2
View File
@@ -129,8 +129,9 @@ class LlamaModel(TextModel):
tokenizer_config_json = json.load(f)
if (add_prefix_space := tokenizer_config_json.get("add_prefix_space")) is not None:
self.gguf_writer.add_add_space_prefix(add_prefix_space)
if tokenizer_config_json.get("tokenizer_class") == "HybridDNATokenizer":
return self._set_vocab_hybriddna()
if self._is_hybriddna():
return self._set_vocab_hybriddna()
try:
self._set_vocab_sentencepiece()
+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")
+9
View File
@@ -59,6 +59,10 @@ class Qwen2Model(TextModel):
model_arch = gguf.MODEL_ARCH.QWEN2
def set_vocab(self):
# HuggingFaceBio/Carbon-14B: Qwen3 with the HybridDNATokenizer (Qwen3 BPE plus DNA 6-mers)
if self._is_hybriddna():
self._set_vocab_hybriddna()
return
try:
self._set_vocab_sentencepiece()
except FileNotFoundError:
@@ -253,6 +257,11 @@ class Qwen3Model(Qwen2Model):
yield from super().modify_tensors(data_torch, name, bid)
return
# output embedding projection (voyage-4-nano)
if name == "linear.weight":
self.gguf_writer.add_embedding_length_out(data_torch.shape[0])
name = "dense_2_out.weight"
yield from super().modify_tensors(data_torch, name, bid)
+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)
+4
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"},
@@ -191,6 +193,8 @@ pre_computed_hashes = [
{"name": "grok-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/alvarobartt/grok-2-tokenizer", "chkhsh": "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273"},
# muse-spark uses the l4_202k tokenizer, whose pre-tokenizer regex is gpt-4o's
{"name": "gpt-4o", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B", "chkhsh": "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc"},
# kolibri1 uses a 128k vocabulary with the qwen2 pre-tokenizer regex
{"name": "qwen2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/Aleph-Alpha/Kolibri-1", "chkhsh": "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78"},
# jina-v2-de variants
{"name": "jina-v2-de", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/aari1995/German_Semantic_V3", "chkhsh": "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df"},
{"name": "gpt-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/evilfreelancer/ruGPT3XL", "chkhsh": "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4"},
+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.
+4 -4
View File
@@ -6,8 +6,8 @@ The cross-compilation toolchain images are provided by the
[Qualcomm Snapdragon Toolchain registry](https://github.com/snapdragon-toolchain).
These Docker images include the Android NDK, OpenCL SDK, Hexagon SDK, CMake, and the necessary cross-compilers:
* **Android toolchain**: `ghcr.io/snapdragon-toolchain/arm64-android:v0.7`
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.7`
* **Android toolchain**: `ghcr.io/snapdragon-toolchain/arm64-android:v0.8`
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.8`
The unified build utility (`scripts/snapdragon/build.py`) automatically pulls
and orchestrates these containers to perform target compilation.
@@ -40,7 +40,7 @@ Alternatively, you can build llama.cpp manually by entering the cross-compilatio
```bash
# Start the cross-compilation container manually:
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.7
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-android:v0.8
# Inside the container, build the project using presets:
[d]/workspace> cp docs/backend/snapdragon/CMakeUserPresets.json .
@@ -53,7 +53,7 @@ Preset CMake variables:
GGML_HEXAGON="ON"
GGML_OPENCL="ON"
GGML_OPENMP="OFF"
HEXAGON_SDK_ROOT="/opt/hexagon/6.6.0.0"
HEXAGON_SDK_ROOT="/opt/hexagon/6.7.0.0"
...
-- Including OpenCL backend
-- Including Hexagon backend
+151
View File
@@ -48,6 +48,157 @@ Alternatively, users can partition and split the model across multiple virtual s
tensor-splitting, or row-splitting modes. For user-facing execution modes and examples, see the
[Snapdragon user guide](README.md#multi-device-execution-modes).
## Tiled Weight Formats
Weight tensors in the Hexagon backend are repacked from flat row-major GGML layouts into 2D tiles in memory.
Tiling groups 2D matrix blocks contiguously:
- Eliminates strided row reads when fetching 2D tiles into VTCM via DMA.
- Matches native HVX vector width (128 bytes) and HMX systolic array dimensions (32x32).
- Repacking happens on the host during model load (`set_tensor()`) or via runtime repacking (`rt-repack`).
Implementation: [`htp-repack.cpp`](../../../ggml/src/ggml-hexagon/htp-repack.cpp).
### Tile Dimensions and Grid Layout
Each tile represents a submatrix of `N_rows = 32` rows and `K_cols` columns:
- Standard types (`Q4_0`, `Q4_1`, `Q8_0`, `IQ4_NL`, `MXFP4`, and K-quants): `K_cols = 32` (`HTP_TENSOR_TILE_N_COLS = 32`).
- `Q2_0`: `K_cols = 64` (`HTP_TENSOR_TILE_K_Q2_0 = 64`).
- `Q1_0`: `K_cols = 128` (`HTP_TENSOR_TILE_K_Q1_0 = 128`).
For a weight matrix of logical shape `[ne0, ne1]` (where `ne0 = K`, `ne1 = N`):
- `n_k_tiles = ne0 / K_cols`
- `n_col_tiles = (ne1 + 31) / 32`
- Tile index for column-tile `kt` and row-tile `ct`: `tile_idx = ct * n_k_tiles + kt`
- Byte offset in matrix buffer: `tile_offset = tile_idx * tile_size`
### Block-32 Quantized Formats
Block-32 formats use a tile size of 32 rows x 32 columns (`K_cols = 32`):
- **Q4_0** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_0`):
- **Quants** (bytes 0..511, 512 bytes):
Contains 32 rows x 32 columns of 4-bit quants.
Divided into 16 column pairs `cp = 0..15` covering columns `2*cp` and `2*cp + 1`.
For each column pair `cp`, 32 bytes are stored consecutively for rows `row = 0..31`:
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
Low nibble holds the even column; high nibble holds the odd column.
- **Scales** (bytes 512..575, 64 bytes):
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
- **Q8_0** (1088 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q8_0`):
- **Quants** (bytes 0..1023, 1024 bytes):
Contains 32 rows x 32 columns of signed 8-bit quants (`int8_t`).
Divided into 16 column pairs `cp = 0..15` covering columns `col0 = 2*cp` and `col1 = 2*cp + 1`.
For each column pair `cp`, 64 bytes store interleaved column elements for each row `row = 0..31`:
`tile[cp * 64 + 2 * row + 0] = quant[row][col0]`
`tile[cp * 64 + 2 * row + 1] = quant[row][col1]`
- **Scales** (bytes 1024..1087, 64 bytes):
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
- **IQ4_NL** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_IQ4_NL`):
- **Quants** (bytes 0..511, 512 bytes):
Same nibble transposition and storage layout as `Q4_0`.
Contains 32 rows x 32 columns of non-linear 4-bit indices (values 0..15).
Divided into 16 column pairs `cp = 0..15`:
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
- **Scales** (bytes 512..575, 64 bytes):
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
- **Dequantization**:
In HVX/HMX kernels, each 4-bit index is looked up in the 16-entry non-linear table (`iq4_nl_to_fp16_lut`) to produce an FP16 value, then multiplied by the row scale `d`.
- **MXFP4** (544 bytes per tile, `HTP_TENSOR_TILE_SIZE_MXFP4`):
- **Quants** (bytes 0..511, 512 bytes):
Same nibble transposition and storage layout as `Q4_0`.
Contains 32 rows x 32 columns of 4-bit OCP Microscaling FP4 (E2M1) values.
Divided into 16 column pairs `cp = 0..15`:
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
- **Scales** (bytes 512..543, 32 bytes):
32 unsigned 8-bit values (`uint8_t`), storing one 8-bit biased exponent (E8M0, bias 127) scale `e` per row `row = 0..31`.
- **Dequantization**:
Kernel converts E8M0 bias to IEEE FP16 bias via `vh = clamp(e - 112, 0, 30) << 10` to form a power-of-two FP16 scale.
Each 4-bit FP4 nibble is unpacked via `mxfp4_to_fp16_lut` and multiplied by this scale.
- **Q4_1** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_1`):
- **Quants** (bytes 0..511, 512 bytes):
Same 4-bit nibble transposition and storage layout as `Q4_0`.
- **Scales** (bytes 512..639, 128 bytes):
32 pairs of FP16 values (`ggml_half`): `scale[2 * row + 0] = d` (scale) and `scale[2 * row + 1] = m` (min) for each row `row = 0..31`.
### Q1_0 Format (1-Bit Quantization)
`Q1_0` packs 128 columns per tile (`K_cols = 128`, `HTP_TENSOR_TILE_K_Q1_0 = 128`):
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q1_0`):
- **Quants** (bytes 0..511, 512 bytes):
Contains 32 rows x 128 columns of 1-bit quants (-1 or +1).
Divided into 4 quarters (`quarter = 0..3`), where each quarter covers 32 columns in K.
Each quarter occupies 128 bytes (1 HVX vector) in `vrmpy` byte order.
Within a quarter, columns `kq = 0..31` map into 8 groups `g = kq / 4` (0..7) of 4 consecutive columns:
`byte_offset = quarter * 128 + 4 * row + (kq % 4)`
`bit = g` (0..7)
- **Scales** (bytes 512..575, 64 bytes):
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
One scale per row applies to all 128 columns (all 4 quarters) of the tile.
- **HMX Mapping (1-to-4 Tile Expansion)**:
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q1_0`) expands each 32x128 Q1_0 tile (576 bytes) into 4 contiguous 32x32 FP16 HMX tiles (4 x 2048 bytes = 8192 bytes total) in VTCM.
- HMX systolic array processes the 4 resulting 32x32 FP16 tiles over 4 compute steps along K.
### Q2_0 Format (2-Bit Quantization)
`Q2_0` packs 64 columns per tile (`K_cols = 64`, `HTP_TENSOR_TILE_K_Q2_0 = 64`):
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_0`):
- **Quants** (bytes 0..511, 512 bytes):
Contains 32 rows x 64 columns of 2-bit quants (codes 0..3 representing signed values -1..2).
Divided into 16 column groups `group = 0..15` of 4 columns each.
Stored in `vrmpy` byte order across 4 HVX vectors (vec 0..3):
Vector `group >> 2` (0..3) holds `group & 3` at bit offset `2 * (group & 3)`.
Byte `4 * row + byte` holds row `row = 0..31` and column byte `0..3`.
Vec 0..1 holds the first 32 columns (half 0), vec 2..3 holds the next 32 columns (half 1).
- **Scales** (bytes 512..575, 64 bytes):
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31` (at offset `512 + 2 * row`).
One scale per row applies to all 64 columns of the tile.
- **HMX Mapping (1-to-2 Tile Expansion)**:
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q2_0`) expands each 32x64 Q2_0 tile (576 bytes) into 2 contiguous 32x32 FP16 HMX tiles (2 x 2048 bytes = 4096 bytes total) in VTCM.
- HMX systolic array processes the 2 resulting 32x32 FP16 tiles over 2 compute steps along K.
- **HVX Vector Dot-Product**:
- `tiled_vec_dot_q2_0_32x1` and `tiled_vec_dot_q2_0_32x2` unpack 2-bit quants using `unpack_q3_k_low2`, subtract 1, and accumulate against Q8_0 activation tiles using `Q6_Vw_vrmpyacc_VwVbVb`.
### K-Quants Formats
In GGML, K-quants pack 256 elements per super-block.
On Hexagon, each 256-element super-block splits along K into 8 tiles of 32 columns (`K_cols = 32`).
Byte ordering: `Q2_K`, `Q3_K`, and `Q6_K` use `vrmpy` byte order within each 128B HVX vector:
`k = 0..31` is split into 8 groups `g = k/4` of 4 `k` each; byte `4*r + (k%4)` holds row `r`.
- **Q2_K** (336 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_K`, lossless tiling with same size as `block_q2_K`):
- `bytes 0..255` (vec 0..1): unsigned 2-bit quants; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
- `bytes 256..319`: raw `scales[]` bytes (`min << 4 | sc`): `[0..31]` for k 0..15, `[32..63]` for k 16..31.
- `bytes 320..335`: 8 FP16 in tile `j` (0..7) of super-block: `d` (`j < 4`) or `-dmin` (`j >= 4`) for rows `8*(j%4)..8*(j%4)+7`.
- **Q3_K** (512 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q3_K`):
- `bytes 0..255` (vec 0..1): low 2 bits; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
- `bytes 256..383` (vec 2): sign mask, bit `g` set when `hmask` is 0 (quant = `low2 - 4`).
- `bytes 384..511` (vec 3): FP16 scales `d * (scales[] - 32)`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
- **Q4_K** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_K`, same layout as Q4_1 tile; each 256-k super-block splits into 8 tiles of 32 k):
- `bytes 0..511` (vec 0..3): 4-bit quants; byte `32*cp + r` holds `k = 2*cp` (low nibble) and `2*cp + 1` (high nibble).
- `bytes 512..639` (vec 4): 32x2 FP16 per row: `(d * sc, -dmin * m)`.
- **Q5_K** (768 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q5_K`):
- `bytes 0..639` (vec 0..4): Q4_K tile (low 4 bits + FP16 scale/min pairs).
- `bytes 640..767` (vec 5): 5th-bit plane; byte `l` holds the 8 high bits for lane `l` across vec `i = 0..3` (`bit 2i = lo, 2i+1 = hi`).
- **Q6_K** (896 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q6_K`):
- `bytes 0..511` (vec 0..3): low 4 bits; vec `i` holds group `2i` (low nibble) and `2i+1` (high nibble).
- `bytes 512..767` (vec 4..5): high 2 bits; vec `4+m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
- `bytes 768..895` (vec 6): FP16 scales `d * scales[]`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
## Op and Kernel Development Guidelines
Writing high-performance operators for Hexagon requires following specific guidelines.
+2 -2
View File
@@ -3,7 +3,7 @@
The cross-compilation is performed using the Snapdragon Linux Docker toolchain image (see
[github.com/snapdragon-toolchain](https://github.com/snapdragon-toolchain)):
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.7`
* **Linux toolchain**: `ghcr.io/snapdragon-toolchain/arm64-linux:v0.8`
The unified build utility (`scripts/snapdragon/build.py`) automatically pulls
and orchestrates this container to perform target compilation. You only need to
@@ -29,7 +29,7 @@ Alternatively, you can build llama.cpp manually by entering the cross-compilatio
```bash
# Start the cross-compilation container manually:
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-linux:v0.7
~/src/llama.cpp$ docker run -it --rm -u $(id -u):$(id -g) --volume $(pwd):/workspace --platform linux/amd64 ghcr.io/snapdragon-toolchain/arm64-linux:v0.8
# Inside the container, build the project using presets:
[d]/workspace> cp docs/backend/snapdragon/CMakeUserPresets.json .
+6 -6
View File
@@ -8,7 +8,7 @@ Native Windows 11 arm64 builds have the following tool dependencies:
- UCRT and Driver Kit
- LLVM core libraries and Clang compiler (winget)
- CMake, Git, Python (winget)
- Hexagon SDK Community Edition 6.6 or later (see below)
- Hexagon SDK Community Edition 6.7 or later (see below)
- OpenCL SDK 2.3 or later (see below)
Note: The rest of the **Windows** build process assumes that you're running natively in Powershell.
@@ -54,15 +54,15 @@ c:\Qualcomm\OpenCL_SDK\2.3.2
Either use the trimmed down version (optimized for CI) from
https://github.com/snapdragon-toolchain/hexagon-sdk/releases/download/v6.6.0.0/hexagon-sdk-v6.6.0.0-arm64-wos.tar.xz
https://github.com/snapdragon-toolchain/hexagon-sdk/releases/download/v6.7.0.0/hexagon-sdk-v6.7.0.0-arm64-wos.tar.xz
Or download the complete official version from
https://softwarecenter.qualcomm.com/catalog/item/Hexagon_SDK?version=6.6.0.0
https://softwarecenter.qualcomm.com/catalog/item/Hexagon_SDK?version=6.7.0.0
Unzip/untar the archive into
```
c:\Qualcomm\Hexagon_SDK\6.6.0.0
c:\Qualcomm\Hexagon_SDK\6.7.0.0
```
## Install the latest Adreno GPU driver
@@ -150,8 +150,8 @@ The overall Hexagon backend build procedure for Windows on Snapdragon is the sam
However, additional settings are required for generating and signing HTP Ops libraries.
```
> $env:OPENCL_SDK_ROOT="C:\Qualcomm\OpenCL_SDK\2.3.2"
> $env:HEXAGON_SDK_ROOT="C:\Qualcomm\Hexagon_SDK\6.6.0.0"
> $env:HEXAGON_TOOLS_ROOT="C:\Qualcomm\Hexagon_SDK\6.6.0.0\tools\HEXAGON_Tools\19.0.07"
> $env:HEXAGON_SDK_ROOT="C:\Qualcomm\Hexagon_SDK\6.7.0.0"
> $env:HEXAGON_TOOLS_ROOT="C:\Qualcomm\Hexagon_SDK\6.7.0.0\tools\HEXAGON_Tools\21.0.04"
> $env:HEXAGON_HTP_CERT="c:\Users\MyUsers\Certs\ggml-htp-v1.pfx"
> $env:WINDOWS_SDK_BIN="C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0"
+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.
+30
View File
@@ -852,6 +852,36 @@ WebGPU allows cross-platform access to the GPU from supported browsers. We utili
Follow the instructions [here](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/) to download or build the emdawnwebgpu package (Note that it might be safer to build the emdawnwebgpu package locally, so that it stays in sync with the version of Dawn you have installed above). When building using CMake, the path to the emdawnwebgpu port file needs to be set with the flag `EMDAWNWEBGPU_DIR`.
## WASI
Requires [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) 34+ with `WASI_SDK_PATH` set. Only the libraries build (`llama-common` does not support WASI yet), so link your program against them:
```bash
FLAGS="-msimd128 -D_WASI_EMULATED_SIGNAL -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
cmake -B build-wasi -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-p2.cmake \
-DCMAKE_C_FLAGS="$FLAGS" -DCMAKE_CXX_FLAGS="$FLAGS" -DCMAKE_HAVE_LIBC_PTHREAD=1 \
-DBUILD_SHARED_LIBS=OFF -DGGML_NATIVE=OFF -DGGML_OPENMP=OFF -DLLAMA_BUILD_COMMON=OFF
cmake --build build-wasi --target llama
$WASI_SDK_PATH/bin/clang++ --target=wasm32-wasip3 -O3 $FLAGS -I include -I ggml/include examples/simple/simple.cpp \
build-wasi/src/libllama.a build-wasi/ggml/src/libggml*.a -lunwind -ldl -Wl,-z,stack-size=8388608 -o llama-simple.wasm
wasmtime run -W exceptions=y --dir models llama-simple.wasm -m models/model.gguf "Hello"
```
### wasi:webgpu
The WebGPU backend can target [wasi:webgpu](https://github.com/WebAssembly/wasi-webgpu) via [wasi-webgpu-headers](https://github.com/wasi-gfx/wasi-webgpu-headers) (with its submodule) and Dawn's `dawn-headers` release. Point `Dawn_DIR` at a directory containing this `DawnConfig.cmake` next to both:
```cmake
set(_wasi_webgpu "${CMAKE_CURRENT_LIST_DIR}/wasi-webgpu-headers")
add_library(dawn::webgpu_dawn INTERFACE IMPORTED)
set_target_properties(dawn::webgpu_dawn PROPERTIES
INTERFACE_SOURCES "${_wasi_webgpu}/webgpu.c;${_wasi_webgpu}/async_futures.c;${_wasi_webgpu}/imports.c;${_wasi_webgpu}/imports_component_type.o"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/dawn-headers/include/webgpu_upstream;${_wasi_webgpu}")
```
Then build as above, but with `wasi-sdk-p3.cmake` / `--target=wasm32-wasip3`, `-DGGML_WEBGPU=ON -DDawn_DIR=...` at configure, and `build-wasi/ggml/src/ggml-webgpu/libggml-webgpu.a` added at link. See `.github/workflows/build-wasi.yml` for a more complete example.
## IBM Z & LinuxONE
To read documentation for how to build on IBM Z & LinuxONE, [click here](./build-s390x.md)
+5 -5
View File
@@ -27,7 +27,7 @@ Legend:
| COL2IM_1D | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| CONCAT | ❌ | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
| CONT | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | 🟡 | ❌ | ❌ |
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| CONV_1D_DW | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| CONV_2D | ❌ | ❌ | 🟡 | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ❌ | ❌ |
| CONV_2D_DW | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| CONV_3D | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
@@ -76,10 +76,10 @@ Legend:
| MUL_MAT | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
| 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 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
+7793 -6047
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -2,10 +2,16 @@ function(ggml_get_flags CCID CCVER)
set(C_FLAGS "")
set(CXX_FLAGS "")
if (CCID MATCHES "Clang")
if (CCID MATCHES "Clang" OR CCID MATCHES "IntelLLVM")
set(C_FLAGS -Wunreachable-code-break -Wunreachable-code-return)
set(CXX_FLAGS -Wunreachable-code-break -Wunreachable-code-return -Wmissing-prototypes -Wextra-semi)
if (CCID STREQUAL "IntelLLVM")
# for Intel DPC++ and LLVM SYCL, using debug does not disable optimization
list(APPEND C_FLAGS -Rno-debug-disables-optimization)
list(APPEND CXX_FLAGS -Rno-debug-disables-optimization)
endif()
if (
(CCID STREQUAL "Clang" AND CCVER VERSION_GREATER_EQUAL 3.8.0) OR
(CCID STREQUAL "AppleClang" AND CCVER VERSION_GREATER_EQUAL 7.3.0)
+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
+21
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,
@@ -2732,6 +2733,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);
+12 -2
View File
@@ -774,6 +774,13 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
return src_ss[0];
};
auto handle_pool_1d = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) {
return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
}
return src_ss[0];
};
auto handle_flash_attn_ext = [&](const std::vector<ggml_backend_meta_split_state> & src_ss) -> ggml_backend_meta_split_state {
GGML_ASSERT(tensor->src[3] == nullptr || src_ss[3].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED);
@@ -990,12 +997,14 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
case GGML_OP_CONV_3D:
case GGML_OP_CONV_2D_DW:
case GGML_OP_CONV_TRANSPOSE_2D:
case GGML_OP_POOL_1D:
case GGML_OP_POOL_2D:
case GGML_OP_POOL_2D_BACK:
case GGML_OP_UPSCALE: {
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
} break;
case GGML_OP_POOL_1D: {
split_state = handle_pool_1d(src_ss);
} break;
case GGML_OP_PAD: {
split_state = handle_pad(src_ss);
} break;
@@ -1046,7 +1055,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;
+11 -7
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);
@@ -2455,9 +2455,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Q4 && Q8 per group is not support on 310p device
return false;
#endif
#else
// only support contiguous for quantized types.
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
#endif
default:
return false;
}
@@ -2472,9 +2473,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Q4 && Q8 per group is not support on 310p device
return false;
#endif
#else
// only support contiguous for quantized types.
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
#endif
default:
return false;
}
@@ -2641,7 +2643,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Ger is not supported on 310p device
return false;
#endif
#else
switch (op->src[0]->type) {
case GGML_TYPE_F16:
case GGML_TYPE_F32:
@@ -2649,6 +2651,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
default:
return false;
}
#endif
}
case GGML_OP_CONV_TRANSPOSE_1D:
return true;
@@ -2667,7 +2670,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// FA not support on 310p device
return false;
#endif
#else
// derived from [ggml-cuda.cu]
if (op->src[1]->type != GGML_TYPE_F16 || op->src[2]->type != GGML_TYPE_F16) {
return false;
@@ -2693,6 +2696,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
return false;
}
return true;
#endif
}
case GGML_OP_SSM_CONV:
return true;
+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}
+5
View File
@@ -2105,6 +2105,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 +2292,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 :
+109 -10
View File
@@ -1292,24 +1292,37 @@ static void ggml_compute_forward_sum_f32(
}
assert(ggml_is_scalar(dst));
assert(src0->nb[0] == sizeof(float));
GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne)
GGML_TENSOR_LOCALS(size_t, nb0, src0, nb)
ggml_float sum = 0;
ggml_float row_sum = 0;
ggml_float sum = 0;
for (int64_t i03 = 0; i03 < ne03; i03++) {
for (int64_t i02 = 0; i02 < ne02; i02++) {
for (int64_t i01 = 0; i01 < ne01; i01++) {
ggml_vec_sum_f32_ggf(ne00,
&row_sum,
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
sum += row_sum;
if (nb00 == sizeof(float)) {
ggml_float row_sum = 0;
for (int64_t i03 = 0; i03 < ne03; i03++) {
for (int64_t i02 = 0; i02 < ne02; i02++) {
for (int64_t i01 = 0; i01 < ne01; i01++) {
ggml_vec_sum_f32_ggf(ne00,
&row_sum,
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
sum += row_sum;
}
}
}
} else {
for (int64_t i03 = 0; i03 < ne03; i03++) {
for (int64_t i02 = 0; i02 < ne02; i02++) {
for (int64_t i01 = 0; i01 < ne01; i01++) {
const char * row = (const char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
for (int64_t i00 = 0; i00 < ne00; i00++) {
sum += (ggml_float) *(const float *) (row + i00*nb00);
}
}
}
}
}
((float *) dst->data)[0] = sum;
}
@@ -11723,6 +11736,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);
+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;
+100
View File
@@ -401,6 +401,10 @@ void ggml_vec_silu_f32(const int n, float * y, const float * x) {
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, ggml_v_silu(vld1q_f32(x + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(ggml_v_silu(vec_xl(0, x + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
@@ -438,6 +442,10 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, vmulq_f32(ggml_v_silu(vld1q_f32(x + i)), vld1q_f32(g + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(vec_mul(ggml_v_silu(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
@@ -452,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;
@@ -581,6 +675,12 @@ ggml_float ggml_vec_soft_max_f32(const int n, float * y, const float * x, float
vst1q_f32(y + i, val);
sum += (ggml_float)vaddvq_f32(val);
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
float32x4_t v_val = ggml_v_expf(vec_sub(vec_xl(0, x + i), vec_splats(max)));
vec_xst(v_val, 0, y + i);
sum += (ggml_float)vec_hsum_f32x4(v_val);
}
#elif defined(__riscv_v_intrinsic)
vfloat64m1_t vsum = __riscv_vfmv_v_f_f64m1(0, 1);
for (int avl; i < n; i += avl) {
+91 -12
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,60 @@ 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
// the maximum error is 1.45358 plus 0.5 ulps
// numbers above 88.38 will flush to infinity
// numbers beneath -103.97 will flush to zero
inline static float32x4_t ggml_v_expf(float32x4_t v_x) {
const float32x4_t v_r = vec_splats(0x1.8p23f);
const float32x4_t v_z = vec_madd(v_x, vec_splats(0x1.715476p+0f), v_r);
const float32x4_t v_n = vec_sub(v_z, v_r);
const float32x4_t v_b = vec_nmsub(v_n, vec_splats(0x1.7f7d1cp-20f),
vec_nmsub(v_n, vec_splats(0x1.62e4p-1f), v_x));
const uint32x4_t v_e = vec_sl((uint32x4_t)v_z, 23);
const float32x4_t v_k = (float32x4_t)vec_add(v_e, (uint32x4_t)vec_splats(1.0f));
const uint32x4_t v_c = (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(126.0f));
const float32x4_t v_u = vec_mul(v_b, v_b);
const float32x4_t v_j = vec_madd(
vec_madd(vec_madd(vec_splats(0x1.0e4020p-7f), v_b, vec_splats(0x1.573e2ep-5f)), v_u,
vec_madd(vec_splats(0x1.555e66p-3f), v_b, vec_splats(0x1.fffdb6p-2f))),
v_u, vec_mul(vec_splats(0x1.ffffecp-1f), v_b));
if (vec_all_eq(v_c, vec_splats(UINT32_C(0)))) {
return vec_madd(v_j, v_k, v_k);
}
const uint32x4_t v_d = vec_and((uint32x4_t)vec_cmple(v_n, vec_splats(0.0f)), vec_splats(UINT32_C(0x82000000)));
const float32x4_t v_s1 = (float32x4_t)vec_add(v_d, vec_splats(UINT32_C(0x7f000000)));
const float32x4_t v_s2 = (float32x4_t)vec_sub(v_e, v_d);
return vec_sel(vec_sel(vec_madd(v_k, v_j, v_k), vec_mul(vec_madd(v_s2, v_j, v_s2), v_s1), v_c),
vec_mul(v_s1, v_s1), (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(192.0f)));
}
// computes silu x/(1+exp(-x)) in single precision vector
inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
const float32x4_t v_one = vec_splats(1.0f);
const float32x4_t v_zero = vec_splats(0.0f);
const float32x4_t v_neg_x = vec_sub(v_zero, v_x);
const float32x4_t v_exp_neg_x = ggml_v_expf(v_neg_x);
const float32x4_t v_one_plus_exp_neg_x = vec_add(v_one, v_exp_neg_x);
return vec_div(v_x, v_one_plus_exp_neg_x);
}
// 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
@@ -1386,7 +1463,13 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
}
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __riscv_v_intrinsic
// 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) {
for (int i = 0; i < n; ++i) {
@@ -1531,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) {
+2 -2
View File
@@ -1551,11 +1551,11 @@ struct ggml_backend_cuda_context {
// pool
std::unique_ptr<ggml_cuda_pool> pools[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS];
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(int device, int stream_no);
static std::unique_ptr<ggml_cuda_pool> new_pool_for_device(ggml_backend_cuda_context & ctx, int device, int stream_no);
ggml_cuda_pool & pool(int device) {
if (pools[device][curr_stream_no] == nullptr) {
pools[device][curr_stream_no] = new_pool_for_device(device, curr_stream_no);
pools[device][curr_stream_no] = new_pool_for_device(*this, device, curr_stream_no);
}
return *pools[device][curr_stream_no];
}
+24 -7
View File
@@ -145,7 +145,8 @@ static __global__ void dsv4_hc_pre_f32(
dst[i0*sd0 + it*sd1] = scale * sum;
}
template <bool has_comb>
// gated: post = gate_scale_out*sigmoid(gate_scale_in*post), the fused SCALE -> SIGMOID -> SCALE chain of qwen4exp
template <bool has_comb, bool gated>
static __global__ void dsv4_hc_post_f32(
const float * x,
const float * residual,
@@ -167,7 +168,9 @@ static __global__ void dsv4_hc_post_f32(
int64_t sc2,
int64_t sd0,
int64_t sd1,
int64_t sd2) {
int64_t sd2,
float gate_scale_in,
float gate_scale_out) {
ggml_cuda_pdl_lc();
const int64_t ir = (int64_t) blockIdx.x * blockDim.x + threadIdx.x;
const int64_t nr = n_embd * hc * n_tokens;
@@ -182,7 +185,12 @@ static __global__ void dsv4_hc_post_f32(
const int64_t idst = (ir / n_embd) % hc;
const int64_t it = ir / (n_embd * hc);
float sum = x[i0*sx0 + it*sx1] * post[idst*sp0 + it*sp1];
float p = post[idst*sp0 + it*sp1];
if constexpr (gated) {
p = gate_scale_out * (1.0f / (1.0f + expf(-(gate_scale_in * p))));
}
float sum = x[i0*sx0 + it*sx1] * p;
if constexpr (has_comb) {
for (int64_t isrc = 0; isrc < hc; ++isrc) {
sum += residual[i0*sr0 + isrc*sr1 + it*sr2] * comb[idst*sc0 + isrc*sc1 + it*sc2];
@@ -272,12 +280,19 @@ void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst
scale);
}
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
const ggml_tensor * gate_scale_in, const ggml_tensor * gate_scale_out) {
const ggml_tensor * x = dst->src[0];
const ggml_tensor * residual = dst->src[1];
const ggml_tensor * post = dst->src[2];
const ggml_tensor * comb = dst->src[3];
// when fused, read the raw post weights from before the SCALE -> SIGMOID -> SCALE chain
const bool gated = gate_scale_in != nullptr;
const ggml_tensor * post = gated ? gate_scale_in->src[0] : dst->src[2];
const float gate_in = gated ? ggml_get_op_params_f32(gate_scale_in, 0) : 1.0f;
const float gate_out = gated ? ggml_get_op_params_f32(gate_scale_out, 0) : 1.0f;
GGML_ASSERT(x->type == GGML_TYPE_F32);
GGML_ASSERT(residual->type == GGML_TYPE_F32);
GGML_ASSERT(post->type == GGML_TYPE_F32);
@@ -303,7 +318,8 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
const dim3 grid_dims((nr + block_size - 1) / block_size, 1, 1);
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, ctx.stream());
auto kernel = comb ? dsv4_hc_post_f32<true> : dsv4_hc_post_f32<false>;
auto kernel = comb ? (gated ? dsv4_hc_post_f32<true, true> : dsv4_hc_post_f32<true, false>)
: (gated ? dsv4_hc_post_f32<false, true> : dsv4_hc_post_f32<false, false>);
ggml_cuda_kernel_launch(kernel, launch_params,
(const float *) x->data, (const float *) residual->data,
(const float *) post->data, comb ? (const float *) comb->data : nullptr, (float *) dst->data,
@@ -312,5 +328,6 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
nbr0 / sizeof(float), nbr1 / sizeof(float), nbr2 / sizeof(float),
nbp0 / sizeof(float), nbp1 / sizeof(float),
nbc0 / sizeof(float), nbc1 / sizeof(float), nbc2 / sizeof(float),
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float));
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float),
gate_in, gate_out);
}
+3 -1
View File
@@ -3,4 +3,6 @@
void ggml_cuda_op_dsv4_hc_comb(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
// gate_scale_in/gate_scale_out: the SCALE nodes of a fused SCALE -> SIGMOID -> SCALE chain feeding src[2]
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
const ggml_tensor * gate_scale_in = nullptr, const ggml_tensor * gate_scale_out = nullptr);
+1 -1
View File
@@ -955,7 +955,7 @@ static __global__ void flash_attn_tile(
if (ncols2 == 1) {
// Branch with out-of-bounds checks.
int k_VKQ_0 = blockIdx.y*nbatch_fa;
while (k_VKQ_0 < k_VKQ_max - nbatch_fa) {
while (k_VKQ_0 <= k_VKQ_max - nbatch_fa) {
constexpr bool oob_check = false;
flash_attn_tile_iter<warp_size, nwarps, ncols1, ncols2, DKQ, DV, nbatch_fa, nbatch_K, use_logit_softcap, oob_check>
(Q_tmp, K_h2, V_h2, maskh, ne01, logit_softcap, slope, KQ, KV_tmp,
+3 -1
View File
@@ -317,7 +317,7 @@ static __global__ void flash_attn_ext_vec(
#endif // V_DOT2_F32_F16_AVAILABLE
}
ggml_cuda_syncwarp();
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
#pragma unroll
for (int k0 = 0; k0 < WARP_SIZE; k0 += V_cols_per_iter) {
@@ -374,6 +374,8 @@ static __global__ void flash_attn_ext_vec(
}
#endif // V_DOT2_F32_F16_AVAILABLE
}
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
}
if (sinks && blockIdx.y == 0) {
+4 -4
View File
@@ -417,9 +417,7 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
#define FATTN_VEC_CASE(D, type_K_case, type_V_case) \
if constexpr (GGML_CUDA_FA_##type_K_case##_##type_V_case) { \
const bool type_K_okay = type_K == GGML_TYPE_##type_K_case || (type_K == GGML_TYPE_F32 && GGML_TYPE_##type_K_case == GGML_TYPE_F16); \
const bool type_V_okay = type_V == GGML_TYPE_##type_V_case || (type_V == GGML_TYPE_F32 && GGML_TYPE_##type_V_case == GGML_TYPE_F16); \
if (head_size == (D) && type_K_okay && type_V_okay) { \
if (head_size == (D) && type_K == GGML_TYPE_##type_K_case && type_V == GGML_TYPE_##type_V_case) { \
return ggml_cuda_flash_attn_ext_vec_case<D, GGML_TYPE_##type_K_case, GGML_TYPE_##type_V_case>; \
} \
} \
@@ -432,7 +430,9 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
typedef void (* fattn_vec_case_t)(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
// Vector kernel for the given head size and K/V types, nullptr if its template instance was not compiled:
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K, const ggml_type type_V) {
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K_in, const ggml_type type_V_in) {
const ggml_type type_K = type_K_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_K_in;
const ggml_type type_V = type_V_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_V_in;
FATTN_VEC_CASES_ALL_D(F16, F16)
FATTN_VEC_CASES_ALL_D(Q4_0, F16)
FATTN_VEC_CASES_ALL_D(Q4_1, F16)
+2 -1
View File
@@ -4,7 +4,7 @@
#include <climits>
#include <mutex>
namespace {
namespace ggml_cuda_gated_delta_net_mma {
constexpr int D = 128;
using bf16 = nv_bfloat16;
@@ -417,6 +417,7 @@ static gdn_path select_gdn_mma_path(int device, const ggml_cuda_gdn_mma_args & a
#endif // GGML_USE_HIP
}
} // namespace
using namespace ggml_cuda_gated_delta_net_mma;
bool ggml_cuda_gdn_mma_launch(int device, const ggml_cuda_gdn_mma_args & a, cudaStream_t stream) {
if (!a.state_out) {
+145 -23
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>
@@ -457,6 +458,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 +468,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 +491,20 @@ struct ggml_cuda_pool_leg : public ggml_cuda_pool {
}
}
// captured CUDA graphs can still use freed buffers, so they must be captured again
void invalidate_cuda_graphs() {
#ifdef USE_CUDA_GRAPH
int n_captured = 0;
for (const auto & [key, graph] : ctx.cuda_graphs) {
n_captured += graph->instance != nullptr;
}
if (n_captured > 0) {
GGML_LOG_DEBUG(GGML_CUDA_NAME " pool[%d]: memory freed, %d captured CUDA graphs must be captured again\n", device, n_captured);
}
ctx.cuda_graphs.clear();
#endif // USE_CUDA_GRAPH
}
void * alloc(size_t size, size_t * actual_size) override {
#ifdef DEBUG_CUDA_MALLOC
int nnz = 0;
@@ -538,6 +555,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 +584,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 +739,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
@@ -2014,6 +2034,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 +2054,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 +2072,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 +2097,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 +2571,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;
}
@@ -3029,6 +3055,65 @@ static int ggml_cuda_try_ssm_scan_cache_fusion(
return skip;
}
// expert group selection from build_moe_ffn (n_expert_groups > 1)
// src0/src1 index into this table, -1 is the (biased) probs, -2 is not checked
static const struct {
ggml_op op;
int src0;
int src1;
} topk_moe_group_pattern[] = {
{ GGML_OP_RESHAPE, -1, -2 }, // 0: [n_exp_per_group, n_expert_groups, n_tokens]
{ GGML_OP_RESHAPE, 0, -2 }, // 1: [1, n_exp_per_group, n_expert_groups, n_tokens]
{ GGML_OP_ARGSORT, 0, -2 }, // 2
{ GGML_OP_VIEW, 2, -2 }, // 3: top-2 per group
{ GGML_OP_GET_ROWS, 1, 3 }, // 4
{ GGML_OP_RESHAPE, 4, -2 }, // 5
{ GGML_OP_SUM_ROWS, 5, -2 }, // 6: group scores
{ GGML_OP_RESHAPE, 6, -2 }, // 7
{ GGML_OP_ARGSORT, 7, -2 }, // 8
{ GGML_OP_VIEW, 8, -2 }, // 9: top n_group_used groups
{ GGML_OP_GET_ROWS, 0, 9 }, // 10
{ GGML_OP_FILL, 0, -2 }, // 11
{ GGML_OP_SET_ROWS, 10, 9 }, // 12: unused groups -> -inf
{ GGML_OP_RESHAPE, 12, -2 }, // 13: [n_expert, n_tokens]
};
static bool ggml_cuda_topk_moe_group_fusion(const struct ggml_cgraph * cgraph, int node_idx, const ggml_tensor * selection, ggml_cuda_topk_moe_args & args) {
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
GGML_UNUSED_VARS(cgraph, node_idx, selection, args);
return false;
#else
const int n_ops = (int) std::size(topk_moe_group_pattern);
if (node_idx + n_ops > cgraph->n_nodes) {
return false;
}
ggml_tensor ** nodes = cgraph->nodes + node_idx;
auto src_ok = [&](const ggml_tensor * src, int idx) {
return idx == -2 || src == (idx == -1 ? selection : nodes[idx]);
};
for (int i = 0; i < n_ops; i++) {
const auto & p = topk_moe_group_pattern[i];
if (nodes[i]->op != p.op || !src_ok(nodes[i]->src[0], p.src0) || !src_ok(nodes[i]->src[1], p.src1)) {
return false;
}
}
const int n_groups = nodes[0]->ne[1];
const int n_group_used = nodes[9]->ne[0];
if (nodes[3]->ne[0] != 2 || nodes[12]->src[2] != nodes[11] || ggml_get_op_params_f32(nodes[11], 0) != -INFINITY ||
n_groups > WARP_SIZE) {
return false;
}
args.n_groups = n_groups;
args.n_group_used = n_group_used;
return true;
#endif // defined(GGML_USE_HIP) && HIP_VERSION < 60300000
}
static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int node_idx, ggml_cuda_topk_moe_args & args) {
args.sigmoid = false;
args.sqrt_softplus = false;
@@ -3036,6 +3121,8 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
args.delayed_softmax = false;
args.prob_bias = false;
args.norm = false;
args.n_groups = 0;
args.n_group_used = 0;
const int n_nodes = cgraph->n_nodes;
ggml_tensor ** nodes = cgraph->nodes;
@@ -3078,18 +3165,20 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
}
// src of bias add is the unreshaped probs (-2 instead of -1)
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == nodes[node_idx - 2]) {
const ggml_tensor * selection = nodes[node_idx - 2];
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == selection) {
args.prob_bias = true;
selection = nodes[node_idx];
node_idx++;
}
// RESHAPE/ADD -> ARGSORT
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT) {
return false;
if (ggml_cuda_topk_moe_group_fusion(cgraph, node_idx, selection, args)) {
node_idx += (int) std::size(topk_moe_group_pattern);
selection = nodes[node_idx - 1];
}
if (args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
return false;
} else if (!args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 2]) {
// RESHAPE/ADD/group mask -> ARGSORT
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT || nodes[node_idx]->src[0] != selection) {
return false;
}
@@ -3100,6 +3189,11 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
return false;
}
// kept groups must hold all used experts
if (args.n_groups > 0 && nodes[node_idx]->ne[0] > args.n_group_used * (probs_reshaped->ne[1] / args.n_groups)) {
return false;
}
node_idx++;
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_GET_ROWS) {
@@ -3482,6 +3576,26 @@ static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph,
}
}
// scale_out*sigmoid(scale_in*post) as the hc_post weights (qwen4exp hc_combine)
std::initializer_list<enum ggml_op> hc_post_gate_ops = { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST };
if (is_equal(hc_post_gate_ops, ops) && ggml_can_fuse_subgraph(cgraph, node_idx, ops, { node_idx + 3 })) {
const ggml_tensor * scale_in = cgraph->nodes[node_idx];
const ggml_tensor * sigmoid = cgraph->nodes[node_idx + 1];
const ggml_tensor * scale_out = cgraph->nodes[node_idx + 2];
// the chain must feed hc_post->src[2]; a bias on either scale is not handled
if (ggml_check_edges(cgraph, node_idx, {{1, 0, 0}, {2, 0, 1}, {3, 2, 2}}) &&
ggml_get_unary_op(sigmoid) == GGML_UNARY_OP_SIGMOID &&
ggml_get_op_params_f32(scale_in, 1) == 0.0f && ggml_get_op_params_f32(scale_out, 1) == 0.0f &&
scale_in->src[0]->type == GGML_TYPE_F32 && scale_out->type == GGML_TYPE_F32 &&
ggml_are_same_shape(scale_in->src[0], scale_out)) {
int out_nodes[] = { node_idx + 3 };
return ggml_cuda_check_fusion_memory_ranges(cgraph, node_idx, (int)ops.size(), out_nodes, 1);
}
return false;
}
if (!ggml_can_fuse(cgraph, node_idx, ops)) {
return false;
}
@@ -3754,17 +3868,18 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
} else {
ops.insert(ops.end(), { GGML_OP_SOFT_MAX });
}
const int i_probs = i + (int) ops.size() - 1; // last node of the gating activation
ops.push_back(GGML_OP_RESHAPE);
if (args.prob_bias) {
bias = cgraph->nodes[i_probs + 2]->src[1];
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ADD, GGML_OP_ARGSORT, GGML_OP_VIEW,
GGML_OP_GET_ROWS });
out_nodes[0] = i_probs + 4;
} else {
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
out_nodes[0] = i_probs + 3;
ops.push_back(GGML_OP_ADD);
bias = cgraph->nodes[i + ops.size() - 1]->src[1];
}
if (args.n_groups > 0) {
for (const auto & p : topk_moe_group_pattern) {
ops.push_back(p.op);
}
}
ops.insert(ops.end(), { GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
out_nodes[0] = i + ops.size() - 2;
ids = cgraph->nodes[out_nodes[0]];
if (args.norm) {
@@ -4444,6 +4559,11 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
return 1;
}
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST }, { GGML_UNARY_OP_SIGMOID })) {
ggml_cuda_op_dsv4_hc_post(*cuda_ctx, cgraph->nodes[i + 3], node, cgraph->nodes[i + 2]);
return 3;
}
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE }, { GGML_UNARY_OP_TANH })) {
ggml_cuda_op_softcap(*cuda_ctx, cgraph->nodes[i + 2], node);
return 2;
@@ -5828,7 +5948,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
//return ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous_rows(op->src[1]);
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
case GGML_OP_SUM:
return ggml_is_contiguous_rows(op->src[0]);
return ggml_is_contiguously_allocated(op->src[0]);
case GGML_OP_TOP_K:
return op->src[0]->ne[0] <= INT_MAX;
case GGML_OP_ARGSORT:
@@ -5887,6 +6007,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;
+20
View File
@@ -46,6 +46,25 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
for (int j = 0; j < 2; ++j) {
const int q = qxi[j];
#if defined(GGML_USE_HIP)
const uint32_t q_bits = (uint16_t) q;
// Expand each group of four Q1_0 bits into byte selectors for v_perm_b32.
const uint32_t q0_bits = q_bits >> 0;
const uint32_t q1_bits = q_bits >> 4;
const uint32_t q2_bits = q_bits >> 8;
const uint32_t q3_bits = q_bits >> 12;
const uint32_t q0_indices = (q0_bits & 0x01) | ((q0_bits & 0x02) << 7) | ((q0_bits & 0x04) << 14) | ((q0_bits & 0x08) << 21);
const uint32_t q1_indices = (q1_bits & 0x01) | ((q1_bits & 0x02) << 7) | ((q1_bits & 0x04) << 14) | ((q1_bits & 0x08) << 21);
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
#else
// unpack crumbs into nibble indices
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
@@ -59,6 +78,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
const int v1 = __byte_perm(s0, s1, 0x7632);
const int v2 = __byte_perm(s2, s3, 0x5410);
const int v3 = __byte_perm(s2, s3, 0x7632);
#endif // defined(GGML_USE_HIP)
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
x_qs[i*sram_stride + dst_offset + j*4+0] = v0;
+3 -1
View File
@@ -1441,10 +1441,12 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
const int ntiles_dst = ntx * nty * ntzw;
const int tiles_nwaves = (ntiles_dst + nsm - 1) / nsm;
const int tiles_efficiency_percent = 100 * ntiles_dst / (nsm*tiles_nwaves);
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : nsm, 1, 1);
GGML_ASSERT(ntiles_dst * blocks_per_ne00_fd.z < (1 << 30)); // Assert that variable kbc will not overflow.
const int nwork = ntiles_dst * ((args.ncols_x + config.K_vram - 1) / config.K_vram); // Maximum number of CUDA blocks that can do useful work.
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : std::min(nsm, nwork), 1, 1);
const bool fixup_needed = ntiles_dst % block_nums_stream_k.x != 0;
ggml_cuda_pool & pool = ctx.pool(id);
+29 -6
View File
@@ -468,10 +468,12 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
} else if (table_id == MMVQ_PARAMETERS_GCN) {
switch (ncols_dst) {
case 1:
return 2;
case 2:
case 3:
return type == GGML_TYPE_Q3_K ? 1 : 2;
case 4:
return 2;
return type == GGML_TYPE_Q6_K? 1 : 2;
case 5:
case 6:
case 7:
@@ -576,11 +578,31 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
return 1;
}
static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
// Low bit types load few weight bytes per thread, a second row per block shares the q8_1 loads and adds memory parallelism.
static constexpr __host__ __device__ bool mmvq_two_rows_ncols1(ggml_type type) {
switch (type) {
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_K:
case GGML_TYPE_Q3_K:
case GGML_TYPE_IQ1_S:
case GGML_TYPE_IQ1_M:
return true;
default:
return false;
}
}
static constexpr __host__ __device__ int calc_rows_per_block(ggml_type type, int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
if (table_id == MMVQ_PARAMETERS_GENERIC || table_id == MMVQ_PARAMETERS_GCN || table_id == MMVQ_PARAMETERS_TURING || table_id == MMVQ_PARAMETERS_GB10) {
switch (ncols_dst) {
case 1:
return small_k ? nwarps : 1;
if (small_k) {
return nwarps;
}
if (table_id == MMVQ_PARAMETERS_GB10) {
return type == GGML_TYPE_IQ2_S || type == GGML_TYPE_IQ3_S ? 2 : 1;
}
return table_id == MMVQ_PARAMETERS_GENERIC && mmvq_two_rows_ncols1(type) ? 2 : 1;
case 2:
case 3:
case 4:
@@ -615,7 +637,7 @@ static __global__ void mul_mat_vec_q(
constexpr int vdr = get_vdr_mmvq(type);
constexpr mmvq_parameter_table_id table_id = get_device_table_id();
constexpr int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
constexpr int rows_per_cuda_block = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
constexpr int rows_per_cuda_block = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type);
@@ -727,7 +749,8 @@ static __global__ void mul_mat_vec_q(
#if __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
// start the next iterations' weight loads early
if constexpr (mmvq_should_prefetch(type)) {
constexpr int pf_dist = 2; // loop iterations, not blocks
// loop iterations, not blocks; q3_K and q6_K keep the distance in blocks they had with vdr 1
constexpr int pf_dist = type == GGML_TYPE_Q3_K || type == GGML_TYPE_Q6_K ? 1 : 2;
const int kbx_pf = kbx + pf_dist*blocks_per_iter;
if (kbx_pf < blocks_per_row_x) {
#pragma unroll
@@ -1013,7 +1036,7 @@ static std::pair<dim3, dim3> calc_launch_params(
const int ncols_dst, const int nrows_x, const int nchannels_dst, const int nsamples_or_ntokens,
const int warp_size, const mmvq_parameter_table_id table_id, const bool small_k = false, const bool halve_iters = false) {
const int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
const int rpb = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
const int rpb = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
const int64_t nblocks = (nrows_x + rpb - 1) / rpb;
const dim3 block_nums(nblocks, nchannels_dst, nsamples_or_ntokens);
const dim3 block_dims(warp_size, nwarps, 1);
+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);
+66
View File
@@ -11,6 +11,8 @@ struct topk_moe_config {
bool use_sqrt_softplus;
bool with_norm;
bool delayed_softmax;
int n_groups;
int n_group_used;
};
// Warp-local softmax used for both the pre-top-k logits and the post-top-k delayed path.
@@ -78,6 +80,53 @@ __device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], co
}
}
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
// Set -inf for experts outside the top n_group_used groups. Group score is the sum of its top-2 experts (DeepSeek V3).
template <int n_experts, int experts_per_thread>
__device__ void group_mask_warp_inplace(float (&vals)[experts_per_thread], const int n_groups, const int n_group_used, const int lane) {
const int n_per_group = n_experts / n_groups;
float score = -INFINITY; // lane g holds the score of group g
for (int g = 0; g < n_groups; g++) {
float top1 = -INFINITY;
float top2 = -INFINITY;
#pragma unroll
for (int i = 0; i < experts_per_thread; i++) {
if ((i * WARP_SIZE + lane) / n_per_group == g) {
top2 = fmaxf(top2, fminf(top1, vals[i]));
top1 = fmaxf(top1, vals[i]);
}
}
#pragma unroll
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
const float o1 = __shfl_xor_sync(0xFFFFFFFF, top1, offset, WARP_SIZE);
const float o2 = __shfl_xor_sync(0xFFFFFFFF, top2, offset, WARP_SIZE);
top2 = fmaxf(fminf(top1, o1), fmaxf(top2, o2));
top1 = fmaxf(top1, o1);
}
if (lane == g) {
score = top1 + top2;
}
}
// rank of each group, lower index wins ties
int rank = 0;
for (int g = 0; g < n_groups; g++) {
const float other = __shfl_sync(0xFFFFFFFF, score, g, WARP_SIZE);
rank += other > score || (other == score && g < lane);
}
const auto keep = __ballot_sync(0xFFFFFFFFULL, lane < n_groups && rank < n_group_used);
#pragma unroll
for (int i = 0; i < experts_per_thread; i++) {
const int g = (i * WARP_SIZE + lane) / n_per_group;
if (g >= n_groups || !((keep >> g) & 1)) {
vals[i] = -INFINITY;
}
}
}
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
/*
This kernel does the following:
1. optionally softmax over the logits per token [n_experts, n_tokens]
@@ -171,6 +220,17 @@ __global__ void topk_moe_cuda(const float * logits,
}
}
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
// without bias, masking wt is safe: the matcher ensures the kept groups hold all used experts
if (config.n_groups > 0) {
if constexpr (has_bias) {
group_mask_warp_inplace<n_experts>(selection_wt, config.n_groups, config.n_group_used, threadIdx.x);
} else {
group_mask_warp_inplace<n_experts>(wt, config.n_groups, config.n_group_used, threadIdx.x);
}
}
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
//at this point, each thread holds either a portion of the softmax distribution
//or the raw logits. We do the argmax reduce over n_expert_used, each time marking
//the expert weight as -inf to exclude from the next iteration
@@ -297,6 +357,10 @@ static void launch_topk_moe_cuda(ggml_backend_cuda_context & ctx,
cudaStream_t stream = ctx.stream();
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, stream);
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
GGML_ASSERT(config.n_groups == 0 && "expert groups fusion is not supported on this HIP version");
#endif
switch (n_expert) {
case 1:
ggml_cuda_kernel_launch(topk_moe_cuda<1, has_bias>, launch_params,
@@ -389,6 +453,8 @@ void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
config.use_sqrt_softplus = args.sqrt_softplus;
config.with_norm = with_norm;
config.delayed_softmax = args.delayed_softmax;
config.n_groups = args.n_groups;
config.n_group_used = args.n_group_used;
if (bias) {
launch_topk_moe_cuda<true>(ctx, logits_d, weights_d, ids_d, bias_d, n_rows, n_experts, n_expert_used, clamp_val,
+2
View File
@@ -14,6 +14,8 @@ struct ggml_cuda_topk_moe_args {
bool prob_bias{};
bool norm{};
bool scale{};
int n_groups{}; // > 0: pick experts only from the top n_group_used expert groups
int n_group_used{};
};
void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
+116 -53
View File
@@ -360,12 +360,12 @@ static __device__ __forceinline__ float vec_dot_nvfp4_q8_1(
return sum;
}
#define VDR_Q2_K_Q8_1_MMVQ 1
#define VDR_Q2_K_Q8_1_MMVQ 2
#define VDR_Q2_K_Q8_1_MMQ 4
// contiguous v/x values
static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq(
const int & v, const int * __restrict__ u, const uint8_t * __restrict__ scales,
const int * __restrict__ v, const int * __restrict__ u, const int * __restrict__ scales,
const half2 & dm2, const float * __restrict__ d8) {
float sumf_d = 0.0f;
@@ -373,17 +373,20 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq(
#pragma unroll
for (int i = 0; i < QR2_K; ++i) {
const int sc = scales[2*i];
const int sc = scales[i/2] >> (16*(i%2));
const int vi = (v >> (2*i)) & 0x03030303;
int sumi_d = 0;
int sumi_m = 0;
#pragma unroll
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
const int vi = (v[j] >> (2*i)) & 0x03030303;
sumf_d += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * (sc & 0xF)); // SIMD dot product
sumi_d = ggml_cuda_dp4a(vi, u[VDR_Q2_K_Q8_1_MMVQ*i + j], sumi_d); // SIMD dot product
sumi_m = ggml_cuda_dp4a(0x01010101, u[VDR_Q2_K_Q8_1_MMVQ*i + j], sumi_m); // sum of q8_1 values
}
// fill int with 4x m
int m = sc >> 4;
m |= m << 8;
m |= m << 16;
sumf_m += d8[i] * ggml_cuda_dp4a(m, u[i], 0); // multiply constant q2_K part with sum of q8_1 values
sumf_d += d8[i] * (sumi_d * (sc & 0xF));
sumf_m += d8[i] * (sumi_m * ((sc >> 4) & 0xF)); // multiply constant q2_K part with sum of q8_1 values
}
const float2 dm2f = __half22float2(dm2);
@@ -443,37 +446,35 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmq(
return sumf + d8*sumf_d8;
}
#define VDR_Q3_K_Q8_1_MMVQ 1
#define VDR_Q3_K_Q8_1_MMVQ 2
#define VDR_Q3_K_Q8_1_MMQ 2
// contiguous v/x values
static __device__ __forceinline__ float vec_dot_q3_K_q8_1_impl_mmvq(
const int & vl, const int & vh, const int * __restrict__ u, const uint8_t * __restrict__ scales,
const int & scale_offset, const float & d3, const float * __restrict__ d8) {
const int * __restrict__ vl, const int * __restrict__ vh, const int * __restrict__ u, const int * __restrict__ scales,
const float & d3, const float * __restrict__ d8) {
float sumf = 0.0f;
#pragma unroll
for (int i = 0; i < QR3_K; ++i) {
const int isc = scale_offset + 2*i;
// 4 low bits from scales[0..7], 2 high bits from scales[8..11]
const int sc_low = (scales[i/2] >> (16*(i%2))) & 0x0F;
const int sc_high = (scales[2] >> (16*(i%2) + 2*(i/2))) & 0x03;
const int sc = (sc_low | (sc_high << 4)) - 32;
const int isc_low = isc % (QK_K/32);
const int sc_shift_low = 4 * (isc / (QK_K/32));
const int sc_low = (scales[isc_low] >> sc_shift_low) & 0xF;
int sumi = 0;
#pragma unroll
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
const int vil = (vl[j] >> (2*i)) & 0x03030303;
const int vih = ((vh[j] >> i) << 2) & 0x04040404;
const int isc_high = isc % (QK_K/64);
const int sc_shift_high = 2 * (isc / (QK_K/64));
const int sc_high = ((scales[(QK_K/32) + isc_high] >> sc_shift_high) & 3) << 4;
// (vil | vih) - 4 for each byte, the - 4 is done by the second dp4a
sumi = ggml_cuda_dp4a(vil | vih, u[VDR_Q3_K_Q8_1_MMVQ*i + j], sumi); // SIMD dot product
sumi = ggml_cuda_dp4a(0xFCFCFCFC, u[VDR_Q3_K_Q8_1_MMVQ*i + j], sumi);
}
const int sc = (sc_low | sc_high) - 32;
const int vil = (vl >> (2*i)) & 0x03030303;
const int vih = ((vh >> i) << 2) & 0x04040404;
const int vi = __vsub4(vil, vih);
sumf += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * sc); // SIMD dot product
sumf += d8[i] * (sumi * sc);
}
return d3 * sumf;
@@ -620,12 +621,12 @@ static __device__ __forceinline__ float vec_dot_q5_K_q8_1_impl_mmq(
return dm4f.x*sumf_d - dm4f.y*sumf_m;
}
#define VDR_Q6_K_Q8_1_MMVQ 1
#define VDR_Q6_K_Q8_1_MMVQ 2
#define VDR_Q6_K_Q8_1_MMQ 8
// contiguous v/x values
static __device__ __forceinline__ float vec_dot_q6_K_q8_1_impl_mmvq(
const int & vl, const int & vh, const int * __restrict__ u, const int8_t * __restrict__ scales,
const int * __restrict__ vl, const int * __restrict__ vh, const int * __restrict__ u, const int8_t * __restrict__ scales,
const float & d, const float * __restrict__ d8) {
float sumf = 0.0f;
@@ -634,13 +635,18 @@ static __device__ __forceinline__ float vec_dot_q6_K_q8_1_impl_mmvq(
for (int i = 0; i < QR6_K; ++i) {
const int sc = scales[4*i];
const int vil = (vl >> (4*i)) & 0x0F0F0F0F;
int sumi = 0;
#pragma unroll
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
const int vil = (vl[j] >> (4*i)) & 0x0F0F0F0F;
const int vih = ((vh[j] >> (4*i)) << 4) & 0x30303030;
const int vih = ((vh >> (4*i)) << 4) & 0x30303030;
// (vil | vih) - 32 for each byte, the - 32 is done by the second dp4a
sumi = ggml_cuda_dp4a(vil | vih, u[VDR_Q6_K_Q8_1_MMVQ*i + j], sumi); // SIMD dot product
sumi = ggml_cuda_dp4a(0xE0E0E0E0, u[VDR_Q6_K_Q8_1_MMVQ*i + j], sumi);
}
const int vi = __vsub4((vil | vih), 0x20202020); // vi = (vil | vih) - 32
sumf += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * sc); // SIMD dot product
sumf += d8[i] * (sumi * sc);
}
return d*sumf;
@@ -700,6 +706,25 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
const int u2 = get_int_b4(bq8_1_chunk->qs, j*4+2);
const int u3 = get_int_b4(bq8_1_chunk->qs, j*4+3);
#if defined(GGML_USE_HIP)
const uint32_t q_bits = (uint16_t) q;
// Expand each group of four Q1_0 bits into byte selectors for v_perm_b32.
const uint32_t q0_bits = q_bits >> 0;
const uint32_t q1_bits = q_bits >> 4;
const uint32_t q2_bits = q_bits >> 8;
const uint32_t q3_bits = q_bits >> 12;
const uint32_t q0_indices = (q0_bits & 0x01) | ((q0_bits & 0x02) << 7) | ((q0_bits & 0x04) << 14) | ((q0_bits & 0x08) << 21);
const uint32_t q1_indices = (q1_bits & 0x01) | ((q1_bits & 0x02) << 7) | ((q1_bits & 0x04) << 14) | ((q1_bits & 0x08) << 21);
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
#else
// unpack crumbs into nibble indices
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
@@ -713,6 +738,7 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
const int v1 = __byte_perm(s0, s1, 0x7632);
const int v2 = __byte_perm(s2, s3, 0x5410);
const int v3 = __byte_perm(s2, s3, 0x7632);
#endif // defined(GGML_USE_HIP)
sumi = ggml_cuda_dp4a(v0, u0, sumi);
sumi = ggml_cuda_dp4a(v1, u1, sumi);
@@ -870,18 +896,31 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1(
const block_q2_K * bq2_K = (const block_q2_K *) vbq + kbx;
// iqs is in 0,2..14, each 32 bytes of qs hold 4 q8_1 blocks (one per 2 bit shift)
const int bq8_offset = QR2_K * (iqs / QI8_1);
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
const int iqs8 = iqs % QI8_1;
const uint8_t * scales = bq2_K->scales + scale_offset;
int v[VDR_Q2_K_Q8_1_MMVQ];
#pragma unroll
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
v[j] = get_int_b4(bq2_K->qs, iqs + j);
}
const int v = get_int_b4(bq2_K->qs, iqs);
int u[QR2_K];
// scale of shift i is byte 2*i + (iqs8 >= QI8_1/2) of the 8 bytes for this half
const int sc_shift = 8 * (iqs8 / (QI8_1/2));
int scales[2];
scales[0] = get_int_b4(bq2_K->scales, 2*(iqs / QI8_1) + 0) >> sc_shift;
scales[1] = get_int_b4(bq2_K->scales, 2*(iqs / QI8_1) + 1) >> sc_shift;
int u[VDR_Q2_K_Q8_1_MMVQ*QR2_K];
float d8[QR2_K];
#pragma unroll
for (int i = 0; i < QR2_K; ++ i) {
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
for (int i = 0; i < QR2_K; ++i) {
#pragma unroll
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
u[VDR_Q2_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs8 + j);
}
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
}
@@ -893,26 +932,42 @@ static __device__ __forceinline__ float vec_dot_q3_K_q8_1(
const block_q3_K * bq3_K = (const block_q3_K *) vbq + kbx;
// iqs is in 0,2..14, each 32 bytes of qs hold 4 q8_1 blocks (one per 2 bit shift)
const int bq8_offset = QR3_K * (iqs / (QI3_K/2));
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
const int iqs8 = iqs % QI8_1;
const float d = bq3_K->d;
const int vl = get_int_b2(bq3_K->qs, iqs);
int vl[VDR_Q3_K_Q8_1_MMVQ];
int vh[VDR_Q3_K_Q8_1_MMVQ];
#pragma unroll
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
vl[j] = get_int_b2(bq3_K->qs, iqs + j);
vh[j] = get_int_b2(bq3_K->hmask, iqs8 + j) >> bq8_offset;
}
// invert the mask with ~ so that a 0/1 results in 4/0 being subtracted
const int vh = ~get_int_b2(bq3_K->hmask, iqs % (QI3_K/2)) >> bq8_offset;
// sub-block 8*(iqs/8) + 2*i + (iqs8 >= QI8_1/2) for shift i:
// 4 low bits in nibble iqs/8 of bytes, 2 high bits at bit 4*(iqs/8) + 2*(i/2) of bytes 8..11
const int sc_shift = 8 * (iqs8 / (QI8_1/2)) + 4 * (iqs / (QI3_K/2));
int scales[3];
#pragma unroll
for (int l = 0; l < 3; ++l) {
scales[l] = get_int_b2(bq3_K->scales, l) >> sc_shift;
}
int u[QR3_K];
int u[VDR_Q3_K_Q8_1_MMVQ*QR3_K];
float d8[QR3_K];
#pragma unroll
for (int i = 0; i < QR3_K; ++i) {
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
#pragma unroll
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
u[VDR_Q3_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs8 + j);
}
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
}
return vec_dot_q3_K_q8_1_impl_mmvq(vl, vh, u, bq3_K->scales, scale_offset, d, d8);
return vec_dot_q3_K_q8_1_impl_mmvq(vl, vh, u, scales, d, d8);
}
static __device__ __forceinline__ float vec_dot_q4_K_q8_1(
@@ -1025,17 +1080,25 @@ static __device__ __forceinline__ float vec_dot_q6_K_q8_1(
const int scale_offset = (QI6_K/4) * (iqs / (QI6_K/2)) + (iqs % (QI6_K/2)) / (QI6_K/8);
const int vh_shift = 2 * ((iqs % (QI6_K/2)) / (QI6_K/4));
const int vl = get_int_b2(bq6_K->ql, iqs);
const int vh = get_int_b2(bq6_K->qh, (QI6_K/4) * (iqs / (QI6_K/2)) + iqs % (QI6_K/4)) >> vh_shift;
int vl[VDR_Q6_K_Q8_1_MMVQ];
int vh[VDR_Q6_K_Q8_1_MMVQ];
#pragma unroll
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
vl[j] = get_int_b2(bq6_K->ql, iqs + j);
vh[j] = get_int_b2(bq6_K->qh, (QI6_K/4) * (iqs / (QI6_K/2)) + iqs % (QI6_K/4) + j) >> vh_shift;
}
const int8_t * scales = bq6_K->scales + scale_offset;
int u[QR6_K];
int u[VDR_Q6_K_Q8_1_MMVQ*QR6_K];
float d8[QR6_K];
#pragma unroll
for (int i = 0; i < QR6_K; ++i) {
u[i] = get_int_b4(bq8_1[bq8_offset + 2*i].qs, iqs % QI8_1);
#pragma unroll
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
u[VDR_Q6_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + 2*i].qs, iqs % QI8_1 + j);
}
d8[i] = __low2float(bq8_1[bq8_offset + 2*i].ds);
}
+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;
+11 -1
View File
@@ -21,6 +21,7 @@ message(STATUS "hexagon: using ${HEXAGON_SDK_ROOT} and ${HEXAGON_TOOLS_ROOT} for
include(${HEXAGON_SDK_ROOT}/build/cmake/hexagon_fun.cmake)
include(ExternalProject)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
option(GGML_HEXAGON_HTP_DEBUG "ggml-hexagon: enable HTP debug output" OFF)
set(GGML_HEXAGON_HTP_BUILD_TYPE "Release" CACHE STRING "ggml-hexagon: HTP skel build type (Release, RelWithDebInfo, Debug)")
@@ -50,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
@@ -74,7 +82,8 @@ function(build_htp_skel V)
-DHEXAGON_TOOLS_ROOT=${HEXAGON_TOOLS_ROOT}
-DHEXAGON_HTP_DEBUG=${GGML_HEXAGON_HTP_DEBUG}
-DDSP_VERSION=${V}
-DPREBUILT_LIB_DIR="toolv19_${V}")
-DPython3_EXECUTABLE=${Python3_EXECUTABLE}
-DPREBUILT_LIB_DIR="toolv21_${V}")
list(APPEND HTP_SKELS ${CMAKE_CURRENT_BINARY_DIR}/libggml-htp-${V}.so)
list(APPEND HTP_PROJECTS htp-${V})
set(HTP_SKELS ${HTP_SKELS} PARENT_SCOPE)
@@ -85,6 +94,7 @@ build_htp_skel(v73)
build_htp_skel(v75)
build_htp_skel(v79)
build_htp_skel(v81)
build_htp_skel(v85)
# Install Hexagon skels required at runtime
install(FILES ${HTP_SKELS} TYPE LIB)
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_head_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
+650
View File
@@ -0,0 +1,650 @@
#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.src0();
const ggml_tensor * src1 = prev.src1();
const ggml_tensor * dst = cur.dst();
if (!ggml_are_same_shape(prev.dst(), dst)) 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) ||
orig_kparams->collapse != kparams.collapse) {
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 };
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 = prev.inputs[curr_n];
const ggml_tensor * w_in = cur.src0();
const ggml_tensor * x_in = cur.src1();
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 || 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 = orig_kparams->collapse && htp_matmul_can_collapse(w_in, x, d_in);
if ((x->ne[2] > 1 || x->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;
if (collapse) {
const ggml_tensor x_collapsed = htp_tensor_collapse_rows(x);
if (!htp_kparams_solve_mmnx(sess, w0, &x_collapsed, curr_n + 1, &kparams)) return false;
kparams.collapse = 1;
} else if (!htp_kparams_solve_mmnx(sess, w0, x, 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(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;
struct htp_glu_kernel_params kparams;
if (!htp_kparams_solve_unary_mul_glu(sess, glu_op, gate, up, dst, &kparams)) return false;
prev.opcode = glu_op;
prev.inputs = { gate, up };
prev.outputs = { dst };
prev.fused.push_back(cur.node);
memset(prev.params, 0, sizeof(prev.params));
prev.set_kparams(kparams);
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
File diff suppressed because it is too large Load Diff
+123
View File
@@ -0,0 +1,123 @@
#ifndef HTP_KPARAMS_H
#define HTP_KPARAMS_H
#include "htp-common.h"
#include "htp/allreduce-ops.h"
#include "htp/argsort-ops.h"
#include "htp/binary-ops.h"
#include "htp/concat-ops.h"
#include "htp/cpy-ops.h"
#include "htp/cumsum-ops.h"
#include "htp/diag-ops.h"
#include "htp/fill-ops.h"
#include "htp/flash-attn-ops.h"
#include "htp/gated-delta-net-ops.h"
#include "htp/get-rows-ops.h"
#include "htp/glu-ops.h"
#include "htp/im2col-ops.h"
#include "htp/matmul-ops.h"
#include "htp/pad-ops.h"
#include "htp/pool-ops.h"
#include "htp/repeat-ops.h"
#include "htp/roll-ops.h"
#include "htp/rope-ops.h"
#include "htp/set-rows-ops.h"
#include "htp/softmax-ops.h"
#include "htp/solve-tri-ops.h"
#include "htp/ssm-conv.h"
#include "htp/sum-rows-ops.h"
#include "htp/unary-ops.h"
#include <type_traits>
#include <utility>
bool htp_rows_stride(const int64_t * ne, const size_t * nb, size_t * stride);
bool htp_matmul_can_collapse(const struct ggml_tensor * src0, const struct ggml_tensor * src1, const struct ggml_tensor * dst);
ggml_tensor htp_tensor_collapse_rows(const struct ggml_tensor * t);
template <typename... Args>
auto htp_kparams_type(bool (*)(Args...)) -> std::remove_pointer_t<std::remove_reference_t<decltype((std::declval<Args>(), ...))>>;
template <auto SolveFn, typename... Args>
static inline bool htp_kparams_check(Args &&... args) {
decltype(htp_kparams_type(SolveFn)) kparams;
return SolveFn(std::forward<Args>(args)..., &kparams);
}
bool htp_kparams_solve(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * op,
void * kparams = nullptr
);
static inline bool htp_kparams_check(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * op
) {
return htp_kparams_solve(sess, op, nullptr);
}
bool htp_kparams_solve_allreduce(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * dst,
uint32_t rank,
uint32_t n_ranks,
bool has_add,
bool is_row_bcast,
bool is_shard_ok,
struct htp_allreduce_kernel_params * kparams
);
bool htp_kparams_solve_rms_norm_mul(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * dst,
struct htp_unary_kernel_params * kparams
);
bool htp_kparams_solve_matmul_add(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * src2,
const struct ggml_tensor * dst,
struct htp_mm_kernel_params * kparams
);
bool htp_kparams_solve_mmnx(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
const struct ggml_tensor * act,
int32_t n_weights,
struct htp_mm_kernel_params * kparams
);
bool htp_kparams_solve_mmidnx(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
const struct ggml_tensor * act,
const struct ggml_tensor * dst,
int32_t n_weights,
struct htp_mm_kernel_params * kparams
);
bool htp_kparams_solve_repeat_get_rows(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * dst,
struct htp_get_rows_kernel_params * kparams
);
bool htp_kparams_solve_unary_mul_glu(
const struct ggml_hexagon_session * sess,
uint32_t op,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * dst,
struct htp_glu_kernel_params * kparams
);
#endif // HTP_KPARAMS_H
+260
View File
@@ -0,0 +1,260 @@
#include "htp-opnode.h"
#include "htp/allreduce-ops.h"
#include "htp/argsort-ops.h"
#include "htp/binary-ops.h"
#include "htp/cumsum-ops.h"
#include "htp/diag-ops.h"
#include "htp/fill-ops.h"
#include "htp/flash-attn-ops.h"
#include "htp/gated-delta-net-ops.h"
#include "htp/get-rows-ops.h"
#include "htp/glu-ops.h"
#include "htp/matmul-ops.h"
#include "htp/pad-ops.h"
#include "htp/repeat-ops.h"
#include "htp/roll-ops.h"
#include "htp/softmax-ops.h"
#include "htp/solve-tri-ops.h"
#include "htp/ssm-conv.h"
#include "htp/sum-rows-ops.h"
#include "htp/unary-ops.h"
#include <stdio.h>
std::string htp_opnode::name() const {
switch (opcode) {
case HTP_OP_MDEV_GROUP: return "MDEV_GROUP";
case HTP_OP_CPY_FENCE: return "CPY+FENCE";
case HTP_OP_FENCE: return (node && node->op_params[1] == 0) ? "FENCE_WAIT" : "FENCE_SIGNAL";
case HTP_OP_ALLREDUCE: return "ALLREDUCE";
case HTP_OP_ALLREDUCE_ADD: return "ALLREDUCE+ADD";
case HTP_OP_HADAMARD: return "MUL_MAT_HADAMARD";
case HTP_OP_MUL_MAT_NX: return "MUL_MAT_NX";
case HTP_OP_MUL_MAT_ID_NX: return "MUL_MAT_ID_NX";
case HTP_OP_ADD:
case HTP_OP_SUB:
case HTP_OP_MUL:
case HTP_OP_DIV:
if (!fused.empty()) {
const auto * kp = kparams<htp_binary_kernel_params>();
if (kp->kernel_type != HTP_BINARY_KERNEL_SCALAR) {
switch (opcode) {
case HTP_OP_ADD: return "ADD_NX";
case HTP_OP_SUB: return "SUB_NX";
case HTP_OP_MUL: return "MUL_NX";
case HTP_OP_DIV: return "DIV_NX";
default: break;
}
}
}
break;
default:
break;
}
if (!node) {
return "";
}
if (htp_is_hadamard(node)) {
return "MUL_MAT_HADAMARD";
}
std::string res = ggml_op_desc(node);
for (const auto * t : fused) {
res += "+";
res += ggml_op_desc(t);
}
return res;
}
static const char * format_tensor_name(const struct ggml_tensor * t) {
return t ? t->name : "NONE";
}
static std::string format_tensor_dims(const struct ggml_tensor * t) {
if (!t) {
return "NONE";
}
char buf[64];
if (t->ne[2] == 1 && t->ne[3] == 1) {
snprintf(buf, sizeof(buf), "%d:%d", (int) t->ne[0], (int) t->ne[1]);
} else {
snprintf(buf, sizeof(buf), "%d:%d:%d:%d", (int) t->ne[0], (int) t->ne[1], (int) t->ne[2], (int) t->ne[3]);
}
return buf;
}
static const char * format_tensor_type(const struct ggml_tensor * t) {
return t ? ggml_type_name(t->type) : "NONE";
}
static std::string format_tensor_strides(const struct ggml_tensor * t) {
if (!t) {
return "NONE";
}
char buf[64];
const char * c = ggml_is_contiguous(t) ? "" : "!";
if (t->ne[2] == 1 && t->ne[3] == 1) {
snprintf(buf, sizeof(buf), "%zu:%zu%s", (size_t) t->nb[0], (size_t) t->nb[1], c);
} else {
snprintf(buf, sizeof(buf), "%zu:%zu:%zu:%zu%s", (size_t) t->nb[0], (size_t) t->nb[1], (size_t) t->nb[2], (size_t) t->nb[3], c);
}
return buf;
}
static const char * format_tensor_buff(const struct ggml_tensor * t) {
if (t && t->buffer) {
return ggml_backend_buffer_name(t->buffer);
}
return "NONE";
}
template <typename Fn>
static std::string format_op_tensors(const htp_opnode & node, Fn fn) {
std::string res;
const auto & inputs = node.get_inputs();
const auto & outputs = node.get_outputs();
if (!inputs.empty()) {
for (size_t i = 0; i < inputs.size(); i++) {
if (i > 0) res += " x ";
res += fn(inputs[i]);
}
res += " -> ";
}
if (outputs.empty()) {
res += fn(node.dst());
} else {
for (size_t i = 0; i < outputs.size(); i++) {
if (i > 0) res += " x ";
res += fn(outputs[i]);
}
}
return res;
}
static std::string format_kernel_params(const htp_opnode & node) {
char str[128];
const size_t max_size = sizeof(str);
if (node.opcode == HTP_OP_MUL_MAT || node.opcode == HTP_OP_MUL_MAT_ID ||
node.opcode == HTP_OP_MUL_MAT_NX || node.opcode == HTP_OP_MUL_MAT_ID_NX ||
node.opcode == HTP_OP_MUL_MAT_ADD) {
const auto * kparams = node.kparams<htp_mm_kernel_params>();
const char * path = "unknown";
int32_t type = kparams->kernel_type;
if (type == HTP_MM_KERNEL_HMX_2D || type == HTP_MM_KERNEL_HMX_BATCHED) {
path = (kparams->tile_size > 0) ? "hmx-tiled" : "hmx-flat";
} else if (type == HTP_MM_KERNEL_HVX_F16_F16_VTCM || type == HTP_MM_KERNEL_HVX_F32_F32_VTCM ||
type == HTP_MM_KERNEL_HVX_BF16_F32_VTCM) {
path = "hvx-flat";
} else if (type == HTP_MM_KERNEL_HVX_QUANT_ROW || type == HTP_MM_KERNEL_HVX_QUANT_BLOCK) {
path = "hvx-tiled";
}
snprintf(str, max_size, "%s vtcm %d", path, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_FLASH_ATTN_EXT) {
const auto * kparams = node.kparams<htp_fa_kernel_params>();
const char * path = "unknown";
int32_t type = kparams->kernel_type;
if (type == HTP_FA_KERNEL_HMX) {
path = kparams->u.hmx.pipeline ? "hmx-pipe" : "hmx-seq";
} else if (type == HTP_FA_KERNEL_HVX) {
path = "hvx";
}
snprintf(str, max_size, "%s vtcm %d", path, (int) kparams->vtcm_size);
} else if (htp_op_is_unary(node.opcode)) {
const auto * kparams = node.kparams<htp_unary_kernel_params>();
snprintf(str, max_size, "%s vtcm %d", kparams->col_tile ? "wide-row" : "row-block", (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_MDEV_GROUP && node.node) {
snprintf(str, max_size, "idx %d count %d", (int) node.node->op_params[0], (int) node.dst()->ne[1]);
} else if ((node.opcode == HTP_OP_FENCE || node.opcode == HTP_OP_CPY_FENCE) && node.node) {
snprintf(str, max_size, "seq 0x%x", (uint32_t) node.node->op_params[0]);
} else if (node.opcode == HTP_OP_ALLREDUCE && node.node) {
snprintf(str, max_size, "seq 0x%x -> 0x%x", (uint32_t) node.node->op_params[0], (uint32_t) node.node->op_params[1]);
} else if (node.opcode == HTP_OP_SSM_CONV) {
const auto * kparams = node.kparams<htp_ssm_conv_kernel_params>();
snprintf(str, max_size, "%s vtcm %d", kparams->n_t == 1 ? "decode" : "prefill", (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_SOFTMAX) {
const auto * kparams = node.kparams<htp_softmax_kernel_params>();
snprintf(str, max_size, "k%d nth %d vtcm %d", (int) kparams->kernel_type, (int) kparams->n_threads, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_GATED_DELTA_NET) {
const auto * kparams = node.kparams<htp_gdn_kernel_params>();
const char * path = (kparams->kernel_type == HTP_GDN_KERNEL_HMX_CHUNKED) ? "hmx-chunked" : "hvx-recurrent";
snprintf(str, max_size, "%s-%s vtcm %u", path, kparams->kda ? "kda" : "scalar",
(unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_MUL || node.opcode == HTP_OP_ADD || node.opcode == HTP_OP_ADD_ID ||
node.opcode == HTP_OP_SUB || node.opcode == HTP_OP_DIV) {
const auto * kparams = node.kparams<htp_binary_kernel_params>();
snprintf(str, max_size, "vtcm %u", (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_ARGSORT || node.opcode == HTP_OP_TOP_K) {
const auto * kparams = node.kparams<htp_sort_kernel_params>();
snprintf(str, max_size, "%s nth %d nchk %d chk %d vtcm %d",
node.opcode == HTP_OP_TOP_K ? "top_k" : "argsort",
(int) kparams->n_threads, (int) kparams->n_chunks,
(int) kparams->chunk_elems, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_GET_ROWS) {
const auto * kparams = node.kparams<htp_get_rows_kernel_params>();
const char * ktype_str = "unknown";
switch (kparams->kernel_type) {
case HTP_GET_ROWS_KERNEL_SAMETYPE: ktype_str = "sametype"; break;
case HTP_GET_ROWS_KERNEL_TILED: ktype_str = "tiled"; break;
case HTP_GET_ROWS_KERNEL_FLAT: ktype_str = "flat"; break;
}
snprintf(str, max_size, "%s%s vtcm %u", ktype_str, kparams->n_threads > 1 ? "-multi" : "", (unsigned int) kparams->vtcm_size);
} else if (htp_op_is_glu(node.opcode)) {
const auto * kparams = node.kparams<htp_glu_kernel_params>();
snprintf(str, max_size, "nth %d vtcm %u", (int) kparams->n_threads, (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_PAD) {
const auto * kparams = node.kparams<htp_pad_kernel_params>();
snprintf(str, max_size, "k%d nth %d vtcm %u", (int) kparams->kernel_type, (int) kparams->n_threads, (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_CUMSUM) {
const auto * kparams = node.kparams<htp_cumsum_kernel_params>();
snprintf(str, max_size, "%s nth %d vtcm %u", kparams->use_dma ? "dma" : "hvx", (int) kparams->n_threads, (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_FILL) {
const auto * kparams = node.kparams<htp_fill_kernel_params>();
snprintf(str, max_size, "%s nth %d", kparams->kernel_type == HTP_FILL_KERNEL_CONTIGUOUS ? "contig" : "strided", (int) kparams->n_threads);
} else if (node.opcode == HTP_OP_ROLL) {
const auto * kparams = node.kparams<htp_roll_kernel_params>();
snprintf(str, max_size, "k%d nth %d", (int) kparams->kernel_type, (int) kparams->n_threads);
} else if (node.opcode == HTP_OP_DIAG) {
const auto * kparams = node.kparams<htp_diag_kernel_params>();
snprintf(str, max_size, "%s nth %d vtcm %u", kparams->use_dma ? "dma" : "hvx", (int) kparams->n_threads, (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_SOLVE_TRI) {
const auto * kparams = node.kparams<htp_solve_tri_kernel_params>();
snprintf(str, max_size, "%s nth %d", kparams->batched ? "batched" : "chunked", (int) kparams->n_threads);
} else {
snprintf(str, max_size, "----");
}
return str;
}
static void append_section(std::string & res, const std::string & sec) {
if (!res.empty()) {
res += "|";
}
res += sec;
}
std::string htp_format_tensor(const ggml_tensor * t, uint32_t flags) {
std::string res;
if (flags & HTP_OPFMT_NAMES) append_section(res, format_tensor_name(t));
if (flags & HTP_OPFMT_DIMS) append_section(res, format_tensor_dims(t));
if (flags & HTP_OPFMT_TYPES) append_section(res, format_tensor_type(t));
if (flags & HTP_OPFMT_STRIDES) append_section(res, format_tensor_strides(t));
if (flags & HTP_OPFMT_BUFFS) append_section(res, format_tensor_buff(t));
return res;
}
std::string htp_format_op(const htp_opnode & node, uint32_t flags) {
std::string res;
if (flags & HTP_OPFMT_OP) append_section(res, node.name());
if (flags & HTP_OPFMT_NAMES) append_section(res, format_op_tensors(node, format_tensor_name));
if (flags & HTP_OPFMT_DIMS) append_section(res, format_op_tensors(node, format_tensor_dims));
if (flags & HTP_OPFMT_TYPES) append_section(res, format_op_tensors(node, format_tensor_type));
if (flags & HTP_OPFMT_STRIDES) append_section(res, format_op_tensors(node, format_tensor_strides));
if (flags & HTP_OPFMT_BUFFS) append_section(res, format_op_tensors(node, format_tensor_buff));
if (flags & HTP_OPFMT_KPARAMS) append_section(res, format_kernel_params(node));
return res;
}
+112 -353
View File
@@ -1,38 +1,74 @@
#ifndef HTP_OPNODE_H
#define HTP_OPNODE_H
#define GGML_COMMON_IMPL_CPP
#include "ggml-backend-impl.h"
#include "ggml-common.h"
#include "ggml-impl.h"
#include "htp/htp-ops.h"
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
#include <memory>
#include <stdio.h>
#include "htp-ops.h"
#include "htp/matmul-ops.h"
#include "htp/flash-attn-ops.h"
#include "htp/unary-ops.h"
#include "htp/binary-ops.h"
#include "htp/allreduce-ops.h"
#include "htp/ssm-conv.h"
#include "htp/gated-delta-net-ops.h"
#include "htp/softmax-ops.h"
#include "htp/argsort-ops.h"
#include "htp/get-rows-ops.h"
#define HTP_OP_MAX_FUSED 16
// local std::inplace_vector replacement
template <typename T, size_t MAX_N>
struct htp_opvec {
T data[MAX_N] {};
size_t n = 0;
htp_opvec() = default;
htp_opvec(std::initializer_list<T> init) {
GGML_ASSERT(init.size() <= MAX_N);
for (const auto & v : init) {
data[n++] = v;
}
}
size_t size() const { return n; }
bool empty() const { return n == 0; }
void clear() { n = 0; }
T * begin() { return data; }
T * end() { return data + n; }
const T * begin() const { return data; }
const T * end() const { return data + n; }
T & back() { return data[n - 1]; }
const T & back() const { return data[n - 1]; }
T & operator[](size_t i) { return data[i]; }
const T & operator[](size_t i) const { return data[i]; }
void push_back(T val) {
GGML_ASSERT(n < MAX_N);
data[n++] = val;
}
void insert(T * pos, T val) {
GGML_ASSERT(n < MAX_N);
for (T * p = data + n; p > pos; --p) {
*p = *(p - 1);
}
*pos = val;
n++;
}
};
static inline bool htp_is_hadamard(const struct ggml_tensor * t) {
return t && t->op == GGML_OP_MUL_MAT && ggml_get_op_params_i32(t, 1) == GGML_HINT_SRC0_IS_HADAMARD;
}
struct htp_opnode {
ggml_tensor * node { nullptr };
htp_op_code opcode { HTP_OP_INVALID };
int32_t kernel_params[HTP_OP_MAX_KERN_PARAMS] {0};
const ggml_tensor * node { nullptr };
htp_op_code opcode { HTP_OP_INVALID };
int32_t params[HTP_OP_MAX_PARAMS] {0};
int32_t kernel_params[HTP_OP_MAX_KERN_PARAMS] {0};
std::vector<ggml_tensor *> fused;
std::vector<std::shared_ptr<ggml_tensor>> dummy;
std::vector<std::shared_ptr<ggml_tensor>> dummy;
std::vector<const ggml_tensor *> inputs;
std::vector<const ggml_tensor *> outputs;
std::string name;
htp_opvec<const ggml_tensor *, HTP_OP_MAX_FUSED> fused;
htp_opvec<const ggml_tensor *, HTP_OP_MAX_INPUTS> inputs;
htp_opvec<const ggml_tensor *, HTP_OP_MAX_OUTPUTS> outputs;
int n_active_src(const ggml_tensor * t) const {
if (!t) return 0;
@@ -44,28 +80,36 @@ struct htp_opnode {
return 0;
}
void init(ggml_tensor * node) {
void init(const ggml_tensor * node) {
static_assert(sizeof(this->params) == sizeof(this->node->op_params), "params size mismatch");
this->node = node;
if (this->node) {
this->name = ggml_op_desc(this->node);
if (!node) return;
// Build inputs (preserving optional nullptrs)
memcpy(this->params, this->node->op_params, sizeof(this->params));
// Build inputs (preserving optional nullptrs)
if (htp_is_hadamard(this->node)) {
this->inputs.push_back(this->node->src[1]);
} else {
int n_inputs = n_active_src(this->node);
this->inputs.resize(n_inputs, nullptr);
for (int i = 0; i < n_inputs; i++) {
this->inputs[i] = this->node->src[i];
this->inputs.push_back(this->node->src[i]);
}
// Build outputs
this->outputs.push_back(this->dst());
}
// Build outputs
this->outputs.push_back(this->dst());
}
htp_opnode(htp_op_code opcode = HTP_OP_INVALID, ggml_tensor * node = nullptr) : opcode(opcode) {
htp_opnode(htp_op_code opcode = HTP_OP_INVALID, const ggml_tensor * node = nullptr) : opcode(opcode) {
init(node);
}
htp_opnode(const ggml_tensor * node) : opcode(HTP_OP_INVALID) {
init(node);
}
ggml_op op() const { return node->op; }
const ggml_tensor * src0() const { return node->src[0]; }
const ggml_tensor * src1() const { return node->src[1]; }
const ggml_tensor * dst() const { return outputs.empty() ? node : outputs.back(); }
@@ -75,339 +119,54 @@ struct htp_opnode {
return dummy.back().get();
}
void add_fused(ggml_tensor * t, bool extra_dst = false) {
fused.push_back(t);
name += "+";
name += ggml_op_desc(t);
if (extra_dst) {
outputs.push_back(t);
} else {
outputs.clear();
outputs.push_back(t);
}
// Remove the newly fused intermediate output tensor t from inputs (if it was there)
inputs.erase(std::remove(inputs.begin(), inputs.end(), t), inputs.end());
// Append new inputs from t, preserving middle nullptrs
int n_inputs = n_active_src(t);
for (int i = 0; i < n_inputs; i++) {
const auto * src = t->src[i];
if (!src) {
inputs.push_back(nullptr);
} else if (src != node &&
std::find(fused.begin(), fused.end(), src) == fused.end() &&
std::find(inputs.begin(), inputs.end(), src) == inputs.end()) {
inputs.push_back(src);
}
}
}
const std::vector<const ggml_tensor *> & get_inputs() const {
const htp_opvec<const ggml_tensor *, HTP_OP_MAX_INPUTS> & get_inputs() const {
return inputs;
}
const std::vector<const ggml_tensor *> & get_outputs() const {
const htp_opvec<const ggml_tensor *, HTP_OP_MAX_OUTPUTS> & get_outputs() const {
return outputs;
}
std::string op_name() const {
return name;
template <typename T>
T * kparams() {
static_assert(sizeof(T) <= sizeof(kernel_params), "kparams size exceeds HTP_OP_MAX_KERN_PARAMS");
static_assert(alignof(T) <= alignof(int32_t), "kparams alignment exceeds int32_t");
return reinterpret_cast<T *>(kernel_params);
}
bool is_empty() const {
return ggml_op_is_empty(node->op);
template <typename T>
const T * kparams() const {
static_assert(sizeof(T) <= sizeof(kernel_params), "kparams size exceeds HTP_OP_MAX_KERN_PARAMS");
static_assert(alignof(T) <= alignof(int32_t), "kparams alignment exceeds int32_t");
return reinterpret_cast<const T *>(kernel_params);
}
bool stackable() const {
switch (this->op()) {
case GGML_OP_MUL_MAT:
case GGML_OP_MUL_MAT_ID:
return ggml_is_quantized(this->src0()->type);
default:
return false;
}
template <typename T>
void set_kparams(const T & kp) {
static_assert(sizeof(T) <= sizeof(kernel_params), "kparams size exceeds HTP_OP_MAX_KERN_PARAMS");
static_assert(alignof(T) <= alignof(int32_t), "kparams alignment exceeds int32_t");
memcpy(kernel_params, &kp, sizeof(T));
}
bool same_input(const htp_opnode& n) const {
return n.src1() == this->src1();
}
std::string name() const;
};
struct htp_opformat {
char strides[64 * GGML_MAX_SRC];
char dims[64 * GGML_MAX_SRC];
char types[16 * GGML_MAX_SRC];
char buffs[64 * GGML_MAX_SRC];
char names[64 * GGML_MAX_SRC];
char kparams[128];
enum htp_opformat_flags {
HTP_OPFMT_OP = (1 << 0),
HTP_OPFMT_NAMES = (1 << 1),
HTP_OPFMT_DIMS = (1 << 2),
HTP_OPFMT_TYPES = (1 << 3),
HTP_OPFMT_STRIDES = (1 << 4),
HTP_OPFMT_BUFFS = (1 << 5),
HTP_OPFMT_KPARAMS = (1 << 6),
int format_tensor_dims(char * str, size_t max_size, const struct ggml_tensor * t) {
if (!t) {
return snprintf(str, max_size, "NONE");
}
if (t->ne[2] == 1 && t->ne[3] == 1) {
return snprintf(str, max_size, "%d:%d", (int) t->ne[0], (int) t->ne[1]);
} else {
return snprintf(str, max_size, "%d:%d:%d:%d", (int) t->ne[0], (int) t->ne[1], (int) t->ne[2], (int) t->ne[3]);
}
}
void format_op_dims(char * str, size_t max_size, const htp_opnode & node) {
char * p = str;
char * p_end = str + max_size;
auto inputs = node.get_inputs();
if (!inputs.empty()) {
p += std::min((size_t)format_tensor_dims(p, p_end - p, inputs[0]), (size_t)(p_end - p));
for (size_t i = 1; i < inputs.size(); i++) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " x "), (size_t)(p_end - p));
}
if (p < p_end) {
p += std::min((size_t)format_tensor_dims(p, p_end - p, inputs[i]), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " -> "), (size_t)(p_end - p));
}
}
char self[64];
format_tensor_dims(self, sizeof(self), node.dst());
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", self), (size_t)(p_end - p));
}
}
int format_tensor_strides(char * str, size_t max_size, const struct ggml_tensor * t) {
if (!t) {
return snprintf(str, max_size, "NONE");
}
const char * c = ggml_is_contiguous(t) ? "" : "!";
if (t->ne[2] == 1 && t->ne[3] == 1) {
return snprintf(str, max_size, "%zu:%zu%s", (size_t) t->nb[0], (size_t) t->nb[1], c);
} else {
return snprintf(str, max_size, "%zu:%zu:%zu:%zu%s", (size_t) t->nb[0], (size_t) t->nb[1], (size_t) t->nb[2], (size_t) t->nb[3], c);
}
}
void format_op_strides(char * str, size_t max_size, const htp_opnode & node) {
char * p = str;
char * p_end = str + max_size;
auto inputs = node.get_inputs();
if (!inputs.empty()) {
p += std::min((size_t)format_tensor_strides(p, p_end - p, inputs[0]), (size_t)(p_end - p));
for (size_t i = 1; i < inputs.size(); i++) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " x "), (size_t)(p_end - p));
}
if (p < p_end) {
p += std::min((size_t)format_tensor_strides(p, p_end - p, inputs[i]), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " -> "), (size_t)(p_end - p));
}
}
char self[64];
format_tensor_strides(self, sizeof(self), node.dst());
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", self), (size_t)(p_end - p));
}
}
void format_op_types(char * str, size_t max_size, const htp_opnode & node) {
char * p = str;
char * p_end = str + max_size;
auto inputs = node.get_inputs();
if (!inputs.empty()) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", inputs[0] ? ggml_type_name(inputs[0]->type) : "NONE"), (size_t)(p_end - p));
}
for (size_t i = 1; i < inputs.size(); i++) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " x "), (size_t)(p_end - p));
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", inputs[i] ? ggml_type_name(inputs[i]->type) : "NONE"), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " -> "), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", ggml_type_name(node.dst()->type)), (size_t)(p_end - p));
}
}
const char * tensor_buff_name(const struct ggml_tensor * t) {
if (t && t->buffer) {
return ggml_backend_buffer_name(t->buffer);
}
return "NONE";
}
void format_op_buffs(char * str, size_t max_size, const htp_opnode & node) {
char * p = str;
char * p_end = str + max_size;
auto inputs = node.get_inputs();
if (!inputs.empty()) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", tensor_buff_name(inputs[0])), (size_t)(p_end - p));
}
for (size_t i = 1; i < inputs.size(); i++) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " x "), (size_t)(p_end - p));
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", tensor_buff_name(inputs[i])), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " -> "), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", tensor_buff_name(node.dst())), (size_t)(p_end - p));
}
}
void format_op_names(char * str, size_t max_size, const htp_opnode & node) {
char * p = str;
char * p_end = str + max_size;
auto inputs = node.get_inputs();
if (!inputs.empty()) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", inputs[0] ? inputs[0]->name : "NONE"), (size_t)(p_end - p));
}
for (size_t i = 1; i < inputs.size(); i++) {
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " x "), (size_t)(p_end - p));
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", inputs[i] ? inputs[i]->name : "NONE"), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, " -> "), (size_t)(p_end - p));
}
}
if (p < p_end) {
p += std::min((size_t)snprintf(p, p_end - p, "%s", node.dst()->name), (size_t)(p_end - p));
}
}
void format_kernel_params(char * str, size_t max_size, const htp_opnode & node) {
if (node.opcode == HTP_OP_MUL_MAT || node.opcode == HTP_OP_MUL_MAT_ID ||
node.opcode == HTP_OP_MUL_MAT_NX || node.opcode == HTP_OP_MUL_MAT_ID_NX ||
node.opcode == HTP_OP_MUL_MAT_ADD) {
const auto * kparams = (const struct htp_mm_kernel_params *) node.kernel_params;
const char * path = "unknown";
int32_t type = kparams->kernel_type;
if (type == HTP_MM_KERNEL_HMX_2D || type == HTP_MM_KERNEL_HMX_BATCHED) {
path = (kparams->tile_size > 0) ? "hmx-tiled" : "hmx-flat";
} else if (type == HTP_MM_KERNEL_HVX_F16_F16_VTCM || type == HTP_MM_KERNEL_HVX_F32_F32_VTCM ||
type == HTP_MM_KERNEL_HVX_BF16_F32_VTCM) {
path = "hvx-flat";
} else if (type == HTP_MM_KERNEL_HVX_QUANT_ROW || type == HTP_MM_KERNEL_HVX_QUANT_BLOCK) {
path = "hvx-tiled";
}
snprintf(str, max_size, "%s vtcm %d", path, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_FLASH_ATTN_EXT) {
const auto * kparams = (const struct htp_fa_kernel_params *) node.kernel_params;
const char * path = "unknown";
int32_t type = kparams->kernel_type;
if (type == HTP_FA_KERNEL_HMX) {
path = kparams->u.hmx.pipeline ? "hmx-pipe" : "hmx-seq";
} else if (type == HTP_FA_KERNEL_HVX) {
path = "hvx";
}
snprintf(str, max_size, "%s vtcm %d", path, (int) kparams->vtcm_size);
} else if (htp_op_is_unary(node.opcode)) {
const auto * kparams = (const struct htp_unary_kernel_params *) node.kernel_params;
snprintf(str, max_size, "%s vtcm %d", kparams->col_tile ? "wide-row" : "row-block", (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_MDEV_GROUP && node.node) {
snprintf(str, max_size, "idx %d count %d", (int) node.node->op_params[0], (int) node.dst()->ne[1]);
} else if ((node.opcode == HTP_OP_FENCE || node.opcode == HTP_OP_CPY_FENCE) && node.node) {
snprintf(str, max_size, "seq 0x%x", (uint32_t) node.node->op_params[0]);
} else if (node.opcode == HTP_OP_ALLREDUCE && node.node) {
snprintf(str, max_size, "seq 0x%x -> 0x%x", (uint32_t) node.node->op_params[0], (uint32_t) node.node->op_params[1]);
} else if (node.opcode == HTP_OP_SSM_CONV) {
const auto * kparams = (const struct htp_ssm_conv_kernel_params *) node.kernel_params;
snprintf(str, max_size, "%s vtcm %d", kparams->n_t == 1 ? "decode" : "prefill", (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_SOFTMAX) {
const auto * kparams = (const struct htp_softmax_kernel_params *) node.kernel_params;
snprintf(str, max_size, "k%d nth %d vtcm %d", (int) kparams->kernel_id, (int) kparams->n_threads, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_GATED_DELTA_NET) {
const auto * kparams = (const struct htp_gdn_kernel_params *) node.kernel_params;
const char * path = (kparams->kernel_type == HTP_GDN_KERNEL_HMX_CHUNKED) ? "hmx-chunked" : "hvx-recurrent";
snprintf(str, max_size, "%s-%s vtcm %u", path, kparams->kda ? "kda" : "scalar",
(unsigned int) (kparams->vtcm_size ? kparams->vtcm_size : kparams->vtcm_per_thread * kparams->n_threads));
} else if (node.opcode == HTP_OP_MUL || node.opcode == HTP_OP_ADD || node.opcode == HTP_OP_ADD_ID ||
node.opcode == HTP_OP_SUB || node.opcode == HTP_OP_DIV) {
const auto * kparams = (const struct htp_binary_kernel_params *) node.kernel_params;
snprintf(str, max_size, "vtcm %u", (unsigned int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_ARGSORT || node.opcode == HTP_OP_TOP_K) {
const auto * kparams = (const struct htp_sort_kernel_params *) node.kernel_params;
snprintf(str, max_size, "%s nth %d nchk %d chk %d vtcm %d",
node.opcode == HTP_OP_TOP_K ? "top_k" : "argsort",
(int) kparams->n_threads, (int) kparams->n_chunks,
(int) kparams->chunk_elems, (int) kparams->vtcm_size);
} else if (node.opcode == HTP_OP_GET_ROWS) {
const auto * kparams = (const struct htp_get_rows_kernel_params *) node.kernel_params;
const char * ktype_str = "unknown";
switch (kparams->kernel_type) {
case HTP_GET_ROWS_KERNEL_SAMETYPE: ktype_str = "sametype"; break;
case HTP_GET_ROWS_KERNEL_TILED: ktype_str = "tiled"; break;
case HTP_GET_ROWS_KERNEL_FLAT: ktype_str = "flat"; break;
}
snprintf(str, max_size, "%s%s vtcm %u", ktype_str, kparams->n_threads > 1 ? "-multi" : "", (unsigned int) kparams->vtcm_size);
} else {
snprintf(str, max_size, "----");
}
}
void format(const htp_opnode & node) {
format_op_dims(dims, sizeof(dims), node);
format_op_strides(strides, sizeof(strides), node);
format_op_types(types, sizeof(types), node);
format_op_buffs(buffs, sizeof(buffs), node);
format_op_names(names, sizeof(names), node);
format_kernel_params(kparams, sizeof(kparams), node);
}
htp_opformat() {
strides[0] = '\0';
dims[0] = '\0';
types[0] = '\0';
buffs[0] = '\0';
names[0] = '\0';
kparams[0] = '\0';
}
htp_opformat(const htp_opnode & node) { format(node); }
HTP_OPFMT_EXEC = HTP_OPFMT_OP | HTP_OPFMT_NAMES | HTP_OPFMT_DIMS | HTP_OPFMT_TYPES | HTP_OPFMT_STRIDES | HTP_OPFMT_BUFFS | HTP_OPFMT_KPARAMS,
HTP_OPFMT_SUPP = HTP_OPFMT_OP | HTP_OPFMT_NAMES | HTP_OPFMT_DIMS | HTP_OPFMT_TYPES | HTP_OPFMT_STRIDES | HTP_OPFMT_BUFFS,
HTP_OPFMT_PROF = HTP_OPFMT_OP | HTP_OPFMT_NAMES | HTP_OPFMT_DIMS | HTP_OPFMT_TYPES | HTP_OPFMT_STRIDES | HTP_OPFMT_KPARAMS,
HTP_OPFMT_TENSOR = HTP_OPFMT_NAMES | HTP_OPFMT_DIMS | HTP_OPFMT_TYPES | HTP_OPFMT_STRIDES,
};
std::string htp_format_op(const htp_opnode & node, uint32_t flags = HTP_OPFMT_EXEC);
std::string htp_format_tensor(const ggml_tensor * t, uint32_t flags = HTP_OPFMT_TENSOR);
#endif // HTP_OPNODE_H
+674 -38
View File
@@ -9,6 +9,11 @@
#include <cmath>
#include <cstring>
#include <initializer_list>
#include <thread>
#if defined(__ARM_NEON) || defined(__aarch64__)
# include <arm_neon.h>
#endif
// --- 4-bit block-32 nibble helpers (shared by Q4_0, IQ4_NL, Q4_1, MXFP4) ---
@@ -43,8 +48,309 @@ static inline void transpose_tile_to_nibbles(uint8_t tile_quants[32][32], const
}
}
#if defined(__ARM_NEON) || defined(__aarch64__)
static void repack_neon_transpose8x8(const uint8x8_t rows[8], uint8x8_t columns[8]) {
const uint8x8x2_t t0 = vtrn_u8(rows[0], rows[1]);
const uint8x8x2_t t1 = vtrn_u8(rows[2], rows[3]);
const uint8x8x2_t t2 = vtrn_u8(rows[4], rows[5]);
const uint8x8x2_t t3 = vtrn_u8(rows[6], rows[7]);
const uint16x4x2_t t4 = vtrn_u16(vreinterpret_u16_u8(t0.val[0]), vreinterpret_u16_u8(t1.val[0]));
const uint16x4x2_t t5 = vtrn_u16(vreinterpret_u16_u8(t0.val[1]), vreinterpret_u16_u8(t1.val[1]));
const uint16x4x2_t t6 = vtrn_u16(vreinterpret_u16_u8(t2.val[0]), vreinterpret_u16_u8(t3.val[0]));
const uint16x4x2_t t7 = vtrn_u16(vreinterpret_u16_u8(t2.val[1]), vreinterpret_u16_u8(t3.val[1]));
const uint32x2x2_t t8 = vtrn_u32(vreinterpret_u32_u16(t4.val[0]), vreinterpret_u32_u16(t6.val[0]));
const uint32x2x2_t t9 = vtrn_u32(vreinterpret_u32_u16(t4.val[1]), vreinterpret_u32_u16(t6.val[1]));
const uint32x2x2_t ta = vtrn_u32(vreinterpret_u32_u16(t5.val[0]), vreinterpret_u32_u16(t7.val[0]));
const uint32x2x2_t tb = vtrn_u32(vreinterpret_u32_u16(t5.val[1]), vreinterpret_u32_u16(t7.val[1]));
columns[0] = vreinterpret_u8_u32(t8.val[0]);
columns[1] = vreinterpret_u8_u32(ta.val[0]);
columns[2] = vreinterpret_u8_u32(t9.val[0]);
columns[3] = vreinterpret_u8_u32(tb.val[0]);
columns[4] = vreinterpret_u8_u32(t8.val[1]);
columns[5] = vreinterpret_u8_u32(ta.val[1]);
columns[6] = vreinterpret_u8_u32(t9.val[1]);
columns[7] = vreinterpret_u8_u32(tb.val[1]);
}
static uint8x8_t repack_neon_pack_4bit_pairs(uint8x8_t first, uint8x8_t second, bool high) {
const uint8x8x2_t separated = vuzp_u8(first, second);
const uint8x8_t mask = vdup_n_u8(0x0F);
uint8x8_t even = separated.val[0];
uint8x8_t odd = separated.val[1];
if (high) {
even = vshr_n_u8(even, 4);
odd = vshr_n_u8(odd, 4);
}
even = vand_u8(even, mask);
odd = vand_u8(odd, mask);
return vorr_u8(vshl_n_u8(odd, 4), even);
}
static void repack_neon_4bit_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0) {
uint8x8_t low_rows[8];
uint8x8_t high_rows[8];
uint8x8_t columns[8];
for (int rr = 0; rr < 8; ++rr) {
const uint8x16_t packed = vld1q_u8(qs[rr]);
const uint8x8_t first = vget_low_u8(packed);
const uint8x8_t second = vget_high_u8(packed);
low_rows[rr] = repack_neon_pack_4bit_pairs(first, second, false);
high_rows[rr] = repack_neon_pack_4bit_pairs(first, second, true);
}
repack_neon_transpose8x8(low_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + cp * 32 + row0, columns[cp]);
}
repack_neon_transpose8x8(high_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + (cp + 8) * 32 + row0, columns[cp]);
}
}
static void repack_neon_k4_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0, bool high) {
uint8x8_t first_rows[8];
uint8x8_t second_rows[8];
uint8x8_t columns[8];
for (int rr = 0; rr < 8; ++rr) {
first_rows[rr] = repack_neon_pack_4bit_pairs(vld1_u8(qs[rr]), vld1_u8(qs[rr] + 8), high);
second_rows[rr] = repack_neon_pack_4bit_pairs(vld1_u8(qs[rr] + 16), vld1_u8(qs[rr] + 24), high);
}
repack_neon_transpose8x8(first_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + cp * 32 + row0, columns[cp]);
}
repack_neon_transpose8x8(second_rows, columns);
for (int cp = 0; cp < 8; ++cp) {
vst1_u8(tile_dst + (cp + 8) * 32 + row0, columns[cp]);
}
}
static void repack_neon_q8_rows(const uint8_t * qs[8], uint8_t * tile_dst, int row0) {
uint8x8_t rows[8];
uint8x8_t columns[8];
for (int cp = 0; cp < 16; cp += 4) {
for (int rr = 0; rr < 8; ++rr) {
rows[rr] = vld1_u8(qs[rr] + 2 * cp);
}
repack_neon_transpose8x8(rows, columns);
for (int pair = 0; pair < 4; ++pair) {
const uint8x8x2_t interleaved = vzip_u8(columns[2 * pair], columns[2 * pair + 1]);
uint8_t * dst = tile_dst + (cp + pair) * 64 + 2 * row0;
vst1_u8(dst, interleaved.val[0]);
vst1_u8(dst + 8, interleaved.val[1]);
}
}
}
static inline void repack_store_u32(uint8_t * dst, uint32_t v) {
memcpy(dst, &v, sizeof(v));
}
static inline uint32x4_t repack_neon_transpose_2bit_4x4(uint32x4_t w) {
uint32x4_t t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 6)), vdupq_n_u32(0x00CC00CCu));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 6)));
t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 12)), vdupq_n_u32(0x0000F0F0u));
return veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 12)));
}
static inline void repack_neon_q2_0_block(const uint8_t * qs, uint8_t * tile, int row) {
const uint32x4_t w = repack_neon_transpose_2bit_4x4(vreinterpretq_u32_u8(vld1q_u8(qs)));
repack_store_u32(tile + 0 * 128 + 4 * row, vgetq_lane_u32(w, 0));
repack_store_u32(tile + 1 * 128 + 4 * row, vgetq_lane_u32(w, 1));
repack_store_u32(tile + 2 * 128 + 4 * row, vgetq_lane_u32(w, 2));
repack_store_u32(tile + 3 * 128 + 4 * row, vgetq_lane_u32(w, 3));
}
static inline void repack_neon_q1_0_block(const uint8_t * qs, uint8_t * tile, int row) {
uint32x4_t w = vreinterpretq_u32_u8(vld1q_u8(qs));
uint32x4_t t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 1)), vdupq_n_u32(0x22222222u));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 1)));
t = vandq_u32(veorq_u32(w, vshrq_n_u32(w, 3)), vdupq_n_u32(0x0A0A0A0Au));
w = veorq_u32(w, veorq_u32(t, vshlq_n_u32(t, 3)));
w = repack_neon_transpose_2bit_4x4(w);
repack_store_u32(tile + 0 * 128 + 4 * row, vgetq_lane_u32(w, 0));
repack_store_u32(tile + 1 * 128 + 4 * row, vgetq_lane_u32(w, 1));
repack_store_u32(tile + 2 * 128 + 4 * row, vgetq_lane_u32(w, 2));
repack_store_u32(tile + 3 * 128 + 4 * row, vgetq_lane_u32(w, 3));
}
static inline void repack_neon_pack_2bit_plane(uint8x16_t v0, uint8x16_t v1, uint8_t * dst, int row) {
const uint32x4x2_t z = vuzpq_u32(vreinterpretq_u32_u8(v0), vreinterpretq_u32_u8(v1));
const uint32x4_t p01 = vorrq_u32(z.val[0], vshlq_n_u32(z.val[1], 2));
const uint32x2x2_t u = vuzp_u32(vget_low_u32(p01), vget_high_u32(p01));
const uint32x2_t out = vorr_u32(u.val[0], vshl_n_u32(u.val[1], 4));
repack_store_u32(dst + 0 * 128 + row * 4, vget_lane_u32(out, 0));
repack_store_u32(dst + 1 * 128 + row * 4, vget_lane_u32(out, 1));
}
static inline void repack_neon_q2_3_k_low2(const uint8_t * qs, int kt_local, uint8_t * lo_pl, int row) {
const uint8_t * qs_sub = qs + (kt_local / 4) * 32;
const int8x16_t shift = vdupq_n_s8(-2 * (kt_local % 4));
const uint8x16_t mask = vdupq_n_u8(0x03);
const uint8x16_t v0 = vandq_u8(vshlq_u8(vld1q_u8(qs_sub + 0), shift), mask);
const uint8x16_t v1 = vandq_u8(vshlq_u8(vld1q_u8(qs_sub + 16), shift), mask);
repack_neon_pack_2bit_plane(v0, v1, lo_pl, row);
}
static inline void repack_neon_q3_k_hmask(const uint8_t * hmask, int kt_local, uint8_t * neg_pl, int row) {
static const uint32_t mask_words[4] = { 0x01010101u, 0x02020202u, 0x04040404u, 0x08080808u };
const uint8x16_t m0 = vreinterpretq_u8_u32(vld1q_u32(mask_words));
const uint8x16_t m1 = vshlq_n_u8(m0, 4);
const uint8x16_t hbit = vdupq_n_u8((uint8_t) (1u << kt_local));
const uint8x16_t b0 = vbicq_u8(m0, vtstq_u8(vld1q_u8(hmask + 0), hbit));
const uint8x16_t b1 = vbicq_u8(m1, vtstq_u8(vld1q_u8(hmask + 16), hbit));
const uint32x4_t b01 = vreinterpretq_u32_u8(vorrq_u8(b0, b1));
const uint32x2_t f0 = vorr_u32(vget_low_u32(b01), vget_high_u32(b01));
const uint32x2_t f1 = vpadd_u32(f0, f0);
repack_store_u32(neg_pl + row * 4, vget_lane_u32(f1, 0));
}
static inline void repack_neon_q6_k(const block_q6_K * b, int kt_local, uint8_t * lo_pl, uint8_t * hi_pl, int row) {
const int c = kt_local / 4;
const int g_sb = kt_local % 4;
const uint8_t * ql = b->ql + c * 64 + (g_sb & 1) * 32;
const uint8_t * qh = b->qh + c * 32;
const int8x16_t lshift = vdupq_n_s8((g_sb >= 2) ? -4 : 0);
const int8x16_t hshift = vdupq_n_s8(-2 * g_sb);
const uint8x16_t mask4 = vdupq_n_u8(0x0F);
const uint8x16_t mask2 = vdupq_n_u8(0x03);
const uint8x16_t lo0 = vandq_u8(vshlq_u8(vld1q_u8(ql + 0), lshift), mask4);
const uint8x16_t lo1 = vandq_u8(vshlq_u8(vld1q_u8(ql + 16), lshift), mask4);
const uint32x4x2_t lz = vuzpq_u32(vreinterpretq_u32_u8(lo0), vreinterpretq_u32_u8(lo1));
const uint32x4_t lout = vorrq_u32(lz.val[0], vshlq_n_u32(lz.val[1], 4));
repack_store_u32(lo_pl + 0 * 128 + row * 4, vgetq_lane_u32(lout, 0));
repack_store_u32(lo_pl + 1 * 128 + row * 4, vgetq_lane_u32(lout, 1));
repack_store_u32(lo_pl + 2 * 128 + row * 4, vgetq_lane_u32(lout, 2));
repack_store_u32(lo_pl + 3 * 128 + row * 4, vgetq_lane_u32(lout, 3));
const uint8x16_t hi0 = vandq_u8(vshlq_u8(vld1q_u8(qh + 0), hshift), mask2);
const uint8x16_t hi1 = vandq_u8(vshlq_u8(vld1q_u8(qh + 16), hshift), mask2);
repack_neon_pack_2bit_plane(hi0, hi1, hi_pl, row);
}
static inline void repack_neon_q5_k_qh(const uint8_t * qh, uint8_t hbit, uint8_t * plane, int row) {
static const uint8_t mask_bytes[16] = { 1, 2, 1, 2, 1, 2, 1, 2, 4, 8, 4, 8, 4, 8, 4, 8 };
const uint8x16_t m0 = vld1q_u8(mask_bytes);
const uint8x16_t m1 = vshlq_n_u8(m0, 4);
const uint8x16_t vbit = vdupq_n_u8(hbit);
const uint8x16_t b0 = vandq_u8(vtstq_u8(vld1q_u8(qh + 0), vbit), m0);
const uint8x16_t b1 = vandq_u8(vtstq_u8(vld1q_u8(qh + 16), vbit), m1);
const uint8x16_t b01 = vorrq_u8(b0, b1);
const uint8x8_t fold = vorr_u8(vget_low_u8(b01), vget_high_u8(b01));
const uint8x8_t out = vpadd_u8(fold, fold);
plane[0 * 32 + row] = vget_lane_u8(out, 0);
plane[1 * 32 + row] = vget_lane_u8(out, 1);
plane[2 * 32 + row] = vget_lane_u8(out, 2);
plane[3 * 32 + row] = vget_lane_u8(out, 3);
}
static inline uint16x8_t repack_neon_load_f16x8(const uint16_t * src) {
return vld1q_u16(src);
}
#if defined(__aarch64__) || defined(__ARM_FP16_FORMAT_IEEE)
static inline uint16x8_t repack_neon_load_f16x8(const float * src) {
const float16x4_t lo = vcvt_f16_f32(vld1q_f32(src));
const float16x4_t hi = vcvt_f16_f32(vld1q_f32(src + 4));
return vreinterpretq_u16_f16(vcombine_f16(lo, hi));
}
static inline uint16x8_t repack_neon_load_f16x8(const ggml_bf16_t * src) {
const uint16x8_t bf = vld1q_u16((const uint16_t *) src);
const float32x4_t f_lo = vreinterpretq_f32_u32(vshll_n_u16(vget_low_u16(bf), 16));
const float32x4_t f_hi = vreinterpretq_f32_u32(vshll_n_u16(vget_high_u16(bf), 16));
const float16x4_t lo = vcvt_f16_f32(f_lo);
const float16x4_t hi = vcvt_f16_f32(f_hi);
return vreinterpretq_u16_f16(vcombine_f16(lo, hi));
}
#else
static inline uint16x8_t repack_neon_load_f16x8(const float * src) {
uint16_t tmp[8];
for (int i = 0; i < 8; ++i) {
tmp[i] = GGML_FP32_TO_FP16(src[i]);
}
return vld1q_u16(tmp);
}
static inline uint16x8_t repack_neon_load_f16x8(const ggml_bf16_t * src) {
uint16_t tmp[8];
for (int i = 0; i < 8; ++i) {
tmp[i] = GGML_FP32_TO_FP16(GGML_BF16_TO_FP32(src[i]));
}
return vld1q_u16(tmp);
}
#endif
template <typename SrcT>
static void repack_neon_f16_tile(uint16_t * tile_dst, const SrcT * src_slice, int64_t row_stride, int ct, int kt) {
for (int row0 = 0; row0 < 32; row0 += 4) {
const SrcT * r0 = src_slice + (int64_t) (ct * 32 + row0 + 0) * row_stride + kt * 32;
const SrcT * r1 = src_slice + (int64_t) (ct * 32 + row0 + 1) * row_stride + kt * 32;
const SrcT * r2 = src_slice + (int64_t) (ct * 32 + row0 + 2) * row_stride + kt * 32;
const SrcT * r3 = src_slice + (int64_t) (ct * 32 + row0 + 3) * row_stride + kt * 32;
for (int cp = 0; cp < 16; cp += 4) {
const uint32x4_t v0 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r0 + 2 * cp));
const uint32x4_t v1 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r1 + 2 * cp));
const uint32x4_t v2 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r2 + 2 * cp));
const uint32x4_t v3 = vreinterpretq_u32_u16(repack_neon_load_f16x8(r3 + 2 * cp));
const uint32x2x2_t t0 = vtrn_u32(vget_low_u32(v0), vget_low_u32(v1));
const uint32x2x2_t t1 = vtrn_u32(vget_high_u32(v0), vget_high_u32(v1));
const uint32x2x2_t t2 = vtrn_u32(vget_low_u32(v2), vget_low_u32(v3));
const uint32x2x2_t t3 = vtrn_u32(vget_high_u32(v2), vget_high_u32(v3));
vst1q_u16(tile_dst + (cp + 0) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t0.val[0], t2.val[0])));
vst1q_u16(tile_dst + (cp + 1) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t0.val[1], t2.val[1])));
vst1q_u16(tile_dst + (cp + 2) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t1.val[0], t3.val[0])));
vst1q_u16(tile_dst + (cp + 3) * 64 + row0 * 2, vreinterpretq_u16_u32(vcombine_u32(t1.val[1], t3.val[1])));
}
}
}
#endif
// --- Generic 32x32 tile grid walkers for block-32 and float types ---
template <typename Fn>
static void repack_parallel_for_ct(size_t slice_bytes, int n_col_tiles, Fn && fn) {
int n_threads = 1;
if (slice_bytes > 128 * 1024) {
for (int nt : { 4, 3, 2 }) {
if (n_col_tiles % nt == 0) {
n_threads = nt;
break;
}
}
}
if (n_threads <= 1) {
fn(0, n_col_tiles);
return;
}
const int chunk = n_col_tiles / n_threads;
std::thread workers[3];
for (int i = 0; i < n_threads - 1; ++i) {
workers[i] = std::thread([&fn, i, chunk]() {
fn(i * chunk, (i + 1) * chunk);
});
}
fn((n_threads - 1) * chunk, n_col_tiles);
for (int i = 0; i < n_threads - 1; ++i) {
workers[i].join();
}
}
template <typename SrcElemT, int ELEMS_PER_KTILE, typename PackTileFn>
static void repack_to_tiled_grid_32(ggml_tensor * t, const void * data, size_t offset, size_t size,
size_t tile_size, PackTileFn && pack_tile) {
@@ -70,12 +376,14 @@ static void repack_to_tiled_grid_32(ggml_tensor * t, const void * data, size_t o
const SrcElemT * src_slice = src_matrix + (slice_idx - start_slice) * (ne1 * row_stride);
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
for (int ct = 0; ct < n_col_tiles; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_tile(tile_dst, src_slice, ne0, ne1, row_stride, ct, kt);
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
for (int ct = ct0; ct < ct1; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_tile(tile_dst, src_slice, ne0, ne1, row_stride, ct, kt);
}
}
}
});
}
}
@@ -130,8 +438,24 @@ static void unpack_from_tiled_grid_32(void * data, const ggml_tensor * t, size_t
static void repack_q4_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q4_0, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_0,
[](uint8_t * tile_dst, const block_q4_0 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].d;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -167,8 +491,26 @@ static void repack_tiled_q4_0(void * data, const ggml_tensor * t, size_t offset,
static void repack_q4_1_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q4_1, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_1,
[](uint8_t * tile_dst, const block_q4_1 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
const block_q4_1 & src_block = src_slice[(ct * 32 + row) * row_stride + kt];
scale_dst[2 * row + 0] = src_block.d;
scale_dst[2 * row + 1] = src_block.m;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -207,8 +549,24 @@ static void repack_tiled_q4_1(void * data, const ggml_tensor * t, size_t offset,
static void repack_mxfp4_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_mxfp4, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_MXFP4,
[](uint8_t * tile_dst, const block_mxfp4 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint8_t tile_quants[32][32];
uint8_t * scale_dst = tile_dst + 512;
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_4bit_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].e;
}
return;
}
#endif
uint8_t tile_quants[32][32];
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < ne1 && kt < ne0 / 32) {
@@ -244,6 +602,23 @@ static void repack_tiled_mxfp4(void * data, const ggml_tensor * t, size_t offset
static void repack_q8_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_to_tiled_grid_32<block_q8_0, 1>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q8_0,
[](uint8_t * tile_dst, const block_q8_0 * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
ggml_half * scale_dst = (ggml_half *) (tile_dst + 1024);
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt < ne0 / 32) {
for (int row0 = 0; row0 < 32; row0 += 8) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = (const uint8_t *) src_slice[(ct * 32 + row0 + rr) * row_stride + kt].qs;
}
repack_neon_q8_rows(qs, tile_dst, row0);
}
for (int row = 0; row < 32; ++row) {
scale_dst[row] = src_slice[(ct * 32 + row) * row_stride + kt].d;
}
return;
}
#endif
for (int cp = 0; cp < 16; cp++) {
const int col0 = cp * 2;
const int col1 = col0 + 1;
@@ -254,7 +629,6 @@ static void repack_q8_0_tiled(ggml_tensor * t, const void * data, size_t offset,
tile_dst[cp * 64 + 2 * row + 1] = b ? b->qs[col1] : 0;
}
}
ggml_half * scale_dst = (ggml_half *) (tile_dst + 1024);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
scale_dst[row] = (r < ne1 && kt < ne0 / 32) ? src_slice[r * row_stride + kt].d : 0;
@@ -289,11 +663,100 @@ static void repack_tiled_q8_0(void * data, const ggml_tensor * t, size_t offset,
// --- Float Types (F16, F32, BF16 -> f16_tiled HMX layout) ---
static void repack_q2_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
GGML_ASSERT(offset == 0);
GGML_ASSERT(t->ne[0] % 64 == 0);
GGML_ASSERT(size == ggml_nbytes(t));
const block_q2_0 * src = (const block_q2_0 *) data;
const int64_t blocks_per_row = t->ne[0] / 64;
const int64_t n_col_tiles = hex_round_up(t->ne[1], 32) / 32;
const int64_t n_weight_tiles = blocks_per_row;
const size_t matrix_size = (size_t) n_col_tiles * n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
for (int64_t slice = 0; slice < t->ne[2] * t->ne[3]; ++slice) {
const block_q2_0 * src_slice = src + slice * t->ne[1] * blocks_per_row;
uint8_t * dst_slice = (uint8_t *) t->data + slice * matrix_size;
const size_t ct_bytes = (size_t) n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
repack_parallel_for_ct(matrix_size, (int) n_col_tiles, [&](int ct0, int ct1) {
memset(dst_slice + (size_t) ct0 * ct_bytes, 0, (size_t) (ct1 - ct0) * ct_bytes);
const int64_t r0 = (int64_t) ct0 * 32;
const int64_t r1 = (std::min)((int64_t) ct1 * 32, t->ne[1]);
for (int64_t row_index = r0; row_index < r1; ++row_index) {
const int64_t ct = row_index / 32;
const int row = (int) (row_index % 32);
for (int64_t kt = 0; kt < n_weight_tiles; ++kt) {
const block_q2_0 * block = src_slice + row_index * blocks_per_row + kt;
uint8_t * tile = dst_slice + (ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_0_block(block->qs, tile, row);
#else
for (int group = 0; group < 16; ++group) {
for (int byte = 0; byte < 4; ++byte) {
const int k = 4 * group + byte;
const uint8_t code = (block->qs[k / 4] >> (2 * (k % 4))) & 3;
tile[(group >> 2) * 128 + 4 * row + byte] |= code << (2 * (group & 3));
}
}
#endif
memcpy(tile + 512 + 2 * row, &block->d, sizeof(block->d));
}
}
});
}
}
static void repack_tiled_q2_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
GGML_ASSERT(t->ne[0] % 64 == 0);
GGML_ASSERT(offset + size <= ggml_nbytes(t));
const size_t first_block = offset / sizeof(block_q2_0);
const size_t end_block = (offset + size + sizeof(block_q2_0) - 1) / sizeof(block_q2_0);
const size_t blocks_per_row = t->ne[0] / 64;
const size_t blocks_per_slice = t->ne[1] * blocks_per_row;
const size_t n_weight_tiles = blocks_per_row;
const size_t n_col_tiles = hex_round_up(t->ne[1], 32) / 32;
const size_t matrix_size = n_col_tiles * n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
for (size_t block_index = first_block; block_index < end_block; ++block_index) {
const size_t slice = block_index / blocks_per_slice;
const size_t in_slice = block_index % blocks_per_slice;
const size_t row_index = in_slice / blocks_per_row;
const size_t kt = in_slice % blocks_per_row;
const size_t ct = row_index / 32;
const size_t row = row_index % 32;
const uint8_t * tile = (const uint8_t *) t->data + slice * matrix_size +
(ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
block_q2_0 block = {};
memcpy(&block.d, tile + 512 + 2 * row, sizeof(block.d));
for (int group = 0; group < 16; ++group) {
for (int byte = 0; byte < 4; ++byte) {
const uint8_t packed = tile[(group >> 2) * 128 + 4 * row + byte];
const uint8_t code = (packed >> (2 * (group & 3))) & 3;
block.qs[group] |= code << (2 * byte);
}
}
const size_t block_begin = block_index * sizeof(block_q2_0);
const size_t copy_begin = (std::max)(block_begin, offset);
const size_t copy_end = (std::min)(block_begin + sizeof(block_q2_0), offset + size);
memcpy((uint8_t *) data + copy_begin - offset, (const uint8_t *) &block + copy_begin - block_begin, copy_end - copy_begin);
}
}
template <typename SrcT, typename ToFp16Fn>
static void repack_float_to_f16_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size, ToFp16Fn && to_fp16) {
repack_to_tiled_grid_32<SrcT, 32>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_F16,
[&](uint8_t * tile_bytes, const SrcT * src_slice, int64_t ne0, int64_t ne1, int64_t row_stride, int ct, int kt) {
uint16_t * tile_dst = (uint16_t *) tile_bytes;
#if defined(__ARM_NEON) || defined(__aarch64__)
if (ct * 32 + 32 <= ne1 && kt * 32 + 32 <= ne0) {
repack_neon_f16_tile(tile_dst, src_slice, row_stride, ct, kt);
return;
}
#endif
for (int cp = 0; cp < 16; cp++) {
const int64_t c0 = kt * 32 + cp * 2;
const int64_t c1 = c0 + 1;
@@ -350,20 +813,25 @@ static void repack_kquant_to_tiled_grid(ggml_tensor * t, const void * data, size
for (int64_t slice_idx = 0; slice_idx < ne2 * ne3; slice_idx++) {
const BlockT * src_slice = src_matrix + slice_idx * (ne1 * sb_per_row);
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
const size_t ct_bytes = (size_t) n_k_tiles * tile_size;
// Padding rows and OR-ed bit planes require zeroed tiles
memset(matrix_dst, 0, matrix_size);
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
// Padding rows and OR-ed bit planes require zeroed tiles
memset(matrix_dst + (size_t) ct0 * ct_bytes, 0, (size_t) (ct1 - ct0) * ct_bytes);
for (int64_t r = 0; r < ne1; r++) {
const int ct = (int) (r / 32);
const int row = (int) (r % 32);
const BlockT * src_row = src_slice + r * sb_per_row;
const int64_t r0 = (int64_t) ct0 * 32;
const int64_t r1 = (std::min)((int64_t) ct1 * 32, ne1);
for (int64_t r = r0; r < r1; r++) {
const int ct = (int) (r / 32);
const int row = (int) (r % 32);
const BlockT * src_row = src_slice + r * sb_per_row;
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_kt(tile, &src_row[kt / 8], row, kt % 8);
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
pack_kt(tile, &src_row[kt / 8], row, kt % 8);
}
}
}
});
}
}
@@ -419,6 +887,7 @@ static void unpack_kquant_from_tiled_grid(void * data, const ggml_tensor * t, si
// --- K-Quant Helpers & Per-Type Implementations (Q2_K, Q3_K, Q4_K, Q5_K, Q6_K) ---
#if !defined(__ARM_NEON) && !defined(__aarch64__)
// unsigned 6-bit value (0..63) of element e of a Q6_K block, same bit layout as dequantize_row_q6_K
static inline uint8_t q6_K_get_quant(const block_q6_K * b, int e) {
const int c = e / 128;
@@ -436,6 +905,7 @@ static inline uint8_t q6_K_get_quant(const block_q6_K * b, int e) {
}
return (uint8_t) (lo | (hi << 4));
}
#endif
static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
repack_kquant_to_tiled_grid<block_q6_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q6_K,
@@ -445,6 +915,9 @@ static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * hi_pl = tile + 512;
ggml_half * sc_pl = (ggml_half *) (tile + 768);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q6_k(b, kt_local, lo_pl, hi_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const uint8_t q6 = q6_K_get_quant(b, kt_local * 32 + lk);
const int g = lk >> 2;
@@ -452,6 +925,7 @@ static void repack_q6_K_tiled(ggml_tensor * t, const void * data, size_t offset,
lo_pl[(g >> 1) * 128 + pos] |= (uint8_t) ((q6 & 0xF) << ((g & 1) * 4));
hi_pl[(g >> 2) * 128 + pos] |= (uint8_t) ((q6 >> 4) << ((g & 3) * 2));
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = GGML_FP32_TO_FP16(d * (float) b->scales[kt_local * 2 + sub]);
}
@@ -511,6 +985,7 @@ static void repack_tiled_q6_K(void * data, const ggml_tensor * t, size_t offset,
});
}
#if !defined(__ARM_NEON) && !defined(__aarch64__)
// low 2 bits (0..3) of element e of a Q2_K or Q3_K block, same bit layout as dequantize_row_q2_K / q3_K
static inline uint8_t q2_3_K_get_low2(const uint8_t * qs, int e) {
const int c = e / 128;
@@ -526,6 +1001,7 @@ static inline bool q3_K_get_hbit(const block_q3_K * b, int e) {
const int l = e % 32;
return (b->hmask[l] >> (c * 4 + j)) & 1;
}
#endif
// signed 6-bit scale j (-32..31) of a Q3_K block, same packing as quantize_row_q3_K_ref
static inline int q3_K_get_scale(const uint8_t * scales, int j) {
@@ -596,6 +1072,10 @@ static void repack_q3_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * neg_pl = tile + 256;
ggml_half * sc_pl = (ggml_half *) (tile + 384);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_3_k_low2(b->qs, kt_local, lo_pl, row);
repack_neon_q3_k_hmask(b->hmask, kt_local, neg_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const int e = kt_local * 32 + lk;
const int g = lk >> 2;
@@ -605,6 +1085,7 @@ static void repack_q3_K_tiled(ggml_tensor * t, const void * data, size_t offset,
neg_pl[pos] |= (uint8_t) (1 << g);
}
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = GGML_FP32_TO_FP16(d * (float) q3_K_get_scale(b->scales, kt_local * 2 + sub));
}
@@ -676,11 +1157,15 @@ static void repack_q2_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t * sc_pl = tile + 256;
ggml_half * dd_pl = (ggml_half *) (tile + 320);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q2_3_k_low2(b->qs, kt_local, lo_pl, row);
#else
for (int lk = 0; lk < 32; lk++) {
const int g = lk >> 2;
const int pos = row * 4 + (lk & 3);
lo_pl[(g >> 2) * 128 + pos] |= (uint8_t) (q2_3_K_get_low2(b->qs, kt_local * 32 + lk) << ((g & 3) * 2));
}
#endif
for (int sub = 0; sub < 2; sub++) {
sc_pl[sub * 32 + row] = b->scales[kt_local * 2 + sub];
}
@@ -772,23 +1257,46 @@ static inline void pack_k4_scales_mins(uint8_t * scales, ggml_half * d, ggml_hal
}
}
template <typename BlockT>
static inline void repack_k4_quants(uint8_t * tile_dst, const BlockT * b, int row, int kt_local,
int64_t sb_per_row, bool use_neon) {
#if defined(__ARM_NEON) || defined(__aarch64__)
if (use_neon) {
if ((row & 7) == 0) {
const uint8_t * qs[8];
for (int rr = 0; rr < 8; ++rr) {
qs[rr] = b[rr * sb_per_row].qs + (kt_local / 2) * 32;
}
repack_neon_k4_rows(qs, tile_dst, row, (kt_local & 1) != 0);
}
return;
}
#else
GGML_UNUSED(sb_per_row);
GGML_UNUSED(use_neon);
#endif
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
}
}
static void repack_q4_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
const int64_t sb_per_row = t->ne[0] / QK_K;
const bool use_neon = (t->ne[1] % 8 == 0);
repack_kquant_to_tiled_grid<block_q4_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q4_K,
[](uint8_t * tile_dst, const block_q4_K * b, int row, int kt_local) {
[sb_per_row, use_neon](uint8_t * tile_dst, const block_q4_K * b, int row, int kt_local) {
const float d = GGML_FP16_TO_FP32(b->d);
const float dmin = GGML_FP16_TO_FP32(b->dmin);
uint8_t sc, m;
get_scale_min_k4(kt_local, b->scales, &sc, &m);
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
}
repack_k4_quants(tile_dst, b, row, kt_local, sb_per_row, use_neon);
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
scale_dst[2 * row + 0] = GGML_FP32_TO_FP16(d * (float) sc);
@@ -826,8 +1334,10 @@ static void repack_tiled_q4_K(void * data, const ggml_tensor * t, size_t offset,
}
static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
const int64_t sb_per_row = t->ne[0] / QK_K;
const bool use_neon = (t->ne[1] % 8 == 0);
repack_kquant_to_tiled_grid<block_q5_K>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_Q5_K,
[](uint8_t * tile_dst, const block_q5_K * b, int row, int kt_local) {
[sb_per_row, use_neon](uint8_t * tile_dst, const block_q5_K * b, int row, int kt_local) {
const float d = GGML_FP16_TO_FP32(b->d);
const float dmin = GGML_FP16_TO_FP32(b->dmin);
uint8_t * plane = tile_dst + 640;
@@ -835,15 +1345,13 @@ static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset,
uint8_t sc, m;
get_scale_min_k4(kt_local, b->scales, &sc, &m);
const uint8_t * qs_sub = b->qs + (kt_local / 2) * 32;
const int shift = (kt_local & 1) ? 4 : 0;
repack_k4_quants(tile_dst, b, row, kt_local, sb_per_row, use_neon);
const uint8_t hbit = (uint8_t) (1 << kt_local);
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q5_k_qh(b->qh, hbit, plane, row);
#else
for (int cp = 0; cp < 16; cp++) {
const uint8_t q0 = (qs_sub[2 * cp + 0] >> shift) & 0x0F;
const uint8_t q1 = (qs_sub[2 * cp + 1] >> shift) & 0x0F;
tile_dst[cp * 32 + row] = (uint8_t) ((q1 << 4) | q0);
const int i = cp / 4;
const int lane = (cp % 4) * 32 + row;
if (b->qh[2 * cp + 0] & hbit) {
@@ -853,6 +1361,7 @@ static void repack_q5_K_tiled(ggml_tensor * t, const void * data, size_t offset,
plane[lane] |= (uint8_t) (1 << (2 * i + 1));
}
}
#endif
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
scale_dst[2 * row + 0] = GGML_FP32_TO_FP16(d * (float) sc);
@@ -900,10 +1409,128 @@ static void repack_tiled_q5_K(void * data, const ggml_tensor * t, size_t offset,
});
}
static inline void q1_0_tiled_bit_location(int k, int row, int & byte_offset, int & bit) {
const int quarter = k / 32;
const int kq = k % 32;
byte_offset = quarter * 128 + 4 * row + (kq % 4);
bit = kq / 4;
}
static void repack_q1_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
const block_q1_0 * src_matrix = (const block_q1_0 *) data;
const int64_t ne0 = t->ne[0];
const int64_t ne1 = t->ne[1];
const int64_t ne2 = t->ne[2];
const int64_t ne3 = t->ne[3];
const int n_col_tiles = hex_round_up(ne1, 32) / 32;
const int n_k_tiles = ne0 / QK1_0;
const size_t tile_size = HTP_TENSOR_TILE_SIZE_Q1_0;
const size_t matrix_size = (size_t) n_col_tiles * n_k_tiles * tile_size;
const size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
const int64_t start_slice = offset / slice_size;
const int64_t end_slice = (std::min)((int64_t) ((offset + size + slice_size - 1) / slice_size), ne2 * ne3);
GGML_ASSERT(ne0 % QK1_0 == 0);
for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
const block_q1_0 * src_slice = src_matrix + (slice_idx - start_slice) * ne1 * n_k_tiles;
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
repack_parallel_for_ct(matrix_size, n_col_tiles, [&](int ct0, int ct1) {
for (int ct = ct0; ct < ct1; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
memset(tile_dst, 0, tile_size);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r >= ne1) {
continue;
}
const block_q1_0 & block = src_slice[r * n_k_tiles + kt];
scale_dst[row] = block.d;
#if defined(__ARM_NEON) || defined(__aarch64__)
repack_neon_q1_0_block(block.qs, tile_dst, row);
#else
for (int k = 0; k < QK1_0; k++) {
int byte_offset;
int bit;
q1_0_tiled_bit_location(k, row, byte_offset, bit);
if (block.qs[k / 8] & (1u << (k % 8))) {
tile_dst[byte_offset] |= (uint8_t) (1u << bit);
}
}
#endif
}
}
}
});
}
}
static void repack_tiled_q1_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
block_q1_0 * dst_matrix = (block_q1_0 *) data;
const int64_t ne0 = t->ne[0];
const int64_t ne1 = t->ne[1];
const int64_t ne2 = t->ne[2];
const int64_t ne3 = t->ne[3];
const int n_col_tiles = hex_round_up(ne1, 32) / 32;
const int n_k_tiles = ne0 / QK1_0;
const size_t tile_size = HTP_TENSOR_TILE_SIZE_Q1_0;
const size_t matrix_size = (size_t) n_col_tiles * n_k_tiles * tile_size;
const size_t row_size_bytes = ggml_row_size(t->type, ne0);
const size_t slice_size = ne1 * row_size_bytes;
const int64_t start_slice = offset / slice_size;
const int64_t end_slice = (std::min)((int64_t) ((offset + size + slice_size - 1) / slice_size), ne2 * ne3);
GGML_ASSERT(ne0 % QK1_0 == 0);
for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
const size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
const size_t cur_end_byte = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
const int64_t start_row = (cur_start_byte - (size_t) slice_idx * slice_size) / row_size_bytes;
const int64_t end_row = (std::min)((int64_t) ((cur_end_byte - (size_t) slice_idx * slice_size + row_size_bytes - 1) / row_size_bytes), ne1);
block_q1_0 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_q1_0);
const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
for (int ct = start_row / 32; ct < (end_row + 31) / 32; ct++) {
for (int kt = 0; kt < n_k_tiles; kt++) {
const uint8_t * tile_src = matrix_src + ((size_t) ct * n_k_tiles + kt) * tile_size;
const ggml_half * scale_src = (const ggml_half *) (tile_src + 512);
for (int row = 0; row < 32; row++) {
const int64_t r = ct * 32 + row;
if (r < start_row || r >= end_row) {
continue;
}
block_q1_0 & block = dst_slice[(r - start_row) * n_k_tiles + kt];
memset(block.qs, 0, sizeof(block.qs));
block.d = scale_src[row];
for (int k = 0; k < QK1_0; k++) {
int byte_offset;
int bit;
q1_0_tiled_bit_location(k, row, byte_offset, bit);
if (tile_src[byte_offset] & (1u << bit)) {
block.qs[k / 8] |= (uint8_t) (1u << (k % 8));
}
}
}
}
}
}
}
// --- Top-level Repack / Unpack Dispatchers ---
void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t size) {
switch (tensor->type) {
case GGML_TYPE_Q1_0:
repack_q1_0_tiled(tensor, data, 0, size);
break;
case GGML_TYPE_Q4_0:
case GGML_TYPE_IQ4_NL:
repack_q4_0_tiled(tensor, data, 0, size);
@@ -917,6 +1544,9 @@ void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t siz
case GGML_TYPE_MXFP4:
repack_mxfp4_tiled(tensor, data, 0, size);
break;
case GGML_TYPE_Q2_0:
repack_q2_0_tiled(tensor, data, 0, size);
break;
case GGML_TYPE_Q2_K:
repack_q2_K_tiled(tensor, data, 0, size);
break;
@@ -938,11 +1568,11 @@ void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t siz
break;
case GGML_TYPE_F32:
repack_float_to_f16_tiled<float>(tensor, data, 0, size,
[](float v) { return (uint16_t) ggml_fp32_to_fp16(v); });
[](float v) { return (uint16_t) GGML_FP32_TO_FP16(v); });
break;
case GGML_TYPE_BF16:
repack_float_to_f16_tiled<ggml_bf16_t>(tensor, data, 0, size,
[](ggml_bf16_t v) { return (uint16_t) ggml_fp32_to_fp16(ggml_bf16_to_fp32(v)); });
[](ggml_bf16_t v) { return (uint16_t) GGML_FP32_TO_FP16(GGML_BF16_TO_FP32(v)); });
break;
default:
break;
@@ -953,6 +1583,9 @@ void htp_unpack_tensor_tiled(void * data, const ggml_tensor * tensor, size_t off
GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
switch (tensor->type) {
case GGML_TYPE_Q1_0:
repack_tiled_q1_0(data, tensor, offset, size);
break;
case GGML_TYPE_Q4_0:
case GGML_TYPE_IQ4_NL:
repack_tiled_q4_0(data, tensor, offset, size);
@@ -966,6 +1599,9 @@ void htp_unpack_tensor_tiled(void * data, const ggml_tensor * tensor, size_t off
case GGML_TYPE_MXFP4:
repack_tiled_mxfp4(data, tensor, offset, size);
break;
case GGML_TYPE_Q2_0:
repack_tiled_q2_0(data, tensor, offset, size);
break;
case GGML_TYPE_Q2_K:
repack_tiled_q2_K(data, tensor, offset, size);
break;
+6 -1
View File
@@ -10,9 +10,11 @@
static inline bool htp_is_tiled_weight_type(enum ggml_type type) {
return type == GGML_TYPE_Q4_0 || type == GGML_TYPE_Q4_1 ||
type == GGML_TYPE_Q8_0 || type == GGML_TYPE_IQ4_NL ||
type == GGML_TYPE_MXFP4 || type == GGML_TYPE_Q6_K ||
type == GGML_TYPE_MXFP4 || type == GGML_TYPE_Q1_0 ||
type == GGML_TYPE_Q6_K ||
type == GGML_TYPE_Q4_K || type == GGML_TYPE_Q5_K ||
type == GGML_TYPE_Q3_K || type == GGML_TYPE_Q2_K ||
type == GGML_TYPE_Q2_0 ||
type == GGML_TYPE_F16 || type == GGML_TYPE_F32 ||
type == GGML_TYPE_BF16;
}
@@ -42,6 +44,9 @@ static inline size_t htp_tiled_row_size(enum ggml_type type, int64_t ne0) {
if (type == GGML_TYPE_Q6_K) {
return (size_t) (ne0 / 32) * (HTP_TENSOR_TILE_SIZE_Q6_K / 32);
}
if (type == GGML_TYPE_Q2_0) {
return (size_t) (ne0 / 64) * 18;
}
if (type == GGML_TYPE_Q4_K) {
return (size_t) (ne0 / 32) * (HTP_TENSOR_TILE_SIZE_Q4_K / 32);
}
+10 -2
View File
@@ -28,7 +28,7 @@ add_library(${HTP_LIB} SHARED
unary-ops.c
sum-rows-ops.c
softmax-ops.c
act-ops.c
glu-ops.c
rope-ops.c
set-rows-ops.c
get-rows-ops.c
@@ -53,9 +53,17 @@ target_compile_definitions(${HTP_LIB} PRIVATE
$<IF:$<BOOL:${HEXAGON_HTP_DEBUG}>,HTP_DEBUG=1,NDEBUG=1>
$<IF:$<BOOL:${HEXAGON_HTP_DEBUG}>,FARF_HIGH=1,>)
build_idl(htp_iface.idl ${HTP_LIB})
set_target_properties(${HTP_LIB} PROPERTIES EXPORT_COMPILE_COMMANDS ON)
if (NOT Python3_EXECUTABLE)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
endif()
add_custom_command(TARGET ${HTP_LIB} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Adding SWIV section $<TARGET_FILE_NAME:${HTP_LIB}>"
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/htp-swiv.py -i $<TARGET_FILE:${HTP_LIB}> -o $<TARGET_FILE:${HTP_LIB}>
VERBATIM)
install(TARGETS ${HTP_LIB})
+7 -1
View File
@@ -32,6 +32,7 @@ static inline size_t htp_allreduce_vtcm_buffer_count(
}
struct htp_allreduce_kernel_params {
uint8_t kernel_type;
int32_t rank;
int32_t n_ranks;
int32_t n_threads;
@@ -39,7 +40,6 @@ struct htp_allreduce_kernel_params {
int32_t elems_per_thread; // 1D: nelem_per_thread, 2D: nrows_per_thread
int32_t vtcm_size_per_thread;
int32_t vtcm_size;
int32_t kernel_type;
int32_t ne0;
int32_t ne1;
int32_t row_size_aligned;
@@ -50,6 +50,12 @@ struct htp_allreduce_kernel_params {
int32_t mode; // enum htp_allreduce_mode: FULL or SHARDED_FANOUT
};
#if defined(__cplusplus)
static_assert(sizeof(struct htp_allreduce_kernel_params) <= 128, "htp_allreduce_kernel_params is too large for kernel_params blob");
#else
_Static_assert(sizeof(struct htp_allreduce_kernel_params) <= 128, "htp_allreduce_kernel_params is too large for kernel_params blob");
#endif
#ifdef __cplusplus
}
#endif
+100 -97
View File
@@ -453,7 +453,10 @@ static void merge_runs(
struct htp_sort_chunk_ctx {
struct htp_ops_context * octx;
const struct htp_sort_kernel_params * kparams;
uint8_t * vtcm_base;
uint8_t * phase1_base;
size_t phase1_slot_size;
float * merge_values;
int32_t * merge_indices;
uint32_t real_count[HTP_MAX_NTHREADS];
};
@@ -462,26 +465,27 @@ static void htp_sort_chunk_job(unsigned int n, unsigned int i, void * data) {
struct htp_ops_context * octx = cctx->octx;
const struct htp_sort_kernel_params * kparams = cctx->kparams;
const struct htp_tensor * src0 = octx->src[0];
const struct htp_tensor * dst = octx->dst;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
uint32_t ne00 = (uint32_t) kparams->ne00;
uint32_t ne00 = src0->ne[0];
uint32_t chunk_base = i * chunk_elems;
uint32_t real_count = (chunk_base < ne00) ? MIN(chunk_elems, ne00 - chunk_base) : 0;
cctx->real_count[i] = real_count;
uint8_t * spad = cctx->vtcm_base + (size_t) kparams->phase1_slot_size * i;
uint8_t * spad = cctx->phase1_base + cctx->phase1_slot_size * i;
float * values_buf = (float *) spad;
int32_t * indices_buf = (int32_t *) (spad + chunk_elems * sizeof(float));
float * merge_values = (float *) (cctx->vtcm_base + kparams->merge_values_off);
int32_t * merge_indices = (int32_t *) (cctx->vtcm_base + kparams->merge_indices_off);
float * merge_values = cctx->merge_values;
int32_t * merge_indices = cctx->merge_indices;
struct htp_thread_trace * tr = &octx->ctx->trace[i];
const dma_addr_t src_addr = src0->data + (size_t) chunk_base * sizeof(float);
if (kparams->is_top_k) {
uint32_t k = (uint32_t) kparams->k;
uint32_t k = dst->ne[0];
uint32_t local_k = MIN(k, chunk_elems);
if (real_count == 0) {
@@ -559,17 +563,17 @@ static void htp_argsort_merge_l1_job(unsigned int n, unsigned int i, void * data
uint32_t count0 = cctx->real_count[c0];
uint32_t count1 = cctx->real_count[c1];
size_t slot_size = (size_t) kparams->phase1_slot_size;
size_t slot_size = cctx->phase1_slot_size;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
const float * val0 = (const float *) (cctx->vtcm_base + slot_size * c0);
const int32_t * idx0 = (const int32_t *) (cctx->vtcm_base + slot_size * c0 + chunk_elems * sizeof(float));
const float * val1 = (const float *) (cctx->vtcm_base + slot_size * c1);
const int32_t * idx1 = (const int32_t *) (cctx->vtcm_base + slot_size * c1 + chunk_elems * sizeof(float));
const float * val0 = (const float *) (cctx->phase1_base + slot_size * c0);
const int32_t * idx0 = (const int32_t *) (cctx->phase1_base + slot_size * c0 + chunk_elems * sizeof(float));
const float * val1 = (const float *) (cctx->phase1_base + slot_size * c1);
const int32_t * idx1 = (const int32_t *) (cctx->phase1_base + slot_size * c1 + chunk_elems * sizeof(float));
uint32_t out_offset = (2 * i) * chunk_elems;
float * out_val = (float *) (cctx->vtcm_base + kparams->merge_values_off) + out_offset;
int32_t * out_idx = (int32_t *) (cctx->vtcm_base + kparams->merge_indices_off) + out_offset;
float * out_val = cctx->merge_values + out_offset;
int32_t * out_idx = cctx->merge_indices + out_offset;
struct htp_thread_trace * tr = &cctx->octx->ctx->trace[i];
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, (uint16_t) i);
@@ -587,21 +591,21 @@ static void htp_argsort_merge_l2_job(unsigned int n, unsigned int i, void * data
struct htp_sort_chunk_ctx * cctx = mctx->cctx;
const struct htp_sort_kernel_params * kparams = cctx->kparams;
size_t slot_size = (size_t) kparams->phase1_slot_size;
size_t slot_size = cctx->phase1_slot_size;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
uint32_t off0 = (4 * i) * chunk_elems;
uint32_t off1 = (4 * i + 2) * chunk_elems;
const float * val0 = (const float *) (cctx->vtcm_base + kparams->merge_values_off) + off0;
const int32_t * idx0 = (const int32_t *) (cctx->vtcm_base + kparams->merge_indices_off) + off0;
const float * val1 = (const float *) (cctx->vtcm_base + kparams->merge_values_off) + off1;
const int32_t * idx1 = (const int32_t *) (cctx->vtcm_base + kparams->merge_indices_off) + off1;
const float * val0 = cctx->merge_values + off0;
const int32_t * idx0 = cctx->merge_indices + off0;
const float * val1 = cctx->merge_values + off1;
const int32_t * idx1 = cctx->merge_indices + off1;
uint32_t count0 = cctx->real_count[4*i + 0] + cctx->real_count[4*i + 1];
uint32_t count1 = cctx->real_count[4*i + 2] + cctx->real_count[4*i + 3];
uint8_t * slot_base = cctx->vtcm_base + (4 * i) * slot_size;
uint8_t * slot_base = cctx->phase1_base + (4 * i) * slot_size;
float * out_val = (float *) slot_base;
int32_t * out_idx = (int32_t *) (slot_base + 4 * chunk_elems * sizeof(float));
@@ -614,7 +618,10 @@ static void htp_argsort_merge_l2_job(unsigned int n, unsigned int i, void * data
struct htp_sort_multi_row_ctx {
struct htp_ops_context * octx;
const struct htp_sort_kernel_params * kparams;
uint8_t * vtcm_base;
uint8_t * phase1_base;
size_t phase1_slot_size;
uint32_t row_start;
uint32_t row_end;
uint32_t nrows_per_thread;
};
@@ -624,12 +631,18 @@ struct htp_tensor_row_coord {
uint32_t i03;
};
static inline struct htp_tensor_row_coord init_row_coord(uint32_t r, uint32_t ne01, uint32_t ne02) {
static inline struct htp_tensor_row_coord init_row_coord(
uint32_t r,
uint32_t ne01,
uint32_t ne02,
const struct fastdiv_values * div_ne01,
const struct fastdiv_values * div_ne01_ne02
) {
uint32_t ne01_ne02 = ne01 * ne02;
uint32_t i03 = (ne01_ne02 > 0) ? (r / ne01_ne02) : 0;
uint32_t rem = (ne01_ne02 > 0) ? (r % ne01_ne02) : 0;
uint32_t i02 = (ne01 > 0) ? (rem / ne01) : 0;
uint32_t i01 = (ne01 > 0) ? (rem % ne01) : 0;
uint32_t i03 = (ne01_ne02 > 0) ? fastdiv(r, div_ne01_ne02) : 0;
uint32_t rem = (ne01_ne02 > 0) ? (r - i03 * ne01_ne02) : 0;
uint32_t i02 = (ne01 > 0) ? fastdiv(rem, div_ne01) : 0;
uint32_t i01 = (ne01 > 0) ? (rem - i02 * ne01) : 0;
return (struct htp_tensor_row_coord){ i01, i02, i03 };
}
@@ -701,7 +714,7 @@ static void htp_sort_multi_row_job(unsigned int n, unsigned int ith, void * data
const struct htp_tensor * src0 = octx->src[0];
const struct htp_tensor * dst = octx->dst;
uint32_t ne00 = (uint32_t) kparams->ne00;
uint32_t ne00 = src0->ne[0];
uint32_t ne01 = src0->ne[1];
uint32_t ne02 = src0->ne[2];
@@ -713,16 +726,16 @@ static void htp_sort_multi_row_job(unsigned int n, unsigned int ith, void * data
uint32_t nb2 = dst->nb[2];
uint32_t nb3 = dst->nb[3];
uint32_t row_start = (uint32_t) kparams->row_start;
uint32_t row_end = (uint32_t) kparams->row_end;
uint32_t row_start = mctx->row_start;
uint32_t row_end = mctx->row_end;
uint32_t r_start = row_start + ith * mctx->nrows_per_thread;
uint32_t r_end = MIN(r_start + mctx->nrows_per_thread, row_end);
if (r_start >= r_end) return;
size_t spad_offset = ith * (size_t) (kparams->phase1_slot_size * kparams->n_slots);
uint8_t * thread_spad = mctx->vtcm_base + spad_offset;
size_t slot_size = (size_t) kparams->phase1_slot_size;
size_t slot_size = mctx->phase1_slot_size;
size_t spad_offset = ith * slot_size * (size_t) kparams->n_slots;
uint8_t * thread_spad = mctx->phase1_base + spad_offset;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
float * values_buf = (float *) thread_spad;
@@ -732,14 +745,14 @@ static void htp_sort_multi_row_job(unsigned int n, unsigned int ith, void * data
struct htp_thread_trace * tr = &octx->ctx->trace[ith];
bool is_top_k = kparams->is_top_k != 0;
uint32_t k = (uint32_t) kparams->k;
uint32_t k = dst->ne[0];
enum ggml_sort_order order = (enum ggml_sort_order) kparams->order;
const uint32_t src_bytes = ne00 * sizeof(float);
const uint32_t dst_bytes = (is_top_k ? k : ne00) * sizeof(int32_t);
if (kparams->n_slots == 2) {
struct htp_tensor_row_coord prime_coord = init_row_coord(r_start, ne01, ne02);
struct htp_tensor_row_coord prime_coord = init_row_coord(r_start, ne01, ne02, &kparams->div_ne01, &kparams->div_ne01_ne02);
for (uint32_t r = r_start, spad_idx = 0; r < r_end && spad_idx < 2; r++, spad_idx++) {
uint8_t * cur_spad = thread_spad + spad_idx * slot_size;
float * cur_values = (float *) cur_spad;
@@ -759,8 +772,8 @@ static void htp_sort_multi_row_job(unsigned int n, unsigned int ith, void * data
step_row_coord(&prime_coord, ne01, ne02);
}
struct htp_tensor_row_coord dst_coord = init_row_coord(r_start, ne01, ne02);
struct htp_tensor_row_coord next_src_coord = init_row_coord(r_start + 2, ne01, ne02);
struct htp_tensor_row_coord dst_coord = init_row_coord(r_start, ne01, ne02, &kparams->div_ne01, &kparams->div_ne01_ne02);
struct htp_tensor_row_coord next_src_coord = init_row_coord(r_start + 2, ne01, ne02, &kparams->div_ne01, &kparams->div_ne01_ne02);
for (uint32_t r = r_start; r < r_end; r++) {
uint32_t cur_slot = (r - r_start) & 1;
@@ -796,7 +809,7 @@ static void htp_sort_multi_row_job(unsigned int n, unsigned int ith, void * data
dma_queue_flush(dma_q);
} else {
struct htp_tensor_row_coord coord = init_row_coord(r_start, ne01, ne02);
struct htp_tensor_row_coord coord = init_row_coord(r_start, ne01, ne02, &kparams->div_ne01, &kparams->div_ne01_ne02);
for (uint32_t r = r_start; r < r_end; r++) {
dma_addr_t src_addr = src0->data + coord_offset(coord, nb01, nb02, nb03);
dma_addr_t dst_addr = dst->data + coord_offset(coord, nb1, nb2, nb3);
@@ -847,52 +860,39 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
uint32_t ne00 = src0->ne[0];
uint32_t k = dst->ne[0];
struct htp_sort_kernel_params kparams_local;
const struct htp_sort_kernel_params * kparams = (const struct htp_sort_kernel_params *) octx->kernel_params;
if (kparams->n_threads == 0) {
struct htp_sort_vtcm_layout layout;
if (!htp_sort_solve_layout(&layout, ne00, total_rows, k, octx->n_threads, octx->ctx->vtcm_size, is_top_k)) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
memset(&kparams_local, 0, sizeof(kparams_local));
kparams_local.n_threads = (int32_t) layout.n_threads;
kparams_local.total_rows = (int32_t) total_rows;
kparams_local.row_start = (int32_t) row_start;
kparams_local.row_end = (int32_t) row_end;
kparams_local.ne00 = (int32_t) ne00;
kparams_local.k = (int32_t) k;
kparams_local.order = is_top_k ? GGML_SORT_ORDER_DESC : (int32_t) octx->op_params[0];
kparams_local.is_top_k = is_top_k ? 1 : 0;
kparams_local.use_dma = 1;
kparams_local.chunk_elems = (int32_t) layout.chunk_elems;
kparams_local.n_chunks = (int32_t) layout.n_chunks;
kparams_local.vtcm_size = (int32_t) layout.total_bytes;
kparams_local.phase1_slot_size = (int32_t) layout.phase1_slot_size;
kparams_local.merge_values_off = (int32_t) layout.merge_values_off;
kparams_local.merge_indices_off = (int32_t) layout.merge_indices_off;
kparams_local.merge_elems = (int32_t) layout.merge_elems;
kparams_local.n_slots = (int32_t) layout.n_slots;
kparams = &kparams_local;
} else if (octx->ctx->mdev.count > 1) {
memcpy(&kparams_local, kparams, sizeof(kparams_local));
kparams_local.row_start = (int32_t) row_start;
kparams_local.row_end = (int32_t) row_end;
kparams = &kparams_local;
if (!htp_ops_context_set_n_threads(octx, kparams->n_threads)) {
return HTP_STATUS_INVAL_PARAMS;
}
if (octx->ctx->vtcm_size < (size_t) kparams->vtcm_size) {
FARF(ERROR, "sort: VTCM size too small. Needed %d, have %zu", kparams->vtcm_size, octx->ctx->vtcm_size);
struct htp_sort_vtcm_layout layout;
htp_sort_vtcm_layout_build(&layout,
total_rows,
(uint32_t) kparams->chunk_elems,
(uint32_t) kparams->n_chunks,
(uint32_t) kparams->merge_elems,
(uint32_t) kparams->n_threads,
(uint32_t) kparams->n_slots);
if (octx->ctx->vtcm_size < layout.total_bytes) {
FARF(ERROR, "sort: VTCM size too small. Needed %zu, have %zu", layout.total_bytes, octx->ctx->vtcm_size);
return HTP_STATUS_VTCM_TOO_SMALL;
}
uint8_t * vtcm_base = (uint8_t *) octx->ctx->vtcm_base;
uint8_t * vtcm_base = (uint8_t *) octx->ctx->vtcm_base;
uint8_t * phase1_base = VTCM_LAYOUT_PTR(uint8_t, vtcm_base, layout.off_phase1);
float * merge_values = VTCM_LAYOUT_PTR(float, vtcm_base, layout.off_merge_values);
int32_t * merge_indices = VTCM_LAYOUT_PTR(int32_t, vtcm_base, layout.off_merge_indices);
if (total_rows == 1 && kparams->n_chunks > 1) {
struct htp_sort_chunk_ctx cctx;
cctx.octx = octx;
cctx.kparams = kparams;
cctx.vtcm_base = vtcm_base;
cctx.octx = octx;
cctx.kparams = kparams;
cctx.phase1_base = phase1_base;
cctx.phase1_slot_size = layout.phase1_slot_size;
cctx.merge_values = merge_values;
cctx.merge_indices = merge_indices;
memset(cctx.real_count, 0, sizeof(cctx.real_count));
work_queue_run(octx->ctx->work_queue, htp_sort_chunk_job, &cctx, (uint32_t) kparams->n_chunks);
@@ -902,8 +902,8 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
uint32_t local_k = MIN(k, chunk_elems);
if (k == 1 && ne00 >= 128*1024) {
float * cand_vals = (float *) (vtcm_base + kparams->merge_values_off);
int32_t * cand_idxs = (int32_t *) (vtcm_base + kparams->merge_indices_off);
float * cand_vals = merge_values;
int32_t * cand_idxs = merge_indices;
float best_val = cand_vals[0];
int32_t best_idx = cand_idxs[0];
for (int32_t c = 1; c < kparams->n_chunks; c++) {
@@ -918,8 +918,8 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
}
uint32_t total_candidates = (uint32_t) kparams->n_chunks * local_k;
float * cand_vals = (float *) (vtcm_base + kparams->merge_values_off);
int32_t * cand_idxs = (int32_t *) (vtcm_base + kparams->merge_indices_off);
float * cand_vals = merge_values;
int32_t * cand_idxs = merge_indices;
uint32_t merge_elems = (uint32_t) kparams->merge_elems;
if (merge_elems > total_candidates) {
@@ -943,18 +943,18 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
struct htp_argsort_merge_l2_ctx l2ctx = { &cctx, asc };
work_queue_run(octx->ctx->work_queue, htp_argsort_merge_l2_job, &l2ctx, 2);
size_t slot_size = (size_t) kparams->phase1_slot_size;
size_t slot_size = layout.phase1_slot_size;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
const float * val0 = (const float *) vtcm_base;
const int32_t * idx0 = (const int32_t *) (vtcm_base + 4 * chunk_elems * sizeof(float));
const float * val1 = (const float *) (vtcm_base + 4 * slot_size);
const int32_t * idx1 = (const int32_t *) (vtcm_base + 4 * slot_size + 4 * chunk_elems * sizeof(float));
const float * val0 = (const float *) phase1_base;
const int32_t * idx0 = (const int32_t *) (phase1_base + 4 * chunk_elems * sizeof(float));
const float * val1 = (const float *) (phase1_base + 4 * slot_size);
const int32_t * idx1 = (const int32_t *) (phase1_base + 4 * slot_size + 4 * chunk_elems * sizeof(float));
uint32_t n01 = cctx.real_count[0] + cctx.real_count[1] + cctx.real_count[2] + cctx.real_count[3];
uint32_t n23 = cctx.real_count[4] + cctx.real_count[5] + cctx.real_count[6] + cctx.real_count[7];
int32_t * final_idx = (int32_t *) (vtcm_base + kparams->merge_indices_off);
int32_t * final_idx = merge_indices;
struct htp_thread_trace * tr = &octx->ctx->trace[0];
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, 0);
merge_runs(val0, idx0, n01, val1, idx1, n23, NULL, final_idx, asc);
@@ -968,27 +968,27 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
uint32_t n01 = cctx.real_count[0] + cctx.real_count[1];
uint32_t n23 = cctx.real_count[2] + cctx.real_count[3];
const float * val01 = (const float *) (vtcm_base + kparams->merge_values_off);
const int32_t * idx01 = (const int32_t *) (vtcm_base + kparams->merge_indices_off);
const float * val23 = (const float *) (vtcm_base + kparams->merge_values_off) + 2 * chunk_elems;
const int32_t * idx23 = (const int32_t *) (vtcm_base + kparams->merge_indices_off) + 2 * chunk_elems;
const float * val01 = merge_values;
const int32_t * idx01 = merge_indices;
const float * val23 = merge_values + 2 * chunk_elems;
const int32_t * idx23 = merge_indices + 2 * chunk_elems;
int32_t * final_idx = (int32_t *) vtcm_base;
int32_t * final_idx = (int32_t *) phase1_base;
struct htp_thread_trace * tr = &octx->ctx->trace[0];
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, 0);
merge_runs(val01, idx01, n01, val23, idx23, n23, NULL, final_idx, asc);
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, 0);
dma_sync_write(octx->ctx->dma[0], dst->data, final_idx, ne00 * sizeof(int32_t));
} else if (kparams->n_chunks == 2) {
size_t slot_size = (size_t) kparams->phase1_slot_size;
size_t slot_size = layout.phase1_slot_size;
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
const float * val0 = (const float *) (vtcm_base);
const int32_t * idx0 = (const int32_t *) (vtcm_base + chunk_elems * sizeof(float));
const float * val1 = (const float *) (vtcm_base + slot_size);
const int32_t * idx1 = (const int32_t *) (vtcm_base + slot_size + chunk_elems * sizeof(float));
const float * val0 = (const float *) (phase1_base);
const int32_t * idx0 = (const int32_t *) (phase1_base + chunk_elems * sizeof(float));
const float * val1 = (const float *) (phase1_base + slot_size);
const int32_t * idx1 = (const int32_t *) (phase1_base + slot_size + chunk_elems * sizeof(float));
int32_t * final_idx = (int32_t *) (vtcm_base + kparams->merge_indices_off);
int32_t * final_idx = merge_indices;
struct htp_thread_trace * tr = &octx->ctx->trace[0];
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, 0);
merge_runs(val0, idx0, cctx.real_count[0], val1, idx1, cctx.real_count[1], NULL, final_idx, asc);
@@ -996,7 +996,7 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
dma_sync_write(octx->ctx->dma[0], dst->data, final_idx, ne00 * sizeof(int32_t));
} else {
uint32_t chunk_elems = (uint32_t) kparams->chunk_elems;
int32_t * final_idx = (int32_t *) (vtcm_base + chunk_elems * sizeof(float));
int32_t * final_idx = (int32_t *) (phase1_base + chunk_elems * sizeof(float));
dma_sync_write(octx->ctx->dma[0], dst->data, final_idx, ne00 * sizeof(int32_t));
}
}
@@ -1006,7 +1006,10 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
struct htp_sort_multi_row_ctx mctx;
mctx.octx = octx;
mctx.kparams = kparams;
mctx.vtcm_base = vtcm_base;
mctx.phase1_base = phase1_base;
mctx.phase1_slot_size = layout.phase1_slot_size;
mctx.row_start = row_start;
mctx.row_end = row_end;
uint32_t dst_row_bytes = (uint32_t) dst_row_size;
uint32_t rows_per_chunk = 0;
@@ -1014,13 +1017,13 @@ static int op_sort_common(struct htp_ops_context * octx, bool is_top_k) {
if (rows_per_chunk == 0) {
rows_per_chunk = 1;
}
uint32_t dr = (nrows + (uint32_t) kparams->n_threads - 1) / (uint32_t) kparams->n_threads;
uint32_t dr = fastdiv(nrows + octx->n_threads - 1, &octx->n_threads_div);
if (rows_per_chunk > 1) {
dr = hex_round_up(dr, rows_per_chunk);
}
mctx.nrows_per_thread = dr;
work_queue_run(octx->ctx->work_queue, htp_sort_multi_row_job, &mctx, (uint32_t) kparams->n_threads);
work_queue_run(octx->ctx->work_queue, htp_sort_multi_row_job, &mctx, octx->n_threads);
return HTP_STATUS_OK;
}
+58 -73
View File
@@ -7,33 +7,27 @@
#include <string.h>
#include "hex-fastdiv.h"
#include "htp-vtcm.h"
struct htp_sort_kernel_params {
int32_t n_threads;
int32_t total_rows;
int32_t row_start;
int32_t row_end;
int32_t ne00;
int32_t k;
int32_t order; // GGML_SORT_ORDER_ASC (0) or GGML_SORT_ORDER_DESC (1)
int32_t is_top_k; // 1 if TOP_K, 0 if ARGSORT
int32_t use_dma; // 1 if DMA enabled
int32_t chunk_elems;
int32_t n_chunks;
int32_t vtcm_size;
int32_t phase1_slot_size;
int32_t merge_values_off;
int32_t merge_indices_off;
int32_t merge_elems;
int32_t n_slots;
int32_t pad[15];
uint32_t n_threads;
uint32_t order; // GGML_SORT_ORDER_ASC (0) or GGML_SORT_ORDER_DESC (1)
uint32_t is_top_k; // 1 if TOP_K, 0 if ARGSORT
uint32_t chunk_elems;
uint32_t n_chunks;
uint32_t vtcm_size;
uint32_t merge_elems;
uint32_t n_slots;
struct fastdiv_values div_ne01;
struct fastdiv_values div_ne01_ne02;
};
struct htp_sort_vtcm_layout {
size_t total_bytes;
size_t phase1_slot_size;
size_t merge_values_off;
size_t merge_indices_off;
size_t off_phase1;
size_t off_merge_values;
size_t off_merge_indices;
uint32_t chunk_elems;
uint32_t n_chunks;
uint32_t merge_elems;
@@ -41,6 +35,39 @@ struct htp_sort_vtcm_layout {
uint32_t n_slots;
};
static inline void htp_sort_vtcm_layout_build(
struct htp_sort_vtcm_layout * L,
uint32_t total_rows,
uint32_t chunk_elems,
uint32_t n_chunks,
uint32_t merge_elems,
uint32_t n_threads,
uint32_t n_slots) {
memset(L, 0, sizeof(*L));
L->chunk_elems = chunk_elems;
L->n_chunks = n_chunks;
L->merge_elems = merge_elems;
L->n_threads = n_threads;
L->n_slots = n_slots;
const size_t values_size = ((chunk_elems * sizeof(float)) + 127) & ~127;
const size_t indices_size = ((chunk_elems * sizeof(int32_t)) + 127) & ~127;
L->phase1_slot_size = ((values_size + indices_size) + 255) & ~255;
size_t off = 0;
if (total_rows > 1) {
VTCM_LAYOUT_ALLOC(off, off_phase1, L->phase1_slot_size * n_slots * n_threads);
} else {
VTCM_LAYOUT_ALLOC(off, off_phase1, L->phase1_slot_size * n_chunks * n_slots);
const size_t merge_values_size = ((merge_elems * sizeof(float)) + 127) & ~127;
const size_t merge_indices_size = ((merge_elems * sizeof(int32_t)) + 127) & ~127;
VTCM_LAYOUT_ALLOC_OPTIONAL(off, off_merge_values, merge_values_size, n_chunks > 1);
VTCM_LAYOUT_ALLOC_OPTIONAL(off, off_merge_indices, merge_indices_size, n_chunks > 1);
}
L->total_bytes = off;
}
static inline bool htp_sort_solve_layout(
struct htp_sort_vtcm_layout * layout,
uint32_t ne00,
@@ -75,18 +102,8 @@ static inline bool htp_sort_solve_layout(
n_threads--;
}
size_t total_bytes = spad_per_thread * n_threads;
if (total_bytes > vtcm_budget) {
return false;
}
layout->total_bytes = total_bytes;
layout->phase1_slot_size = spad_per_slot;
layout->chunk_elems = ne00_padded;
layout->n_chunks = 1;
layout->n_threads = n_threads;
layout->n_slots = n_slots;
return true;
htp_sort_vtcm_layout_build(layout, total_rows, ne00_padded, 1, 0, n_threads, n_slots);
return layout->total_bytes <= vtcm_budget;
}
uint32_t n_vec = (ne00 + 31) / 32;
@@ -102,17 +119,7 @@ static inline bool htp_sort_solve_layout(
uint32_t chunk_n_vec = n_vec_pow2 / n_chunks;
uint32_t chunk_elems = chunk_n_vec * 32;
size_t phase1_values_size = ((chunk_elems * sizeof(float)) + 127) & ~127;
size_t phase1_indices_size = ((chunk_elems * sizeof(int32_t)) + 127) & ~127;
size_t phase1_slot_size = ((phase1_values_size + phase1_indices_size) + 255) & ~255;
size_t phase1_total_size = phase1_slot_size * n_chunks;
size_t merge_values_size = 0;
size_t merge_indices_size = 0;
size_t merge_values_off = phase1_total_size;
size_t merge_indices_off = 0;
uint32_t merge_elems = 0;
uint32_t merge_elems = 0;
if (n_chunks > 1) {
if (is_top_k) {
@@ -125,47 +132,25 @@ static inline bool htp_sort_solve_layout(
} else {
merge_elems = n_vec_pow2 * 32;
}
merge_values_size = ((merge_elems * sizeof(float)) + 127) & ~127;
merge_indices_size = ((merge_elems * sizeof(int32_t)) + 127) & ~127;
merge_indices_off = merge_values_off + merge_values_size;
}
size_t total_bytes = phase1_total_size + merge_values_size + merge_indices_size;
htp_sort_vtcm_layout_build(layout, 1, chunk_elems, n_chunks, merge_elems, n_chunks, 1);
if (total_bytes > vtcm_budget && n_chunks > 1) {
n_chunks = 1;
if (layout->total_bytes > vtcm_budget && n_chunks > 1) {
n_chunks = 1;
chunk_elems = n_vec_pow2 * 32;
phase1_values_size = ((chunk_elems * sizeof(float)) + 127) & ~127;
phase1_indices_size = ((chunk_elems * sizeof(int32_t)) + 127) & ~127;
phase1_slot_size = ((phase1_values_size + phase1_indices_size) + 255) & ~255;
phase1_total_size = phase1_slot_size;
merge_values_size = 0;
merge_indices_size = 0;
merge_values_off = phase1_total_size;
merge_indices_off = 0;
merge_elems = 0;
total_bytes = phase1_total_size;
merge_elems = 0;
htp_sort_vtcm_layout_build(layout, 1, chunk_elems, n_chunks, merge_elems, 1, 1);
}
if (total_bytes > vtcm_budget) {
if (layout->total_bytes > vtcm_budget) {
return false;
}
uint32_t n_slots = 1;
if (n_chunks == 1 && phase1_slot_size * 2 <= vtcm_budget) {
n_slots = 2;
total_bytes = phase1_slot_size * 2;
if (n_chunks == 1 && layout->phase1_slot_size * 2 <= vtcm_budget) {
htp_sort_vtcm_layout_build(layout, 1, chunk_elems, 1, 0, 1, 2);
}
layout->total_bytes = total_bytes;
layout->phase1_slot_size = phase1_slot_size;
layout->merge_values_off = merge_values_off;
layout->merge_indices_off = merge_indices_off;
layout->chunk_elems = chunk_elems;
layout->n_chunks = n_chunks;
layout->merge_elems = merge_elems;
layout->n_threads = n_chunks;
layout->n_slots = n_slots;
return true;
}
+129 -86
View File
@@ -42,6 +42,7 @@ struct htp_binary_context {
uint32_t nrows_per_thread;
uint32_t total_rows;
uint32_t row_start;
uint32_t n_srcs;
size_t src0_row_size_aligned;
size_t src1_row_size_aligned;
size_t dst_row_size_aligned;
@@ -380,6 +381,7 @@ static void binary_thread_vector_same_shape(unsigned int nth, unsigned int ith,
size_t dst_spad_half = layout->dst_spad_half_size;
dma_queue * dma_q = octx->ctx->dma[ith];
const uint32_t n_srcs = bctx->n_srcs;
uint32_t ir_prefetch = start_row;
int spad_idx = 0;
@@ -391,12 +393,7 @@ static void binary_thread_vector_same_shape(unsigned int nth, unsigned int ith,
i02 = fastdiv(rem, &bctx->src0_dim1_div);
i01 = rem - i02 * ne01;
uint32_t i13 = (ne13 == 1) ? 0 : i03;
uint32_t i12 = (ne12 == 1) ? 0 : i02;
uint32_t i11 = (ne11 == 1) ? 0 : i01;
dma_addr_t src0_curr = src0->data + i03 * nb03 + i02 * nb02 + i01 * nb01;
dma_addr_t src1_curr = src1->data + i13 * nb13 + i12 * nb12 + i11 * nb11;
dma_addr_t dst_curr = dst->data + i03 * nb3 + i02 * nb2 + i01 * nb1;
uint8_t * s0_spad = src0_spad_base + spad_idx * src0_spad_half;
@@ -405,7 +402,15 @@ static void binary_thread_vector_same_shape(unsigned int nth, unsigned int ith,
dma_queue_push(dma_q, dma_make_data(dst_curr, d_spad), nb1, bctx->dst_row_size_aligned, row_size_bytes, 0);
dma_queue_push(dma_q, dma_make_data(s0_spad, src0_curr), bctx->src0_row_size_aligned, nb01, row_size_bytes, current_block_size);
dma_queue_push(dma_q, dma_make_data(s1_spad, src1_curr), bctx->src1_row_size_aligned, nb11, row_size_bytes, current_block_size);
for (uint32_t s = 1; s < n_srcs; s++) {
const struct htp_tensor * src_s = octx->src[s];
uint32_t is3 = (src_s->ne[3] == 1) ? 0 : i03;
uint32_t is2 = (src_s->ne[2] == 1) ? 0 : i02;
uint32_t is1 = (src_s->ne[1] == 1) ? 0 : i01;
dma_addr_t s_curr = src_s->data + is3 * src_s->nb[3] + is2 * src_s->nb[2] + is1 * src_s->nb[1];
uint8_t * s_spad = s1_spad + (s - 1) * 2 * src1_spad_half;
dma_queue_push(dma_q, dma_make_data(s_spad, s_curr), bctx->src1_row_size_aligned, src_s->nb[1], row_size_bytes, current_block_size);
}
ir_prefetch += current_block_size;
spad_idx ^= 1;
}
@@ -422,6 +427,11 @@ static void binary_thread_vector_same_shape(unsigned int nth, unsigned int ith,
htp_trace_event_start(tr, HTP_TRACE_EVT_HVX_COMP, (uint16_t) ir);
compute(d_spad, s0_spad, s1_spad, current_block_size,
bctx->dst_row_size_aligned, bctx->src0_row_size_aligned, bctx->src1_row_size_aligned, ne00);
for (uint32_t s = 2; s < n_srcs; s++) {
uint8_t * ss_spad = (uint8_t *) dma_queue_pop(dma_q).dst;
compute(d_spad, d_spad, ss_spad, current_block_size,
bctx->dst_row_size_aligned, bctx->dst_row_size_aligned, bctx->src1_row_size_aligned, ne00);
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, (uint16_t) ir);
uint32_t i03, i02, i01, rem;
@@ -440,15 +450,18 @@ static void binary_thread_vector_same_shape(unsigned int nth, unsigned int ith,
p02 = fastdiv(prem, &bctx->src0_dim1_div);
p01 = prem - p02 * ne01;
uint32_t p13 = (ne13 == 1) ? 0 : p03;
uint32_t p12 = (ne12 == 1) ? 0 : p02;
uint32_t p11 = (ne11 == 1) ? 0 : p01;
dma_addr_t s0_next = src0->data + p03 * nb03 + p02 * nb02 + p01 * nb01;
dma_addr_t s1_next = src1->data + p13 * nb13 + p12 * nb12 + p11 * nb11;
dma_queue_push(dma_q, dma_make_data(s0_spad, s0_next), bctx->src0_row_size_aligned, nb01, row_size_bytes, next_block_size);
dma_queue_push(dma_q, dma_make_data(s1_spad, s1_next), bctx->src1_row_size_aligned, nb11, row_size_bytes, next_block_size);
for (uint32_t s = 1; s < n_srcs; s++) {
const struct htp_tensor * src_s = octx->src[s];
uint32_t ps3 = (src_s->ne[3] == 1) ? 0 : p03;
uint32_t ps2 = (src_s->ne[2] == 1) ? 0 : p02;
uint32_t ps1 = (src_s->ne[1] == 1) ? 0 : p01;
dma_addr_t s_next = src_s->data + ps3 * src_s->nb[3] + ps2 * src_s->nb[2] + ps1 * src_s->nb[1];
uint8_t * s_spad = s1_spad + (s - 1) * 2 * src1_spad_half;
dma_queue_push(dma_q, dma_make_data(s_spad, s_next), bctx->src1_row_size_aligned, src_s->nb[1], row_size_bytes, next_block_size);
}
ir_prefetch += next_block_size;
}
@@ -859,6 +872,7 @@ struct binary_chunked_context {
uint32_t chunk_size;
uint32_t chunks_per_thread;
uint32_t total_chunks;
uint32_t n_srcs;
bool is_scalar;
float scalar_f32;
_Float16 scalar_f16;
@@ -872,7 +886,6 @@ static void binary_thread_chunked(unsigned int nth, unsigned int ith, void * dat
struct binary_chunked_context * ctx = (struct binary_chunked_context *) data;
struct htp_ops_context * octx = ctx->octx;
const struct htp_tensor * src0 = octx->src[0];
const struct htp_tensor * src1 = octx->src[1];
const struct htp_tensor * dst = octx->dst;
const uint32_t start_chunk = ctx->chunks_per_thread * ith;
@@ -895,6 +908,7 @@ static void binary_thread_chunked(unsigned int nth, unsigned int ith, void * dat
uint8_t * dst_spad_base = VTCM_LAYOUT_PTR(uint8_t, ctx->vtcm_base, layout->off_dst) + (ith * layout->dst_bytes_per_thread);
dma_queue * dma_q = octx->ctx->dma[ith];
const uint32_t n_srcs = ctx->n_srcs;
uint32_t prefetch_chunk = start_chunk;
int spad_idx = 0;
@@ -913,9 +927,12 @@ static void binary_thread_chunked(unsigned int nth, unsigned int ith, void * dat
dma_queue_push(dma_q, dma_make_data(d_curr, d_spad), chunk_bytes, chunk_bytes, cur_bytes, 0);
dma_queue_push(dma_q, dma_make_data(s0_spad, s0_curr), chunk_bytes, chunk_bytes, cur_bytes, 1);
if (!ctx->is_scalar) {
dma_addr_t s1_curr = src1->data + (size_t) c_start * elem_size;
uint8_t * s1_spad = src1_spad_base + spad_idx * chunk_bytes;
dma_queue_push(dma_q, dma_make_data(s1_spad, s1_curr), chunk_bytes, chunk_bytes, cur_bytes, 1);
for (uint32_t s = 1; s < n_srcs; s++) {
dma_addr_t s_curr = octx->src[s]->data + (size_t) c_start * elem_size;
uint8_t * s_spad = s1_spad + (s - 1) * 2 * chunk_bytes;
dma_queue_push(dma_q, dma_make_data(s_spad, s_curr), chunk_bytes, chunk_bytes, cur_bytes, 1);
}
}
prefetch_chunk++;
@@ -943,6 +960,10 @@ static void binary_thread_chunked(unsigned int nth, unsigned int ith, void * dat
}
} else {
ctx->compute(d_spad, s0_spad, s1_spad, cur_elems);
for (uint32_t s = 2; s < n_srcs; s++) {
uint8_t * s_spad = (uint8_t *) dma_queue_pop(dma_q).dst;
ctx->compute(d_spad, d_spad, s_spad, cur_elems);
}
}
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_COMP, (uint16_t) c);
@@ -958,8 +979,11 @@ static void binary_thread_chunked(unsigned int nth, unsigned int ith, void * dat
dma_addr_t s0_next = src0->data + (size_t) pc_start * elem_size;
dma_queue_push(dma_q, dma_make_data(s0_spad, s0_next), chunk_bytes, chunk_bytes, p_bytes, 1);
if (!ctx->is_scalar) {
dma_addr_t s1_next = src1->data + (size_t) pc_start * elem_size;
dma_queue_push(dma_q, dma_make_data(s1_spad, s1_next), chunk_bytes, chunk_bytes, p_bytes, 1);
for (uint32_t s = 1; s < n_srcs; s++) {
dma_addr_t s_next = octx->src[s]->data + (size_t) pc_start * elem_size;
uint8_t * s_spad = s1_spad + (s - 1) * 2 * chunk_bytes;
dma_queue_push(dma_q, dma_make_data(s_spad, s_next), chunk_bytes, chunk_bytes, p_bytes, 1);
}
}
prefetch_chunk++;
@@ -974,6 +998,7 @@ static int execute_op_binary(struct htp_ops_context * octx) {
const struct htp_tensor * src1 = octx->src[1];
const struct htp_tensor * dst = octx->dst;
const struct htp_binary_kernel_params * kparams = (const struct htp_binary_kernel_params *) octx->kernel_params;
const uint32_t n_srcs = kparams->n_srcs > 2 ? kparams->n_srcs : 2;
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
@@ -985,7 +1010,7 @@ static int execute_op_binary(struct htp_ops_context * octx) {
const size_t dst_row_size = dst->ne[0] * elem_size;
if (kparams->kernel_type == HTP_BINARY_KERNEL_CHUNKED) {
const bool is_scalar = kparams->is_scalar || (src1->ne[0] == 1 && src1->ne[1] == 1 && src1->ne[2] == 1 && src1->ne[3] == 1);
const bool is_scalar = kparams->is_scalar;
compute_binary_chunked_t compute = NULL;
compute_binary_scalar_chunked_f32_t compute_scalar_f32 = NULL;
@@ -1045,12 +1070,16 @@ static int execute_op_binary(struct htp_ops_context * octx) {
const uint32_t elems_per_line = (src0_type == HTP_TYPE_F32) ? 32 : 64;
if (octx->ctx->mdev.count > 1) {
const bool can_split = htp_tensor_mdev_data_aligned(dst) &&
htp_tensor_mdev_data_aligned(src0) &&
(is_scalar || htp_tensor_mdev_data_aligned(src1)) &&
htp_tensor_is_contiguous(dst, elem_size) &&
htp_tensor_is_contiguous(src0, elem_size) &&
(is_scalar || htp_tensor_is_contiguous(src1, elem_size));
bool can_split = htp_tensor_mdev_data_aligned(dst) &&
htp_tensor_mdev_data_aligned(src0) &&
(is_scalar || htp_tensor_mdev_data_aligned(src1)) &&
htp_tensor_is_contiguous(dst, elem_size) &&
htp_tensor_is_contiguous(src0, elem_size) &&
(is_scalar || htp_tensor_is_contiguous(src1, elem_size));
for (uint32_t s = 2; can_split && s < n_srcs; s++) {
can_split = htp_tensor_mdev_data_aligned(octx->src[s]) &&
htp_tensor_is_contiguous(octx->src[s], elem_size);
}
const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(
total_elems, can_split ? elems_per_line : 0,
octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
@@ -1072,7 +1101,7 @@ static int execute_op_binary(struct htp_ops_context * octx) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
const uint32_t chunk_size = kparams->chunk_size > 0 ? kparams->chunk_size : (32768 / elem_size);
const uint32_t chunk_size = kparams->chunk_size;
const uint32_t total_chunks = (nelem + chunk_size - 1) / chunk_size;
const uint32_t n_threads = (total_chunks >= 2) ? MIN(octx->n_threads, total_chunks) : 1;
const uint32_t chunks_per_thread = (total_chunks + n_threads - 1) / n_threads;
@@ -1101,6 +1130,7 @@ static int execute_op_binary(struct htp_ops_context * octx) {
.chunk_size = chunk_size,
.chunks_per_thread = chunks_per_thread,
.total_chunks = total_chunks,
.n_srcs = n_srcs,
.is_scalar = is_scalar,
.scalar_f32 = scalar_f32,
.scalar_f16 = scalar_f16,
@@ -1146,65 +1176,6 @@ static int execute_op_binary(struct htp_ops_context * octx) {
return HTP_STATUS_VTCM_TOO_SMALL;
}
dma_queue * dma_q = octx->ctx->dma[0];
uint8_t * vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src1);
if (kparams->kernel_type == HTP_BINARY_KERNEL_ROW_BCAST) {
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data), bctx.vtcm_layout.src1_size, 0, src1->ne[0] * elem_size, 1);
} else if (kparams->kernel_type == HTP_BINARY_KERNEL_SCALAR ||
kparams->kernel_type == HTP_BINARY_KERNEL_REPEAT ||
kparams->kernel_type == HTP_BINARY_KERNEL_COMPLEX) {
if (!kparams->src1_strided) {
const size_t src1_bytes = (size_t) src1->ne[0] * src1->ne[1] * src1->ne[2] * src1->ne[3] * elem_size;
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data), bctx.vtcm_layout.src1_size, 0, src1_bytes, 1);
} else {
const uint32_t ne11 = src1->ne[1];
const uint32_t ne12 = src1->ne[2];
const uint32_t ne13 = src1->ne[3];
const size_t row_bytes = src1->ne[0] * elem_size;
for (uint32_t i13 = 0; i13 < ne13; i13++) {
for (uint32_t i12 = 0; i12 < ne12; i12++) {
dma_addr_t src_addr = src1->data + i13 * src1->nb[3] + i12 * src1->nb[2];
uint8_t * dst_ptr = vtcm_src1 + (i13 * ne12 * ne11 + i12 * ne11) * row_bytes;
while (!dma_queue_push(dma_q, dma_make_data(dst_ptr, src_addr),
row_bytes, src1->nb[1], row_bytes, ne11)) {
dma_queue_pop(dma_q);
}
}
}
}
} else if (kparams->kernel_type == HTP_BINARY_KERNEL_ADD_ID) {
const struct htp_tensor * src2 = octx->src[2];
uint8_t * vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src2);
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data),
kparams->src1_row_size_aligned, src1->nb[1],
src1->ne[0] * elem_size, src1->ne[1]);
const size_t src2_row_bytes = src2->ne[0] * sizeof(int32_t);
dma_queue_push(dma_q, dma_make_data(vtcm_src2, src2->data),
src2_row_bytes, src2->nb[1],
src2_row_bytes, src2->ne[1]);
}
bctx.octx = octx;
bctx.nrows_per_thread = fastdiv(nrows + n_threads - 1, &octx->n_threads_div);
bctx.total_rows = nrows;
bctx.row_start = row_start;
bctx.block_max = bctx.vtcm_layout.rows_per_buffer;
bctx.src0_row_size_aligned = src0_row_size_aligned;
bctx.src1_row_size_aligned = src1_row_size_aligned;
bctx.dst_row_size_aligned = dst_row_size_aligned;
bctx.row_size_bytes = src0_row_size;
bctx.src0_dim1_div = init_fastdiv_values(src0->ne[1]);
bctx.src0_dim2_div = init_fastdiv_values(src0->ne[2]);
bctx.src0_dim12_div = init_fastdiv_values(src0->ne[1] * src0->ne[2]);
bctx.src1_dim1_div = init_fastdiv_values(src1->ne[1]);
bctx.src1_dim2_div = init_fastdiv_values(src1->ne[2]);
bctx.src1_dim3_div = init_fastdiv_values(src1->ne[3]);
bctx.split_at_ne01 = kparams->split_at_ne01;
bctx.split_at_ne02 = kparams->split_at_ne02;
worker_callback_t worker_func = NULL;
void * compute_func = NULL;
@@ -1322,7 +1293,70 @@ static int execute_op_binary(struct htp_ops_context * octx) {
return HTP_STATUS_NO_SUPPORT;
}
bctx.compute = compute_func;
dma_queue * dma_q = octx->ctx->dma[0];
uint8_t * vtcm_src1 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src1);
if (kparams->kernel_type == HTP_BINARY_KERNEL_ROW_BCAST) {
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data), bctx.vtcm_layout.src1_size, 0, src1->ne[0] * elem_size, 1);
} else if (kparams->kernel_type == HTP_BINARY_KERNEL_SCALAR ||
kparams->kernel_type == HTP_BINARY_KERNEL_REPEAT ||
kparams->kernel_type == HTP_BINARY_KERNEL_COMPLEX) {
if (!kparams->src1_strided) {
const size_t src1_bytes = (size_t) src1->ne[0] * src1->ne[1] * src1->ne[2] * src1->ne[3] * elem_size;
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data), bctx.vtcm_layout.src1_size, 0, src1_bytes, 1);
if (kparams->kernel_type == HTP_BINARY_KERNEL_SCALAR && octx->src[2] != NULL) {
uint8_t * vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src2);
dma_queue_push(dma_q, dma_make_data(vtcm_src2, octx->src[2]->data), bctx.vtcm_layout.src2_size, 0, src1_bytes, 1);
}
} else {
const uint32_t ne11 = src1->ne[1];
const uint32_t ne12 = src1->ne[2];
const uint32_t ne13 = src1->ne[3];
const size_t row_bytes = src1->ne[0] * elem_size;
for (uint32_t i13 = 0; i13 < ne13; i13++) {
for (uint32_t i12 = 0; i12 < ne12; i12++) {
dma_addr_t src_addr = src1->data + i13 * src1->nb[3] + i12 * src1->nb[2];
uint8_t * dst_ptr = vtcm_src1 + (i13 * ne12 * ne11 + i12 * ne11) * row_bytes;
while (!dma_queue_push(dma_q, dma_make_data(dst_ptr, src_addr),
row_bytes, src1->nb[1], row_bytes, ne11)) {
dma_queue_pop(dma_q);
}
}
}
}
} else if (kparams->kernel_type == HTP_BINARY_KERNEL_ADD_ID) {
const struct htp_tensor * src2 = octx->src[2];
uint8_t * vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src2);
dma_queue_push(dma_q, dma_make_data(vtcm_src1, src1->data),
kparams->src1_row_size_aligned, src1->nb[1],
src1->ne[0] * elem_size, src1->ne[1]);
const size_t src2_row_bytes = src2->ne[0] * sizeof(int32_t);
dma_queue_push(dma_q, dma_make_data(vtcm_src2, src2->data),
src2_row_bytes, src2->nb[1],
src2_row_bytes, src2->ne[1]);
}
bctx.octx = octx;
bctx.nrows_per_thread = fastdiv(nrows + n_threads - 1, &octx->n_threads_div);
bctx.total_rows = nrows;
bctx.row_start = row_start;
bctx.n_srcs = n_srcs;
bctx.block_max = bctx.vtcm_layout.rows_per_buffer;
bctx.src0_row_size_aligned = src0_row_size_aligned;
bctx.src1_row_size_aligned = src1_row_size_aligned;
bctx.dst_row_size_aligned = dst_row_size_aligned;
bctx.row_size_bytes = src0_row_size;
bctx.src0_dim1_div = kparams->div_ne01;
bctx.src0_dim2_div = kparams->div_ne02;
bctx.src0_dim12_div = kparams->div_ne012;
bctx.src1_dim1_div = kparams->div_ne11;
bctx.src1_dim2_div = kparams->div_ne12;
bctx.src1_dim3_div = kparams->div_ne13;
bctx.split_at_ne01 = kparams->split_at_ne01;
bctx.split_at_ne02 = kparams->split_at_ne02;
bctx.compute = compute_func;
if (kparams->kernel_type == HTP_BINARY_KERNEL_ROW_BCAST ||
kparams->kernel_type == HTP_BINARY_KERNEL_SCALAR ||
@@ -1330,6 +1364,15 @@ static int execute_op_binary(struct htp_ops_context * octx) {
kparams->kernel_type == HTP_BINARY_KERNEL_COMPLEX ||
kparams->kernel_type == HTP_BINARY_KERNEL_ADD_ID) {
dma_queue_flush(dma_q);
if (kparams->kernel_type == HTP_BINARY_KERNEL_SCALAR && octx->src[2] != NULL && octx->op == HTP_OP_MUL) {
uint8_t * vtcm_src2 = VTCM_LAYOUT_PTR(uint8_t, bctx.vtcm_base, bctx.vtcm_layout.off_src2);
const uint32_t n_scalars = src1->ne[0] * src1->ne[1] * src1->ne[2] * src1->ne[3];
if (src0_type == HTP_TYPE_F32) {
hvx_mul_f32_aaa(vtcm_src1, vtcm_src1, vtcm_src2, n_scalars);
} else if (src0_type == HTP_TYPE_F16) {
hvx_mul_f16_aaa(vtcm_src1, vtcm_src1, vtcm_src2, n_scalars);
}
}
}
work_queue_run(octx->ctx->work_queue, worker_func, &bctx, n_threads);
+77 -11
View File
@@ -6,6 +6,7 @@
#include <string.h>
#include "hex-common.h"
#include "hex-fastdiv.h"
#include "htp-ops.h"
#include "htp-vtcm.h"
@@ -20,7 +21,15 @@ enum htp_binary_kernel_type {
};
struct htp_binary_kernel_params {
uint32_t kernel_type;
uint8_t kernel_type;
uint8_t split_at_ne01;
uint8_t split_at_ne02;
uint8_t src1_strided;
uint8_t n_srcs;
uint8_t is_scalar;
uint8_t pad[2];
uint32_t n_threads;
uint32_t rows_per_buffer;
@@ -34,12 +43,14 @@ struct htp_binary_kernel_params {
uint32_t chunk_size;
uint32_t chunk_bytes;
uint32_t is_scalar;
uint8_t split_at_ne01;
uint8_t split_at_ne02;
uint8_t src1_strided;
uint8_t pad;
struct fastdiv_values div_ne01;
struct fastdiv_values div_ne02;
struct fastdiv_values div_ne012;
struct fastdiv_values div_ne11;
struct fastdiv_values div_ne12;
struct fastdiv_values div_ne13;
};
#if defined(__cplusplus)
@@ -100,6 +111,8 @@ static inline void htp_binary_vtcm_layout_build(
return;
}
const uint32_t n_srcs = kparams->n_srcs > 2 ? kparams->n_srcs : 2;
if (kparams->kernel_type == HTP_BINARY_KERNEL_CHUNKED) {
const size_t chunk_bytes = kparams->chunk_bytes;
if (chunk_bytes == 0) {
@@ -107,7 +120,7 @@ static inline void htp_binary_vtcm_layout_build(
}
L->src0_bytes_per_thread = 2 * chunk_bytes;
L->src1_bytes_per_thread = kparams->is_scalar ? 0 : (2 * chunk_bytes);
L->src1_bytes_per_thread = kparams->is_scalar ? 0 : (2 * (n_srcs - 1) * chunk_bytes);
L->dst_bytes_per_thread = 2 * chunk_bytes;
L->src0_spad_half_size = chunk_bytes;
@@ -135,7 +148,7 @@ static inline void htp_binary_vtcm_layout_build(
}
const size_t spad_row_total = (kparams->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE)
? 2 * (kparams->src0_row_size_aligned + kparams->src1_row_size_aligned + kparams->dst_row_size_aligned)
? 2 * (kparams->src0_row_size_aligned + (n_srcs - 1) * kparams->src1_row_size_aligned + kparams->dst_row_size_aligned)
: 2 * (kparams->src0_row_size_aligned + kparams->dst_row_size_aligned);
const size_t static_vtcm_size = kparams->src1_size + kparams->src2_size;
@@ -143,7 +156,9 @@ static inline void htp_binary_vtcm_layout_build(
return;
}
const size_t rows_per_buffer = (vtcm_size - static_vtcm_size) / (n_threads * spad_row_total);
const size_t rows_per_buffer = (kparams->rows_per_buffer > 0)
? kparams->rows_per_buffer
: (vtcm_size - static_vtcm_size) / (n_threads * spad_row_total);
if (rows_per_buffer == 0) {
return;
}
@@ -155,11 +170,13 @@ static inline void htp_binary_vtcm_layout_build(
L->src0_bytes_per_thread = rows_per_buffer * 2 * kparams->src0_row_size_aligned;
L->dst_bytes_per_thread = rows_per_buffer * 2 * kparams->dst_row_size_aligned;
L->src1_bytes_per_thread = (kparams->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE)
? rows_per_buffer * 2 * kparams->src1_row_size_aligned
? rows_per_buffer * 2 * (n_srcs - 1) * kparams->src1_row_size_aligned
: 0;
L->src0_spad_half_size = L->src0_bytes_per_thread / 2;
L->src1_spad_half_size = L->src1_bytes_per_thread / 2;
L->src1_spad_half_size = (kparams->kernel_type == HTP_BINARY_KERNEL_SAME_SHAPE)
? rows_per_buffer * kparams->src1_row_size_aligned
: 0;
L->dst_spad_half_size = L->dst_bytes_per_thread / 2;
const size_t src0_total = n_threads * L->src0_bytes_per_thread;
@@ -178,4 +195,53 @@ static inline void htp_binary_vtcm_layout_build(
L->total_bytes = off;
}
static inline bool htp_binary_solve_chunked(
struct htp_binary_vtcm_layout * L,
struct htp_binary_kernel_params * kparams,
uint32_t total_elems,
uint32_t elem_size,
uint32_t max_threads,
uint32_t n_srcs,
bool is_scalar,
size_t vtcm_size
) {
const size_t n_bufs = is_scalar ? 2 : (size_t) n_srcs + 1;
const size_t scalar_bytes = is_scalar ? 128 : 0;
const size_t budget_per_buf = (max_threads > 0 && vtcm_size > scalar_bytes)
? hex_align_down((vtcm_size - scalar_bytes) / (2 * max_threads * n_bufs), 128)
: 0;
const uint32_t max_chunk_bytes = (budget_per_buf > 0 && budget_per_buf < 32768) ? (uint32_t) budget_per_buf : 32768u;
const uint32_t max_chunk_elems = max_chunk_bytes / elem_size;
if (max_chunk_elems == 0) {
return false;
}
const uint32_t min_chunk_elems = 256 < max_chunk_elems ? 256 : max_chunk_elems;
const uint32_t target_chunk_elems = hex_round_up((total_elems + (2 * max_threads) - 1) / (2 * max_threads), 32);
const uint32_t chunk_size = (target_chunk_elems < min_chunk_elems) ? min_chunk_elems
: (target_chunk_elems > max_chunk_elems) ? max_chunk_elems
: target_chunk_elems;
const uint32_t chunk_bytes = hex_round_up(chunk_size * elem_size, 128);
const uint32_t total_chunks = (total_elems + chunk_size - 1) / chunk_size;
const uint32_t n_threads = (total_chunks >= 2) ? (max_threads < total_chunks ? max_threads : total_chunks) : 1;
kparams->kernel_type = HTP_BINARY_KERNEL_CHUNKED;
kparams->n_threads = n_threads;
kparams->n_srcs = (uint8_t) n_srcs;
kparams->rows_per_buffer = 1;
kparams->src1_row_size_aligned = is_scalar ? 0 : kparams->src0_row_size_aligned;
kparams->src1_size = 0;
kparams->chunk_size = chunk_size;
kparams->chunk_bytes = chunk_bytes;
kparams->is_scalar = is_scalar ? 1 : 0;
htp_binary_vtcm_layout_build(L, kparams, vtcm_size);
if (L->total_bytes == 0 || L->total_bytes > vtcm_size) {
return false;
}
kparams->vtcm_size = (uint32_t) L->total_bytes;
return true;
}
#endif
+7 -8
View File
@@ -294,22 +294,21 @@ static int concat_transposed(struct htp_ops_context * octx, const struct htp_con
const uint32_t n_threads = octx->n_threads;
// layout precomputed on host; kept for reference:
// struct htp_concat_transposed_vtcm_layout layout;
// htp_concat_transposed_vtcm_layout_build(&layout, octx->src[0]->ne[0], octx->src[1]->ne[0], type_size, n_threads);
struct htp_concat_transposed_vtcm_layout layout;
htp_concat_transposed_vtcm_layout_build(&layout, octx->src[0]->ne[0], octx->src[1]->ne[0], type_size, n_threads);
uint8_t * vtcm_base = (uint8_t *) octx->ctx->vtcm_base;
struct htp_concat_context cctx;
cctx.octx = octx;
cctx.spad0_base = vtcm_base;
cctx.spad1_base = vtcm_base + n_threads * kparams->u.transposed.spad0_size_per_thread;
cctx.spad0_size_per_thread = kparams->u.transposed.spad0_size_per_thread;
cctx.spad1_size_per_thread = kparams->u.transposed.spad1_size_per_thread;
cctx.spad0_base = VTCM_LAYOUT_PTR(uint8_t, vtcm_base, layout.off_spad0);
cctx.spad1_base = VTCM_LAYOUT_PTR(uint8_t, vtcm_base, layout.off_spad1);
cctx.spad0_size_per_thread = layout.src0_spad_size_per_thread;
cctx.spad1_size_per_thread = layout.src1_spad_size_per_thread;
cctx.row_start = row_start;
cctx.nrows = nrows;
cctx.nplanes = dst->ne[2] * dst->ne[3];
cctx.div_ne2 = init_fastdiv_values(dst->ne[2]);
cctx.div_ne2 = kparams->u.transposed.div_ne2;
cctx.nrows_per_thread = fastdiv(nrows + n_threads - 1, &octx->n_threads_div);
work_queue_func_t worker_func = (type_size == 4) ? concat_2d_f32_transposed : concat_2d_f16_transposed;
+13 -6
View File
@@ -2,6 +2,8 @@
#define HTP_CONCAT_OPS_H
#include "hex-common.h"
#include "hex-fastdiv.h"
#include "htp-vtcm.h"
#include "gather-rows.h"
#include <stdint.h>
@@ -13,8 +15,7 @@ enum htp_concat_kernel_type {
};
struct htp_concat_transposed_params {
uint32_t spad0_size_per_thread;
uint32_t spad1_size_per_thread;
struct fastdiv_values div_ne2;
};
struct htp_concat_kernel_params {
@@ -40,11 +41,13 @@ _Static_assert(sizeof(struct htp_concat_kernel_params) <= 128, "htp_concat_kerne
struct htp_concat_transposed_vtcm_layout {
uint32_t src0_spad_size_per_thread;
uint32_t src1_spad_size_per_thread;
size_t off_spad0;
size_t off_spad1;
uint32_t total_bytes;
};
static inline void htp_concat_transposed_vtcm_layout_build(
struct htp_concat_transposed_vtcm_layout * layout,
struct htp_concat_transposed_vtcm_layout * L,
uint32_t src0_ne0,
uint32_t src1_ne0,
uint32_t type_size,
@@ -55,9 +58,13 @@ static inline void htp_concat_transposed_vtcm_layout_build(
uint32_t src1_ne0_padded = hex_round_up(src1_ne0, block_i);
uint32_t spad0_row_bytes = hex_round_up(src0_ne0 * type_size, 128) + src1_ne0_padded * type_size;
layout->src0_spad_size_per_thread = block_i * spad0_row_bytes;
layout->src1_spad_size_per_thread = src1_ne0_padded * spad1_stride;
layout->total_bytes = n_threads * (layout->src0_spad_size_per_thread + layout->src1_spad_size_per_thread);
L->src0_spad_size_per_thread = block_i * spad0_row_bytes;
L->src1_spad_size_per_thread = src1_ne0_padded * spad1_stride;
size_t off = 0;
VTCM_LAYOUT_ALLOC(off, off_spad0, (size_t) n_threads * L->src0_spad_size_per_thread);
VTCM_LAYOUT_ALLOC(off, off_spad1, (size_t) n_threads * L->src1_spad_size_per_thread);
L->total_bytes = (uint32_t) off;
}
#endif // HTP_CONCAT_OPS_H

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