Compare commits

...
26 Commits
Author SHA1 Message Date
Aman GuptaandGitHub 846e991ec3 cuda: add sqrt_softplus in topk-moe for dsv4 (#25896) 2026-07-22 00:30:01 +08:00
Kamalesh VSandGitHub fb0e6b6219 kleidiai : warn once when a weight type has no KleidiAI kernel (#25701) 2026-07-22 00:10:29 +08:00
PascalandGitHub 60f6a17704 common: resolve draft repo to its requested sidecar (#25955)
With -hfd pointing to a repo shipping speculative sidecars, the draft
resolved to the main model of that repo, since find_best_model()
excludes sidecar files, and the explicit draft plan suppressed the
sidecar discovery on the -hf repo.

The draft plan already discovers its sidecars, they were just never
consumed. Wire them as the draft, following the fallback pattern of
the main plan, so this now works as expected:

llama-server -hf repo -hfd repo --spec-type draft-dflash
2026-07-21 18:03:43 +02:00
PascalandGitHub fd41bf65a2 server: return 400 instead of 500 on validation error with X-Conversation-Id (#25760)
* server: return 400 instead of 500 on validation error with X-Conversation-Id

set_req() attaches the spipe as soon as the header is present, before the request
body is parsed. When params validation throws, set_next() never runs and next_orig
stays empty, so on_complete() called it and crashed with std::bad_function_call,
turning the prepared 400 JSON into a generic 500.

on_complete() now treats an empty next_orig as "streaming never started" and evicts
the session installed by set_req(), so a failed request leaves nothing behind for
discovery or replay. This also covers valid requests that carry the header but do
not stream, which previously left an empty finalized session in the map until the
GC TTL.

* ui: do not send the backend_sampling placeholder

On a fresh profile the syncable settings hold the empty string placeholder meaning
"let the server decide". Every neighbor field goes through the hasValue() guard
that filters it, except backend_sampling, which sent the placeholder verbatim and
made every default settings completion fail validation.

Guard the field with hasValue() like its neighbors. hasValue(false) is true, so an
explicit false still reaches the server and the intent of #18781 (send both true
and false) is preserved. Only the placeholder is filtered.
2026-07-21 17:47:54 +02:00
40b740ad05 server : properly handle null llama_context (#25868)
Co-authored-by: Stanisław Szymczyk <sszymczy@gmail.com>
2026-07-21 17:47:17 +02:00
f048010180 vulkan: Refactor vk_queue to use per-instance mutexes and unique handles (#23570)
* Refactor vk_queue to use per-instance mutexes and unique handles

* integrates VK_KHR_internally_synchronized_queues, abstracting the queue submission into a polymorphic interface that completely bypasses host-side mutex locking when driver-side synchronization is supported

* fix compilation error

* fix duplicate pNext chain for VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR

* add fallback defines for VK_KHR_internally_synchronized_queues

* add null checks for queues in vk_device_struct destructor

* use unique_ptr for outer queues to enforce exclusive ownership and optimize lifetime

* use static constexpr for eInternallySynchronizedKHR

* add lock guard to ggml_vk_create_aliased_queue for thread safety

* initialize sync_query_features.internallySynchronizedQueues to VK_FALSE

* reuse sync_query_features for internallySynchronizedQueues and simplify chaining

* refactor internallySynchronizedQueues detection

* fix internallySynchronizedQueues query guard

* use eInternallySynchronizedKHR constant

* fix self-referential alias for eInternallySynchronizedKHR

* use macro for eInternallySynchronizedKHR fallback

* fix internallySynchronizedQueues query timing in ggml-vulkan.cpp to prevent device creation mismatch

* reset sync_query_features.pNext before reusing in device creation chain, also removed the redundant second probe call

* refactor internally synchronized queues detection to use chained feature query and avoid redundant API calls

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* rename sync_enable_features to internally_synchronized_queues_features

* queue_flags is still computed before has_internally_synchronized_queues is set

* fix trailing whitespace

* replace eInternallySynchronizedKHR macro with static constexpr

* preserve source queue semantics in single-queue aliased transfer queue

* vulkan: fix cmd_pool access via pointer for compute_queue unique_ptr

* vulkan: lock queue during debug label emission when not internally synchronized

---------

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>
2026-07-21 17:40:45 +02:00
Markus EbnerandGitHub 5735e10c49 ggml-openvino: Add GGML_BACKEND_DL_IMPL invocation for OpenVINO backend (#25795)
This adds the missing `GGML_BACKEND_DL_IMPL()` macro invocation, that other backends have.

Fixes #25586 for me
2026-07-21 22:43:11 +08:00
Piotr Wilkin (ilintar)andGitHub 305ba519ab CUDA: vectorize same-type get_rows with int4 copy (#25929)
k_get_rows_float did a scalar one-element-per-thread copy and recomputed the
row-invariant work (index load, fast_div_modulo, src/dst row pointers) for
every element. Hoist that out of the per-element loop, and add a vectorized
path (k_get_rows_float_vec) that copies one int4 (16 B) per thread for the
contiguous same-type (no-cast) case.

The vectorized path is gated at compile time (is_same<src0_t, dst_t>) and at
runtime on 16-byte alignment of the base pointers and all row strides and on
ne00 % VEC == 0. Vectorizing divides the block count by VEC, so a small
single-row gather can drop below the device CU count and regress; an
occupancy gate keeps those on the block-rich scalar path.

On Strix Halo (gfx1151) the DeltaNet recurrent-state gather (ne00=524288)
drops 18.6us -> 13.0us (rocprofv3 HW timestamps), faster than the Vulkan
backend, with no regression on the small conv-state gather; total get_rows
-27%. test-backend-ops GET_ROWS passes (47/47).

Assisted-by: Claude Opus 4.8
2026-07-21 15:53:57 +02:00
Todor BoinovskiandGitHub 76f46ad29d hexagon: add CLAMP op (#25934) 2026-07-20 16:12:09 -07:00
2beefef688 ui: Sidebar Conversations Bulk Action + Improved Settings logic/UI (#25815)
* feat: WIP

* feat: Replace conversation rename flow with unified AlertDialog component

* feat: Add radio group component and consolidate title generation settings

* refactor: Remove JS Sandbox global toggle and migrate legacy user state

* chore: Formatting

* refactor: Cleanup

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* refactor: Cleanup

* refactor: Marquee selection hook

* feat: UI improvements

* refactor: Bulk db operations

* fix: optimize bulk conversation deletion to handle ancestor chains

* refactor: remove pairedKey mechanism from settings system

* fix: remove redundant onclick handler from dialog cancel button

* chore: pin @lucide/svelte to exact version

* feat: Run JavaScript tool disabled by default

* fix: correct active conversation deletion tracking in bulk delete

* feat: improve shift-key multi-selection support in sidebar via keyboard

* refactor: Retrieve JS Tool enabling via Developer Settings

* nits: sync, dialog wording, cycle guard, and lockfile follow-ups

- Restore titleGenerationUseLLM registry entry so it syncs across devices again
- Mention fork cascade in the bulk delete confirmation dialog
- Clear newParent on cycle guard break so children never point at a deleted conversation
- Align @lucide/svelte in package-lock.json with the exact pin in package.json

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-07-20 23:40:08 +02:00
Aman GuptaandGitHub 91d2fc3875 llama_dsv4: write only used rows in state (#25325)
* llama_dsv4: write only used rows in state

* add TODO about conflating token pos with kv rows
2026-07-20 22:43:39 +08:00
PascalandGitHub 4ee6a9af71 ui: fix collapsed user bubble with markdown rendering (#25869)
Edge paragraph margins are now zeroed at the source in
markdown-content.css, but the user bubble and the system message still
carried the -my-4 compensation for them. The uncompensated negative
margins shrank the wrapper 2rem below its content, collapsing
single-line user bubbles into a scrollable sliver and skewing the
system message expand threshold.
2026-07-20 16:28:43 +02:00
PascalandGitHub 43b5e63589 UI: fix Settings/Display tool call content toggle (#25783)
* ui: fix Show tool call in progress toggle ignored

The showToolCallInProgress setting was disconnected from the render
path during the agentic content rework: getDefaultExpanded() returns
a hardcoded false for tool call sections and an unconditional effect
auto-expands the currently executing tool call regardless of the
setting.

Drive default expansion of all tool call section types from the
setting and remove the now redundant auto-expand effect. Manual
toggling still takes precedence over the default in both directions.

* ui: rename Show tool call in progress to Always show tool call content

The previous name suggested symmetry with Show thought in progress,
which only applies while inference is running, but tool call content
stays expanded after completion. Rename the label, the settings key
and the syncable server key to alwaysShowToolCallContent. The synced
parameter never worked under its previous name so no migration is
needed.
2026-07-20 16:28:24 +02:00
PascalandGitHub 1521a9ac31 ui: enable the agentic flow when only the JS sandbox is active (#25865)
The agentic gate counted MCP servers, builtin and custom tools but not
frontend tools, so with the JS sandbox as the only tool source the
agentic flow was skipped, no tools field reached the server and the
chat template rendered without the tool system prompt.

The sandbox is fully client-side: frontendTools derives from the
Developer settings toggle alone, counting it in the gate restores that
single source of truth.
2026-07-20 16:22:16 +02:00
178a6c4493 opencl: Support broadcast for Adreno MUL_MAT and honor view_offs for Adreno Q8_0 MUL_MAT for llama-server multi-stream (#25910)
* opencl: handle broadcast for adreno gemm/gemv_noshuffle

* opencl: honor view_offs for adreno noshuffle gemm/gemv

* opencl: general GEMM/GEMV support broadcast

* opencl: remove unnecessary tests

* opencl: remove unnecessary comments

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
2026-07-19 22:48:57 -07:00
571d0d540d model: rotate injected K/V cache for DFlash (#25823)
* dflash: rotate injected K/V cache when using K/V quantization

* Update src/models/dflash.cpp

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

* clearer format

* remove trailing whitespace

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
2026-07-18 15:02:18 +02:00
Yash Raj PandeyandGitHub 4937ca83f4 llama-quant : exclude i32 ffn_gate_tid2eid routing table from quantization (#25787)
DeepSeek-V4's ffn_gate_tid2eid tensor is an i32 token-id -> expert-id
index table, not weights. It was never added to the name-based
exclusion list alongside ffn_gate_inp.weight, so llama-quantize tries
to quantize it and fails since i32 cannot convert to a float type.

Fixes ggml-org/llama.cpp#25754

Signed-off-by: Yash Raj Pandey <yashpn62@gmail.com>
2026-07-18 13:43:18 +02:00
lhezandGitHub 86a9c79f86 opencl: load and use kernel_gemm_moe_q6_k_f32_ns from bin kernel lib (#25797) 2026-07-17 15:29:29 -07:00
Hongqiang WangandGitHub 6bdd77f13c opencl: read/write MoE dp4a activation tiles to local memory as 128-bit (vectorized LD/ST perf opt) for Adreno GPUs (#25810)
* opencl: read MoE dp4a activation tile as 128-bit local loads

* opencl: vectorize MoE dp4a activation staging as 128-bit loads
2026-07-17 12:02:27 -07:00
Hongqiang WangandGitHub 86d86ed439 opencl: transpose q4_K noshuffle scales for coalesced reads (#25805) 2026-07-17 07:49:43 -07:00
Georgi Gerganov 7d56da7e54 sync : ggml 2026-07-17 17:06:26 +03:00
Georgi Gerganov 3727404068 ggml : bump version to 0.17.0 (ggml/1568) 2026-07-17 17:06:26 +03:00
5d5306bf3e tests : initialize all tensors in test_dsv4_hc to avoid NaNs in sentinel tensors (#25822)
Co-authored-by: Stanisław Szymczyk <sszymczy@gmail.com>
2026-07-17 15:33:35 +02:00
Georgi GerganovandGitHub 635cdd5fcc common : auto-download dflash- and eagle3- HF sidecars (#25811)
* common: auto-download dflash- and eagle3- HF sidecars

Mirror the existing mtp- sidecar logic to support auto-discovery and
download of DFlash (dflash-) and Eagle3 (eagle3-) speculative decoding
sidecars from Hugging Face repos.

Changes:
- Add --dflash and --eagle3 CLI flags to trigger sidecar download
- Add find_best_dflash() and find_best_eagle3() using find_best_sibling
- Exclude dflash- and eagle3- filenames from primary model selection
- Filter dflash- and eagle3- from cached model listings
- Wire download tasks that set speculative.draft.mparams as fallback

Assisted-by: pi:llama.cpp/Qwen3.6-27B

* docs : regen
2026-07-17 12:15:30 +03:00
Aaron TeoandGitHub 11fd0a6fb7 ggml-blas: default hadamard mul_mat to cpu routine (#25710)
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-07-17 11:39:33 +03:00
Jeff BolzandGitHub 788e07dc91 vulkan: Support Q2_0 (#25430)
* vulkan: Support Q2_0

The backend perf tests for mat-vec-mul weren't very good at first (worse than
q2_k), doubling the rows per workgroup made a big difference.

* reorder

* resolve merge conflict, adjust err threshold for f16->q2_0 set_rows
2026-07-17 08:42:59 +02:00
82 changed files with 2544 additions and 690 deletions
+80 -1
View File
@@ -361,6 +361,14 @@ common_models_handler common_models_handler_init(const common_params & params, l
params.speculative.types.end(),
COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params.speculative.types.end();
const bool spec_type_draft_dflash = std::find(params.speculative.types.begin(),
params.speculative.types.end(),
COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH) != params.speculative.types.end();
const bool spec_type_draft_eagle3 = std::find(params.speculative.types.begin(),
params.speculative.types.end(),
COMMON_SPECULATIVE_TYPE_DRAFT_EAGLE3) != params.speculative.types.end();
// only download mmproj if the current example is using it
bool use_mmproj = false;
for (const auto & ex : mmproj_examples) {
@@ -373,6 +381,8 @@ common_models_handler common_models_handler_init(const common_params & params, l
opts.bearer_token = params.hf_token;
opts.offline = params.offline;
opts.download_mtp = spec_type_draft_mtp;
opts.download_eagle3 = spec_type_draft_eagle3;
opts.download_dflash = spec_type_draft_dflash;
opts.download_mmproj = use_mmproj && !params.no_mmproj
&& params.mmproj.path.empty() && params.mmproj.url.empty();
@@ -517,8 +527,43 @@ void common_models_handler_apply(common_models_handler & handler, common_params
}
};
// when a sidecar type is requested, the draft repo resolves to its sidecar instead of a full model
const bool spec_sidecar_found = !plan_spec.mtp.local_path.empty() ||
!plan_spec.dflash.local_path.empty() ||
!plan_spec.eagle3.local_path.empty();
if (!plan_spec.mtp.local_path.empty() && !had_spec_url) {
tasks.emplace_back(plan_spec.mtp, opts, [&]() {
// only use the discovered MTP head when no draft path is set yet
if (params.speculative.draft.mparams.path.empty()) {
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.mtp);
} else {
hf_cache::finalize_file(plan_spec.mtp);
}
});
}
if (!plan_spec.dflash.local_path.empty() && !had_spec_url) {
tasks.emplace_back(plan_spec.dflash, opts, [&]() {
// only use the discovered DFlash sidecar when no draft path is set yet
if (params.speculative.draft.mparams.path.empty()) {
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.dflash);
} else {
hf_cache::finalize_file(plan_spec.dflash);
}
});
}
if (!plan_spec.eagle3.local_path.empty() && !had_spec_url) {
tasks.emplace_back(plan_spec.eagle3, opts, [&]() {
// only use the discovered Eagle3 sidecar when no draft path is set yet
if (params.speculative.draft.mparams.path.empty()) {
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan_spec.eagle3);
} else {
hf_cache::finalize_file(plan_spec.eagle3);
}
});
}
// handle plan_spec (e.g. --spec-draft-hf)
if (!plan_spec.model_files.empty() && !had_spec_url) {
if (!plan_spec.model_files.empty() && !had_spec_url && !spec_sidecar_found) {
add_tasks(plan_spec.model_files, plan_spec.primary, params.speculative.draft.mparams);
had_spec_url = true;
}
@@ -546,6 +591,26 @@ void common_models_handler_apply(common_models_handler & handler, common_params
}
});
}
if (!plan.dflash.local_path.empty() && !had_spec_url) {
tasks.emplace_back(plan.dflash, opts, [&]() {
// only fall back to the discovered DFlash sidecar when no draft was explicitly provided
if (params.speculative.draft.mparams.empty()) {
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.dflash);
} else {
hf_cache::finalize_file(plan.dflash);
}
});
}
if (!plan.eagle3.local_path.empty() && !had_spec_url) {
tasks.emplace_back(plan.eagle3, opts, [&]() {
// only fall back to the discovered Eagle3 sidecar when no draft was explicitly provided
if (params.speculative.draft.mparams.empty()) {
params.speculative.draft.mparams.path = hf_cache::finalize_file(plan.eagle3);
} else {
hf_cache::finalize_file(plan.eagle3);
}
});
}
if (!plan.preset.local_path.empty()) {
tasks.emplace_back(plan.preset, opts, [&]() {
// if HF repo is a preset repo, we simply run server in router mode with the preset.ini file
@@ -2815,6 +2880,20 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.speculative.types.push_back(COMMON_SPECULATIVE_TYPE_DRAFT_MTP);
}
).set_examples({LLAMA_EXAMPLE_DOWNLOAD}));
add_opt(common_arg(
{"--dflash"},
"also download the DFlash sidecar, if available (default: unused)",
[](common_params & params) {
params.speculative.types.push_back(COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH);
}
).set_examples({LLAMA_EXAMPLE_DOWNLOAD}));
add_opt(common_arg(
{"--eagle3"},
"also download the Eagle3 sidecar, if available (default: unused)",
[](common_params & params) {
params.speculative.types.push_back(COMMON_SPECULATIVE_TYPE_DRAFT_EAGLE3);
}
).set_examples({LLAMA_EXAMPLE_DOWNLOAD}));
add_opt(common_arg(
{"--context-file"}, "FNAME",
"file to load context from (use comma-separated values to specify multiple files)",
+23 -3
View File
@@ -620,6 +620,16 @@ static hf_cache::hf_file find_best_mtp(const hf_cache::hf_files & files,
return find_best_sibling(files, model, "mtp-");
}
static hf_cache::hf_file find_best_eagle3(const hf_cache::hf_files & files,
const std::string & model) {
return find_best_sibling(files, model, "eagle3-");
}
static hf_cache::hf_file find_best_dflash(const hf_cache::hf_files & files,
const std::string & model) {
return find_best_sibling(files, model, "dflash-");
}
static bool gguf_filename_is_model(const std::string & filepath) {
if (!string_ends_with(filepath, ".gguf")) {
return false;
@@ -632,7 +642,9 @@ static bool gguf_filename_is_model(const std::string & filepath) {
return filename.find("mmproj") == std::string::npos &&
filename.find("imatrix") == std::string::npos &&
filename.find("mtp-") == std::string::npos;
filename.find("mtp-") == std::string::npos &&
filename.find("eagle3-") == std::string::npos &&
filename.find("dflash-") == std::string::npos;
}
static hf_cache::hf_file find_best_model(const hf_cache::hf_files & files,
@@ -740,6 +752,12 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
if (opts.download_mtp) {
plan.mtp = find_best_mtp(all, primary.path);
}
if (opts.download_dflash) {
plan.dflash = find_best_dflash(all, primary.path);
}
if (opts.download_eagle3) {
plan.eagle3 = find_best_eagle3(all, primary.path);
}
return plan;
}
@@ -911,8 +929,10 @@ 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("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) {
continue;
}
if (seen.insert(f.repo_id + ":" + split.tag).second) {
+6 -2
View File
@@ -55,8 +55,10 @@ struct common_download_opts {
std::string bearer_token;
common_header_list headers;
bool offline = false;
bool download_mmproj = false;
bool download_mtp = false;
bool download_mmproj = false;
bool download_mtp = false;
bool download_eagle3 = false;
bool download_dflash = false;
common_download_callback * callback = nullptr;
};
@@ -106,6 +108,8 @@ struct common_download_hf_plan {
hf_cache::hf_files model_files;
hf_cache::hf_file mmproj;
hf_cache::hf_file mtp;
hf_cache::hf_file eagle3;
hf_cache::hf_file dflash;
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);
+1 -1
View File
@@ -4,7 +4,7 @@ project("ggml" C CXX ASM)
### GGML Version
set(GGML_VERSION_MAJOR 0)
set(GGML_VERSION_MINOR 16)
set(GGML_VERSION_MINOR 17)
set(GGML_VERSION_PATCH 0)
set(GGML_VERSION_BASE "${GGML_VERSION_MAJOR}.${GGML_VERSION_MINOR}.${GGML_VERSION_PATCH}")
+7
View File
@@ -1,3 +1,4 @@
#include "ggml.h"
#include "ggml-impl.h"
#include "ggml-blas.h"
#include "ggml-backend-impl.h"
@@ -415,6 +416,12 @@ static bool ggml_backend_blas_device_supports_op(ggml_backend_dev_t dev, const s
// TODO: find the optimal value
const int64_t min_batch = 32;
// default back to CPU fast path
// see: https://github.com/ggml-org/llama.cpp/issues/25565
if (ggml_get_op_params_i32(op, 1) == GGML_HINT_SRC0_IS_HADAMARD) {
return false;
}
return ggml_is_contiguous(src0) &&
ggml_is_contiguous(src1) &&
src1->type == GGML_TYPE_F32 &&
+15
View File
@@ -1719,6 +1719,7 @@ class extra_buffer_type : ggml::cpu::extra_buffer_type {
return true;
}
return false;
}
@@ -1727,6 +1728,20 @@ class extra_buffer_type : ggml::cpu::extra_buffer_type {
if (op->src[0]->buffer && op->src[0]->buffer->buft == ggml_backend_cpu_kleidiai_buffer_type()) {
return (ggml::cpu::tensor_traits *) op->src[0]->extra;
} else {
// KleidiAI only has kernels for Q4_0 and Q8_0. For a quantized weight of any
// other type (K-quants, IQ) it declines the op and returns nullptr below, so
// KleidiAI does not accelerate it. Another CPU backend may still take the op,
// and this can run during graph planning, so the message says what KleidiAI
// did rather than what ends up executing. Warn once per process.
if (ggml_is_quantized(op->src[0]->type) &&
op->src[0]->type != GGML_TYPE_Q4_0 && op->src[0]->type != GGML_TYPE_Q8_0) {
static std::atomic<bool> warned(false);
if (!warned.exchange(true)) {
GGML_LOG_WARN("kleidiai: no kernel for tensor type %s, not accelerated by KleidiAI "
"(kernels available for Q4_0 and Q8_0)\n",
ggml_type_name(op->src[0]->type));
}
}
if (op->src[0]->type != GGML_TYPE_F16) {
return nullptr;
}
+67 -17
View File
@@ -55,27 +55,51 @@ static __global__ void k_get_rows_float(
dst_t * GGML_CUDA_RESTRICT dst = dst_ptr;
ggml_cuda_pdl_sync();
for (int64_t z = blockIdx.z; z < ne11*(int64_t)ne12_fdv.z; z += gridDim.z) {
// The x and y dimensions of the grid are swapped because the maximum allowed grid size for x is higher.
const int i10 = blockIdx.x;
const uint2 dm = fast_div_modulo((uint32_t)z, ne12_fdv);
const int i11 = dm.x;
const int i12 = dm.y;
const int i01 = src1[i10*s10 + i11*s11 + i12*s12];
dst_t * GGML_CUDA_RESTRICT dst_row = dst + i10*s1 + i11*s2 + i12*s3;
const src0_t * GGML_CUDA_RESTRICT src0_row = (const src0_t *)((const char *) src0 + i01*nb01 + i11*nb02 + i12*nb03);
for (int64_t i00 = blockIdx.y*blockDim.x + threadIdx.x; i00 < ne00; i00 += gridDim.y*blockDim.x) {
// The x and y dimensions of the grid are swapped because the maximum allowed grid size for x is higher.
const int i10 = blockIdx.x;
const uint2 dm = fast_div_modulo((uint32_t)z, ne12_fdv);
const int i11 = dm.x;
const int i12 = dm.y;
if (i00 >= ne00) {
return;
}
const int i01 = src1[i10*s10 + i11*s11 + i12*s12];
dst_t * dst_row = dst + i10*s1 + i11*s2 + i12*s3;
const src0_t * src0_row = (const src0_t *)((const char *) src0 + i01*nb01 + i11*nb02 + i12*nb03);
dst_row[i00] = ggml_cuda_cast<dst_t>(src0_row[i00]);
}
}
}
template<typename dst_t>
static __global__ void k_get_rows_float_vec(
const dst_t * src0_ptr, const int32_t * src1_ptr, dst_t * dst_ptr,
const int64_t ne00v,
const int64_t ne11, const uint3 ne12_fdv,
const size_t s1, const size_t s2, const size_t s3,
const size_t nb01, const size_t nb02, const size_t nb03,
const size_t s10, const size_t s11, const size_t s12) {
ggml_cuda_pdl_lc();
ggml_cuda_pdl_sync();
for (int64_t z = blockIdx.z; z < ne11*(int64_t)ne12_fdv.z; z += gridDim.z) {
const int i10 = blockIdx.x;
const uint2 dm = fast_div_modulo((uint32_t)z, ne12_fdv);
const int i11 = dm.x;
const int i12 = dm.y;
const int i01 = src1_ptr[i10*s10 + i11*s11 + i12*s12];
int4 * GGML_CUDA_RESTRICT dst_row = (int4 *) (dst_ptr + i10*s1 + i11*s2 + i12*s3);
const int4 * GGML_CUDA_RESTRICT src0_row = (const int4 *)((const char *) src0_ptr + i01*nb01 + i11*nb02 + i12*nb03);
for (int64_t i = blockIdx.y*blockDim.x + threadIdx.x; i < ne00v; i += gridDim.y*blockDim.x) {
dst_row[i] = src0_row[i];
}
}
}
template<typename grad_t, typename dst_t>
static __global__ void k_get_rows_back_float(
const grad_t * __restrict__ grad, const int32_t * __restrict__ rows, dst_t * __restrict__ dst,
@@ -148,8 +172,6 @@ static void get_rows_cuda_float(
const size_t nb1, const size_t nb2, const size_t nb3,
cudaStream_t stream) {
const dim3 block_dims(CUDA_GET_ROWS_BLOCK_SIZE, 1, 1);
const int block_num_y = (ne00 + CUDA_GET_ROWS_BLOCK_SIZE - 1) / CUDA_GET_ROWS_BLOCK_SIZE;
const dim3 block_nums(ne10, MIN(block_num_y, UINT16_MAX), MIN(ne11*ne12, UINT16_MAX));
// strides in elements
// const size_t s0 = nb0 / sizeof(dst_t);
@@ -166,6 +188,34 @@ static void get_rows_cuda_float(
GGML_ASSERT(ne11 <= std::numeric_limits<uint32_t>::max() / ne12);
const uint3 ne12_fdv = init_fastdiv_values(ne12);
if constexpr (std::is_same<src0_t, dst_t>::value) {
constexpr int VEC = 16 / sizeof(dst_t);
const int64_t ne00v = ne00 / VEC;
const int64_t vec_block_num_y = (ne00v + CUDA_GET_ROWS_BLOCK_SIZE - 1) / CUDA_GET_ROWS_BLOCK_SIZE;
const bool enough_blocks = vec_block_num_y * ne10 * ne11 * ne12 >= 128;
const bool can_vec = VEC > 1 && enough_blocks &&
(ne00 % VEC == 0) &&
(nb01 % 16 == 0) && (nb02 % 16 == 0) && (nb03 % 16 == 0) &&
(nb1 % 16 == 0) && (nb2 % 16 == 0) && (nb3 % 16 == 0) &&
(((uintptr_t) src0_d) % 16 == 0) && (((uintptr_t) dst_d) % 16 == 0);
if (can_vec) {
const int block_num_y = vec_block_num_y;
const dim3 block_nums(ne10, MIN(block_num_y, UINT16_MAX), MIN(ne11*ne12, UINT16_MAX));
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{block_nums, block_dims, 0, stream};
ggml_cuda_kernel_launch(k_get_rows_float_vec<dst_t>, launch_params,
(const dst_t *) src0_d, src1_d, dst_d,
ne00v, ne11, ne12_fdv,
s1, s2, s3,
nb01, nb02, nb03,
s10, s11, s12);
return;
}
}
const int block_num_y = (ne00 + CUDA_GET_ROWS_BLOCK_SIZE - 1) / CUDA_GET_ROWS_BLOCK_SIZE;
const dim3 block_nums(ne10, MIN(block_num_y, UINT16_MAX), MIN(ne11*ne12, UINT16_MAX));
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{block_nums, block_dims, 0, stream};
ggml_cuda_kernel_launch(k_get_rows_float<src0_t, dst_t>, launch_params,
src0_d, src1_d, dst_d,
+27 -13
View File
@@ -2703,6 +2703,7 @@ static int ggml_cuda_try_gdn_cache_fusion(
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;
args.softmax = false;
args.delayed_softmax = false;
args.prob_bias = false;
@@ -2716,10 +2717,17 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
}
if (nodes[node_idx]->op == GGML_OP_UNARY) {
if (ggml_get_unary_op(nodes[node_idx]) != GGML_UNARY_OP_SIGMOID) {
const ggml_unary_op unary_op = ggml_get_unary_op(nodes[node_idx]);
if (unary_op == GGML_UNARY_OP_SIGMOID) {
args.sigmoid = true;
} else if (unary_op == GGML_UNARY_OP_SOFTPLUS && node_idx + 1 < n_nodes &&
nodes[node_idx + 1]->op == GGML_OP_SQRT && nodes[node_idx + 1]->src[0] == nodes[node_idx]) {
// sqrt(softplus(x)) scoring (DeepSeek-V4)
args.sqrt_softplus = true;
node_idx++;
} else {
return false;
}
args.sigmoid = true;
}
if (nodes[node_idx]->op == GGML_OP_ARGSORT) {
@@ -2728,7 +2736,7 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
node_idx++;
if (args.sigmoid || args.softmax) {
if (args.sigmoid || args.sqrt_softplus || args.softmax) {
// SOFTMAX -> RESHAPE
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_RESHAPE ||
nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
@@ -3172,21 +3180,27 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
const ggml_tensor * scale = nullptr;
if (!args.delayed_softmax) {
ggml_op gating_op = args.sigmoid ? GGML_OP_UNARY : GGML_OP_SOFT_MAX;
int out_nodes[2]; // nodes which can't be elided
int out_nodes[2]; // nodes which can't be elided
if (args.sigmoid) {
ops.insert(ops.end(), { GGML_OP_UNARY });
} else if (args.sqrt_softplus) {
ops.insert(ops.end(), { GGML_OP_UNARY, GGML_OP_SQRT });
} else {
ops.insert(ops.end(), { GGML_OP_SOFT_MAX });
}
const int i_probs = i + (int) ops.size() - 1; // last node of the gating activation
if (args.prob_bias) {
bias = cgraph->nodes[i + 2]->src[1];
ops.insert(ops.end(), { gating_op, GGML_OP_RESHAPE, GGML_OP_ADD, GGML_OP_ARGSORT, GGML_OP_VIEW,
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 + 4;
ids = cgraph->nodes[i + 4];
out_nodes[0] = i_probs + 4;
} else {
ops.insert(ops.end(),
{ gating_op, GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
out_nodes[0] = i + 3;
ids = cgraph->nodes[i + 3];
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
out_nodes[0] = i_probs + 3;
}
ids = cgraph->nodes[out_nodes[0]];
if (args.norm) {
ops.insert(ops.end(),
+18 -4
View File
@@ -8,6 +8,7 @@
// Kernel config struct - passed by value to CUDA kernel
struct topk_moe_config {
bool use_sigmoid;
bool use_sqrt_softplus;
bool with_norm;
bool delayed_softmax;
};
@@ -67,6 +68,16 @@ __device__ void sigmoid_warp_inplace(float (&vals)[experts_per_thread], const in
}
}
template <int experts_per_thread, bool use_limit>
__device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], const int limit, const int lane) {
#pragma unroll
for (int i = 0; i < experts_per_thread; i++) {
const int idx = lane + i * WARP_SIZE;
const bool active = !use_limit || (idx < limit);
vals[i] = active ? sqrtf(vals[i] > 20.0f ? vals[i] : logf(1.0f + expf(vals[i]))) : -INFINITY;
}
}
/*
This kernel does the following:
1. optionally softmax over the logits per token [n_experts, n_tokens]
@@ -115,6 +126,8 @@ __launch_bounds__(4 * WARP_SIZE, 1) __global__ void topk_moe_cuda(const float *
if (!config.delayed_softmax) {
if (config.use_sigmoid) {
sigmoid_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
} else if (config.use_sqrt_softplus) {
sqrt_softplus_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
} else {
softmax_warp_inplace<experts_per_thread, false>(wt, n_experts, threadIdx.x);
}
@@ -364,9 +377,10 @@ void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
}
topk_moe_config config;
config.use_sigmoid = args.sigmoid;
config.with_norm = with_norm;
config.delayed_softmax = args.delayed_softmax;
config.use_sigmoid = args.sigmoid;
config.use_sqrt_softplus = args.sqrt_softplus;
config.with_norm = with_norm;
config.delayed_softmax = args.delayed_softmax;
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,
@@ -415,7 +429,7 @@ bool ggml_cuda_should_use_topk_moe(const ggml_tensor * gating_op,
} else if (gating_op->op == GGML_OP_UNARY) {
ggml_unary_op op = ggml_get_unary_op(gating_op);
if (op != GGML_UNARY_OP_SIGMOID) {
if (op != GGML_UNARY_OP_SIGMOID && op != GGML_UNARY_OP_SOFTPLUS) {
return false;
}
}
+1
View File
@@ -5,6 +5,7 @@
struct ggml_cuda_topk_moe_args {
bool sigmoid{};
bool sqrt_softplus{};
bool softmax{};
bool delayed_softmax{};
bool prob_bias{};
+2
View File
@@ -3476,6 +3476,7 @@ static htp_op_code op_remap_to_htp(const ggml_tensor * t) {
case GGML_OP_RMS_NORM: return HTP_OP_RMS_NORM;
case GGML_OP_CONCAT: return HTP_OP_CONCAT;
case GGML_OP_SCALE: return HTP_OP_SCALE;
case GGML_OP_CLAMP: return HTP_OP_CLAMP;
case GGML_OP_SQR: return HTP_OP_SQR;
case GGML_OP_SQRT: return HTP_OP_SQRT;
case GGML_OP_SOFT_MAX: return HTP_OP_SOFTMAX;
@@ -4126,6 +4127,7 @@ static bool ggml_backend_hexagon_device_supports_op(ggml_backend_dev_t dev, cons
case GGML_OP_L2_NORM:
case GGML_OP_RMS_NORM:
case GGML_OP_SCALE:
case GGML_OP_CLAMP:
supp = ggml_hexagon_supported_unary(sess, op);
break;
+1
View File
@@ -97,6 +97,7 @@ enum htp_op_code {
HTP_OP_PAD,
HTP_OP_NORM,
HTP_OP_CONCAT,
HTP_OP_CLAMP,
HTP_OP_INVALID
};
+1
View File
@@ -718,6 +718,7 @@ static int execute_op(struct htp_ops_context * octx) {
case HTP_OP_RMS_NORM:
case HTP_OP_RMS_NORM_MUL:
case HTP_OP_SCALE:
case HTP_OP_CLAMP:
case HTP_OP_SQR:
case HTP_OP_SQRT:
case HTP_OP_UNARY_SOFTPLUS:
+31
View File
@@ -138,6 +138,24 @@ static void scale_f32(const float * restrict src,
}
}
static void clamp_f32(const float * restrict src,
float * restrict dst,
const uint32_t num_rows,
const struct htp_unary_context * uctx) {
htp_unary_op_preamble;
float min = 0.f;
float max = 0.f;
memcpy(&min, &op_params[0], sizeof(float));
memcpy(&max, &op_params[1], sizeof(float));
for (uint32_t ir = 0; ir < num_rows; ir++) {
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
hvx_clamp_scalar_f32(dst_local, src_local, min, max, ne0);
}
}
static void rms_norm_f32(const float * restrict src,
float * restrict dst,
const uint32_t num_rows,
@@ -542,6 +560,7 @@ DEFINE_UNARY_TASK(norm, false, false, norm_f32(src0_vtcm, dst_vtcm, bl
DEFINE_UNARY_TASK(rms_norm, false, false, rms_norm_f32(src0_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(rms_norm_mul, true, false, rms_norm_mul_f32(src0_vtcm, uctx->broadcast_weight ? (const float *) src1_vtcm_data : src1_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(scale, false, false, scale_f32(src0_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(clamp, false, false, clamp_f32(src0_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(sqr, false, false, sqr_f32(src0_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(sqrt, false, false, sqrt_f32(src0_vtcm, dst_vtcm, block_size, uctx))
DEFINE_UNARY_TASK(unary_neg, false, false, neg_f32(src0_vtcm, dst_vtcm, block_size, uctx))
@@ -681,6 +700,14 @@ static inline void tile_scale_f32(uint8_t * dst_vtcm, const uint8_t * src_vtcm,
hvx_scale_offset_f32_aa(dst_vtcm, src_vtcm, tw, scale, bias);
}
static inline void tile_clamp_f32(uint8_t * dst_vtcm, const uint8_t * src_vtcm, uint32_t tw, const int32_t * op_params) {
float min = 0.f;
float max = 0.f;
memcpy(&min, &op_params[0], sizeof(float));
memcpy(&max, &op_params[1], sizeof(float));
hvx_clamp_scalar_f32(dst_vtcm, src_vtcm, min, max, tw);
}
static inline void tile_unary_softplus_f32(uint8_t * dst_vtcm, const uint8_t * src_vtcm, uint32_t tw) {
const float * restrict sf = (const float *) src_vtcm;
float * restrict df = (float *) dst_vtcm;
@@ -765,6 +792,7 @@ static inline void tri_apply_tile_f32(const uint8_t * restrict src, uint8_t * re
}
DEFINE_UNARY_TILED_TASK(scale, false, tile_scale_f32(dst_vtcm, src_vtcm, tw, op_params))
DEFINE_UNARY_TILED_TASK(clamp, false, tile_clamp_f32(dst_vtcm, src_vtcm, tw, op_params))
DEFINE_UNARY_TILED_TASK(sqr, false, hvx_sqr_f32_aa(dst_vtcm, src_vtcm, tw))
DEFINE_UNARY_TILED_TASK(sqrt, false, hvx_sqrt_f32_aa(dst_vtcm, src_vtcm, tw))
DEFINE_UNARY_TILED_TASK(unary_neg, false, hvx_scale_f32_aa(dst_vtcm, src_vtcm, tw, -1.0f))
@@ -787,6 +815,7 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
case HTP_OP_RMS_NORM: op_type = "rmsnorm-f32"; break;
case HTP_OP_RMS_NORM_MUL: op_type = "rmsnorm-mul-f32"; break;
case HTP_OP_SCALE: op_type = "scale-f32"; break;
case HTP_OP_CLAMP: op_type = "clamp-f32"; break;
case HTP_OP_SQR: op_type = "sqr-f32"; break;
case HTP_OP_SQRT: op_type = "sqrt-f32"; break;
case HTP_OP_UNARY_NEG: op_type = "neg-f32"; break;
@@ -882,6 +911,7 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
if (col_tile) {
switch (octx->op) {
case HTP_OP_SCALE: task_func = unary_task_f32_tiled_scale; break;
case HTP_OP_CLAMP: task_func = unary_task_f32_tiled_clamp; break;
case HTP_OP_SQR: task_func = unary_task_f32_tiled_sqr; break;
case HTP_OP_SQRT: task_func = unary_task_f32_tiled_sqrt; break;
case HTP_OP_UNARY_NEG: task_func = unary_task_f32_tiled_unary_neg; break;
@@ -898,6 +928,7 @@ static int execute_op_unary_f32(struct htp_ops_context * octx) {
case HTP_OP_RMS_NORM: task_func = unary_task_f32_rms_norm; break;
case HTP_OP_RMS_NORM_MUL: task_func = unary_task_f32_rms_norm_mul; break;
case HTP_OP_SCALE: task_func = unary_task_f32_scale; break;
case HTP_OP_CLAMP: task_func = unary_task_f32_clamp; break;
case HTP_OP_SQR: task_func = unary_task_f32_sqr; break;
case HTP_OP_SQRT: task_func = unary_task_f32_sqrt; break;
case HTP_OP_UNARY_NEG: task_func = unary_task_f32_unary_neg; break;
+1
View File
@@ -41,6 +41,7 @@ _Static_assert(sizeof(struct htp_unary_kernel_params) <= 128, "htp_unary_kernel_
static inline bool htp_op_is_unary(uint32_t opcode) {
switch (opcode) {
case HTP_OP_CLAMP:
case HTP_OP_NORM:
case HTP_OP_RMS_NORM:
case HTP_OP_RMS_NORM_MUL:
+68 -14
View File
@@ -876,7 +876,7 @@ struct ggml_backend_opencl_context {
cl_kernel kernel_gemm_moe_q8_1_dp4a_q5k = nullptr; // generic dp4a MoE GEMM (MOE_QT=5, q5_K), opt-in
cl_kernel kernel_moe_expand_scale_q5_K = nullptr; // q5_K 6-bit s[] -> uniform scale[16]/min[8]
cl_kernel kernel_gemv_moe_q5_k_f32_ns, kernel_gemm_moe_q5_k_f32_ns;
cl_kernel kernel_gemv_moe_q6_k_f32_ns, kernel_gemm_moe_q6_k_f32_ns;
cl_kernel kernel_gemv_moe_q6_k_f32_ns, kernel_gemm_moe_q6_k_f32_ns, kernel_gemm_moe_q6_k_f32_ns_bin;
cl_kernel kernel_gemm_moe_q6_k_q8_1_dp4a = nullptr; // dp4a (int8) q6_K MoE prefill GEMM
cl_kernel kernel_gemv_moe_mxfp4_f32, kernel_gemm_moe_mxfp4_f32;
cl_kernel kernel_gemv_moe_mxfp4_f32_ns, kernel_gemm_moe_mxfp4_f32_ns, kernel_gemm_moe_mxfp4_f32_ns_bin;
@@ -4310,6 +4310,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
GGML_LOG_CONT(".");
}
// gemm_moe_q6_k_f32_ns_bin
{
size_t bin_size = 0;
backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin = nullptr;
if (use_adreno_bin_kernels(backend_ctx)) {
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_q6_k_f32_ns_ila", &bin_size);
if (kernel_bin && bin_size > 0) {
cl_program prog =
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
CL_CHECK((backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin = clCreateKernel(prog, "kernel_gemm_moe_q6_k_f32_ns_ila", &err), err));
CL_CHECK(clReleaseProgram(prog));
GGML_LOG_CONT(".");
}
}
}
// gemm_moe_q6_k_q8_1_dp4a (dp4a q6_K MoE prefill GEMM)
if (backend_ctx->has_integer_dot) {
#ifdef GGML_OPENCL_EMBED_KERNELS
@@ -6012,7 +6030,8 @@ static void transpose_2d(
cl_kernel kernel,
cl_mem src, cl_mem dst, size_t size,
cl_int stride, cl_int rows,
bool blocking = true
bool blocking = true,
bool auto_local = false // let driver pick local size for non-uniform workgroups
) {
static ggml_cl_buffer buf;
@@ -6038,7 +6057,7 @@ static void transpose_2d(
size_t local_size[3] = {64, 1, 1};
size_t global_size[3] = {(size_t)stride, (size_t)rows, 1};;
CL_CHECK(clEnqueueNDRangeKernel(backend_ctx->queue, kernel, 3, NULL,
global_size, local_size, 0, NULL, NULL));
global_size, auto_local ? NULL : local_size, 0, NULL, NULL));
if (blocking) {
CL_CHECK(clEnqueueCopyBuffer(backend_ctx->queue, trans, dst, 0, 0, size, 0, NULL, &evt));
@@ -6055,10 +6074,11 @@ static void transpose_2d_as_8b(
ggml_backend_opencl_context * backend_ctx,
cl_mem src, cl_mem dst, size_t size,
cl_int stride, cl_int rows,
bool blocking = true
bool blocking = true,
bool auto_local = false
) {
transpose_2d(backend_ctx, backend_ctx->kernel_transpose_8_buf,
src, dst, size, stride, rows, blocking);
src, dst, size, stride, rows, blocking, auto_local);
}
static void transpose_2d_as_16b(
@@ -9054,6 +9074,9 @@ static void ggml_backend_opencl_buffer_set_tensor(ggml_backend_buffer_t buffer,
transpose_2d_as_16b(backend_ctx, extra->q, extra->q, size_q, K/4, M);
transpose_2d_as_16b(backend_ctx, extra->d, extra->d, size_d, K/256, M);
transpose_2d_as_16b(backend_ctx, extra->dm, extra->dm, size_dm, K/256, M);
// Transpose s as uchar
transpose_2d_as_8b(backend_ctx, extra->s, extra->s, size_s, K/256*12, M, true, true);
}
#endif // GGML_OPENCL_USE_ADRENO_KERNELS
return;
@@ -10222,23 +10245,27 @@ static void ggml_backend_opencl_buffer_get_tensor(ggml_backend_buffer_t buffer,
size_t size_q = ggml_nelements(tensor)/ggml_blck_size(tensor->type)*ggml_blck_size(tensor->type)/2;
size_t size_d = ggml_nelements(tensor)/ggml_blck_size(tensor->type)*sizeof(ggml_fp16_t);
size_t size_dm = ggml_nelements(tensor)/ggml_blck_size(tensor->type)*sizeof(ggml_fp16_t);
size_t size_s = ggml_nelements(tensor)/ggml_blck_size(tensor->type)*12;
static ggml_cl_buffer buf_trans_q;
static ggml_cl_buffer buf_trans_d;
static ggml_cl_buffer buf_trans_dm;
static ggml_cl_buffer buf_trans_s;
buf_trans_q.allocate(backend_ctx->context, size_q);
buf_trans_d.allocate(backend_ctx->context, size_d);
buf_trans_dm.allocate(backend_ctx->context, size_dm);
buf_trans_s.allocate(backend_ctx->context, size_s);
// Transpose q, d, dm back
// Transpose q, d, dm, s back
transpose_2d_as_16b(backend_ctx, extra->q, buf_trans_q.buffer, size_q, M, K/4);
transpose_2d_as_16b(backend_ctx, extra->d, buf_trans_d.buffer, size_d, M, K/256);
transpose_2d_as_16b(backend_ctx, extra->dm, buf_trans_dm.buffer, size_dm, M, K/256);
transpose_2d_as_8b (backend_ctx, extra->s, buf_trans_s.buffer, size_s, M, K/256*12, true, true);
cl_kernel kernel = backend_ctx->kernel_restore_block_q4_K_noshuffle;
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &buf_trans_q.buffer));
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &extra->s));
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &buf_trans_s.buffer));
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &buf_trans_d.buffer));
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_mem), &buf_trans_dm.buffer));
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_mem), &data_device));
@@ -17058,9 +17085,6 @@ static void ggml_cl_mul_mat_q8_0_f32_adreno(ggml_backend_t backend, const ggml_t
cl_ulong offset1 = extra1->offset + src1->view_offs;
cl_ulong offsetd = extrad->offset + dst->view_offs;
GGML_ASSERT(src1->view_offs == 0);
GGML_ASSERT(dst->view_offs == 0);
const int ne00 = src0->ne[0];
const int ne01 = src0->ne[1];
const int ne02 = src0->ne[2];
@@ -17121,9 +17145,9 @@ static void ggml_cl_mul_mat_q8_0_f32_adreno(ggml_backend_t backend, const ggml_t
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &q_img));
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &extra0_q8_0->d));
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &b_img));
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_ulong), &extra1->offset));
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_ulong), &offset1));
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_mem), &extrad->data_device));
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(cl_ulong), &extrad->offset));
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(cl_ulong), &offsetd));
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(int), &ne00));
CL_CHECK(clSetKernelArg(kernel, 7, sizeof(int), &ne01));
CL_CHECK(clSetKernelArg(kernel, 8, sizeof(int), &ne02));
@@ -18518,6 +18542,26 @@ static void ggml_cl_mul_mat(ggml_backend_t backend, const ggml_tensor * src0, co
GGML_ASSERT(ne00 == ne10);
#ifdef GGML_OPENCL_USE_ADRENO_KERNELS
// adreno GEMM/GEMV kernels do not support broadcast, assuming ne2 and ne3 are 1 for src1
// so we handle broadcast here
if ((ne12 > 1 || ne13 > 1) && ne02 == 1 && ne03 == 1 &&
src0t != GGML_TYPE_F16 && src0t != GGML_TYPE_F32) {
for (int i13 = 0; i13 < ne13; ++i13) {
for (int i12 = 0; i12 < ne12; ++i12) {
ggml_tensor s1 = *src1;
s1.ne[2] = 1; s1.ne[3] = 1;
s1.view_offs = src1->view_offs + (size_t)i12*nb12 + (size_t)i13*nb13;
ggml_tensor d = *dst;
d.ne[2] = 1; d.ne[3] = 1;
d.view_offs = dst->view_offs + (size_t)i12*nb2 + (size_t)i13*nb3;
ggml_cl_mul_mat(backend, src0, &s1, &d);
}
}
return;
}
#endif
int nth0 = 32;
int nth1 = 1;
int nrows = 1;
@@ -22282,8 +22326,8 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
bool use_moe_dp4a = q5kmdp4a_on
&& backend_ctx->kernel_gemm_moe_q8_1_dp4a_q5k != nullptr
&& extra0_q5_K->scale != nullptr;
// bin kernel takes precedence
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q4_k_f32_ns_bin == nullptr;
// dot prod has to be available
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
if (use_moe_dp4a) {
const size_t tok_slots = (size_t)max_post_router_tile * n_tile_size;
@@ -22501,6 +22545,9 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
} else { // for gemm
kernel = backend_ctx->kernel_gemm_moe_q6_k_f32_ns;
if (backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin) {
kernel = backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin;
}
// Reorder router if called from test-backend-ops or when new router is generated.
// Otherwise reuse the reordered result from previous mul_mat_id call.
@@ -22521,6 +22568,8 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|| backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
// dot prod has to be available
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
// bin kernel takes precedence
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin == nullptr;
cl_buffer_region region;
region.origin = 0;
@@ -22557,6 +22606,11 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
CL_CHECK(status);
cl_image_format image_format_buf_src1 = {CL_RGBA, CL_FLOAT};
cl_image_desc image_desc_buf_src1 = {CL_MEM_OBJECT_IMAGE1D_BUFFER, static_cast<size_t>(ne00 * max_post_router_tile * n_tile_size / 4), 0,0,0,0,0,0,0, {buf_src1_reordered}};
if (backend_ctx->kernel_gemm_moe_q6_k_f32_ns_bin) {
// bin kernel uses slightly different image format
image_format_buf_src1 = {CL_R, CL_FLOAT};
image_desc_buf_src1.image_width = static_cast<size_t>(ne00 * max_post_router_tile * n_tile_size);
}
image_src1_reordered = clCreateImage(backend_ctx->context, CL_MEM_READ_ONLY, &image_format_buf_src1, &image_desc_buf_src1, NULL, &status);
CL_CHECK(status);
@@ -37,15 +37,17 @@ static inline float e8m0_to_fp32(uchar x) {
// One token's dp4a dot (8 uints = 32 K elems) + mxfp4 block-scale epilogue.
// blk_scale already carries the 0.5 factor (== 0.5 * 2^e).
#define MOE_MXFP4_DP4A_T(t) do { \
uint4 a0 = vload4(0, &sh_qa[t][0]); \
uint4 a1 = vload4(0, &sh_qa[t][4]); \
int raw = 0; \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], sh_qa[t][0], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], sh_qa[t][1], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], sh_qa[t][2], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], sh_qa[t][3], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], sh_qa[t][4], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], sh_qa[t][5], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], sh_qa[t][6], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], sh_qa[t][7], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], a0.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], a0.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], a0.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], a0.s3, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], a1.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], a1.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], a1.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], a1.s3, raw); \
acc[t] += blk_scale * (float)sh_d[t] * (float)raw; \
} while (0)
@@ -133,11 +135,13 @@ kernel void kernel_gemm_moe_mxfp4_q8_1_dp4a(
qw[6] = mxfp4_pack((ushort)(r3)); qw[7] = mxfp4_pack((ushort)(r3 >> 16));
// cooperatively stage the n_real-token x 32-K int8 activations
const uint stage_lim = (uint)n_real * 8;
for (uint idx = lid; idx < stage_lim; idx += 64) {
const uint t = idx >> 3;
const uint u = idx & 7;
sh_qa[t][u] = src1_qa[(col + t) * ne00_u + (step >> 2) + u];
// Stage each token's 8 activation uints as two 128-bit uint4 loads/stores.
const uint vlim = (uint)n_real * 2;
for (uint idx = lid; idx < vlim; idx += 64) {
const uint t = idx >> 1;
const uint h = (idx & 1) << 2; // 0 or 4
uint4 v = vload4(0, &src1_qa[(col + t) * ne00_u + (step >> 2) + h]);
vstore4(v, 0, &sh_qa[t][h]);
}
if (lid < (uint)n_real) {
sh_d[lid] = src1_da[(col + lid) * num_blocks + sub];
@@ -17,15 +17,17 @@
// One token's dp4a dot (8 uints = 32 K elems) + q4_0 scale/zero-point epilogue.
#define MOE_Q40_DP4A_T(t) do { \
uint4 a0 = vload4(0, &sh_qa[t][0]); \
uint4 a1 = vload4(0, &sh_qa[t][4]); \
int raw = 0; \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], sh_qa[t][0], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], sh_qa[t][1], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], sh_qa[t][2], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], sh_qa[t][3], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], sh_qa[t][4], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], sh_qa[t][5], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], sh_qa[t][6], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], sh_qa[t][7], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], a0.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], a0.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], a0.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], a0.s3, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], a1.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], a1.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], a1.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], a1.s3, raw); \
acc[t] += d_val * ((float)sh_d[t] * (float)raw - 8.0f * (float)sh_s[t]); \
} while (0)
@@ -112,11 +114,13 @@ kernel void kernel_gemm_moe_q4_0_q8_1_dp4a(
qw[6] = EXP4(r3); qw[7] = EXP4(r3 >> 16);
// cooperatively stage the n_real-token x 32-K int8 activations
const uint stage_lim = (uint)n_real * 8;
for (uint idx = lid; idx < stage_lim; idx += 64) {
const uint t = idx >> 3;
const uint u = idx & 7;
sh_qa[t][u] = src1_qa[(col + t) * ne00_u + (step >> 2) + u];
// Stage each token's 8 activation uints as two 128-bit uint4 loads/stores.
const uint vlim = (uint)n_real * 2;
for (uint idx = lid; idx < vlim; idx += 64) {
const uint t = idx >> 1;
const uint h = (idx & 1) << 2; // 0 or 4
uint4 v = vload4(0, &src1_qa[(col + t) * ne00_u + (step >> 2) + h]);
vstore4(v, 0, &sh_qa[t][h]);
}
if (lid < (uint)n_real) {
sh_d[lid] = src1_da[(col + lid) * num_blocks + sub];
@@ -37,16 +37,21 @@ inline void get_scale_min_k4(
(((uint)((u) & 0xF000u)) << 12) )
// One token's dp4a dot (8 uints = 32 K elems) + q4_K scale/min epilogue into acc[t].
// The 8 activation uints are read as two 128-bit uint4 loads staged to private (Adreno
// wants 128-bit local reads, and a __local operand fed straight to the dp4a builtin is
// slower and can miscompile).
#define MOE_Q4K_DP4A_T(t) do { \
uint4 a0 = vload4(0, &sh_qa[t][0]); \
uint4 a1 = vload4(0, &sh_qa[t][4]); \
int raw = 0; \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], sh_qa[t][0], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], sh_qa[t][1], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], sh_qa[t][2], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], sh_qa[t][3], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], sh_qa[t][4], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], sh_qa[t][5], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], sh_qa[t][6], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], sh_qa[t][7], raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[0], a0.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[1], a0.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[2], a0.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[3], a0.s3, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[4], a1.s0, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[5], a1.s1, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[6], a1.s2, raw); \
raw = dot_acc_sat_4x8packed_ss_int(qw[7], a1.s3, raw); \
acc[t] += scale * (float)sh_d[t] * (float)raw - minv * (float)sh_s[t]; \
} while (0)
@@ -145,12 +150,14 @@ kernel void kernel_gemm_moe_q4_k_q8_1_dp4a(
qw[4] = EXP4(r2); qw[5] = EXP4(r2 >> 16);
qw[6] = EXP4(r3); qw[7] = EXP4(r3 >> 16);
// --- cooperatively stage the n_real-token x 32-K int8 activations to LDS ---
const uint stage_lim = (uint)n_real * 8;
for (uint idx = lid; idx < stage_lim; idx += 64) {
const uint t = idx >> 3;
const uint u = idx & 7;
sh_qa[t][u] = src1_qa[(col + t) * ne00_u + (step >> 2) + u];
// cooperatively stage the n_real-token x 32-K int8 activations to lm
// Stage each token's 8 activation uints as two 128-bit uint4 loads/stores.
const uint vlim = (uint)n_real * 2;
for (uint idx = lid; idx < vlim; idx += 64) {
const uint t = idx >> 1;
const uint h = (idx & 1) << 2; // 0 or 4
uint4 v = vload4(0, &src1_qa[(col + t) * ne00_u + (step >> 2) + h]);
vstore4(v, 0, &sh_qa[t][h]);
}
if (lid < (uint)n_real) {
sh_d[lid] = src1_da[(col + lid) * ne00_b + sub];
@@ -41,8 +41,10 @@ inline int dp4a_q6(uint qw0, uint qw1, uint qw2, uint qw3,
// One token's q6_K dp4a dot (two halves, per-16 scales) + epilogue into acc[t].
#define MOE_Q6K_DP4A_T(t) do { \
const int raw1 = dp4a_q6(qw[0], qw[1], qw[2], qw[3], sh_qa[t][0], sh_qa[t][1], sh_qa[t][2], sh_qa[t][3]); \
const int raw2 = dp4a_q6(qw[4], qw[5], qw[6], qw[7], sh_qa[t][4], sh_qa[t][5], sh_qa[t][6], sh_qa[t][7]); \
uint4 a0 = vload4(0, &sh_qa[t][0]); \
uint4 a1 = vload4(0, &sh_qa[t][4]); \
const int raw1 = dp4a_q6(qw[0], qw[1], qw[2], qw[3], a0.s0, a0.s1, a0.s2, a0.s3); \
const int raw2 = dp4a_q6(qw[4], qw[5], qw[6], qw[7], a1.s0, a1.s1, a1.s2, a1.s3); \
const float a_d = (float)sh_d[t]; \
acc[t] += scale0 * a_d * (float)raw1 + scale1 * a_d * (float)raw2; \
} while (0)
@@ -144,11 +146,13 @@ kernel void kernel_gemm_moe_q6_k_q8_1_dp4a(
qw[6] = SIGN6(EXP4(r3) | EXP2((qh2 >> 16) & 0xFFu));
qw[7] = SIGN6(EXP4(r3 >> 16) | EXP2((qh2 >> 24) & 0xFFu));
const uint stage_lim = (uint)n_real * 8;
for (uint idx = lid; idx < stage_lim; idx += 64) {
const uint t = idx >> 3;
const uint u = idx & 7;
sh_qa[t][u] = src1_qa[(col + t) * ne00_u + (step >> 2) + u];
// Stage each token's 8 activation uints as two 128-bit uint4 loads/stores.
const uint vlim = (uint)n_real * 2;
for (uint idx = lid; idx < vlim; idx += 64) {
const uint t = idx >> 1;
const uint h = (idx & 1) << 2; // 0 or 4
uint4 v = vload4(0, &src1_qa[(col + t) * ne00_u + (step >> 2) + h]);
vstore4(v, 0, &sh_qa[t][h]);
}
if (lid < (uint)n_real) {
sh_d[lid] = src1_da[(col + lid) * ne00_b + sub];
@@ -102,8 +102,10 @@ inline int dp4a4(uint w0,uint w1,uint w2,uint w3,uint a0,uint a1,uint a2,uint a3
// One token's two-half dp4a + uniform scale/min epilogue into acc[t].
#define MOE_DP4A_T(t) do { \
const int raw1 = dp4a4(qw[0],qw[1],qw[2],qw[3], sh_qa[t][0],sh_qa[t][1],sh_qa[t][2],sh_qa[t][3]); \
const int raw2 = dp4a4(qw[4],qw[5],qw[6],qw[7], sh_qa[t][4],sh_qa[t][5],sh_qa[t][6],sh_qa[t][7]); \
uint4 a0 = vload4(0, &sh_qa[t][0]); \
uint4 a1 = vload4(0, &sh_qa[t][4]); \
const int raw1 = dp4a4(qw[0],qw[1],qw[2],qw[3], a0.s0,a0.s1,a0.s2,a0.s3); \
const int raw2 = dp4a4(qw[4],qw[5],qw[6],qw[7], a1.s0,a1.s1,a1.s2,a1.s3); \
const float a_d = (float)sh_d[t]; \
acc[t] += sc0*a_d*(float)raw1 + sc1*a_d*(float)raw2 - mn*(float)sh_s[t]; \
} while (0)
@@ -178,10 +180,13 @@ kernel void kernel_gemm_moe_q8_1_dp4a(
LOAD_QW(step, sub)
const uint stage_lim = (uint)n_real * 8;
for (uint idx = lid; idx < stage_lim; idx += 64) {
const uint t = idx >> 3, u = idx & 7;
sh_qa[t][u] = src1_qa[(col + t) * ne00_u + (step >> 2) + u];
// Stage each token's 8 activation uints as two 128-bit uint4 loads/stores.
const uint vlim = (uint)n_real * 2;
for (uint idx = lid; idx < vlim; idx += 64) {
const uint t = idx >> 1;
const uint h = (idx & 1) << 2; // 0 or 4
uint4 v = vload4(0, &src1_qa[(col + t) * ne00_u + (step >> 2) + h]);
vstore4(v, 0, &sh_qa[t][h]);
}
if (lid < (uint)n_real) {
sh_d[lid] = src1_da[(col + lid) * ne00_b + sub];
@@ -8,9 +8,11 @@
#define QK_K 256
#define K_SCALE_SIZE 12
// scales are transposed: consecutive codes of a row are `stride` apart
inline void get_scale_min_k4(
int j,
global const uchar * q,
int stride,
uchar * d,
uchar * m,
uchar mask_d6,
@@ -18,11 +20,11 @@ inline void get_scale_min_k4(
uchar mask_hi2
) {
if (j < 4) {
*d = q[j] & mask_d6;
*m = q[j+4] & mask_d6;
*d = q[j*stride] & mask_d6;
*m = q[(j+4)*stride] & mask_d6;
} else {
*d = (q[j+4] & mask_d4) | ((q[j-4] & mask_hi2) >> 2);
*m = ((q[j+4] >> 4) & mask_d4) | ((q[j] & mask_hi2) >> 2);
*d = (q[(j+4)*stride] & mask_d4) | ((q[(j-4)*stride] & mask_hi2) >> 2);
*m = ((q[(j+4)*stride] >> 4) & mask_d4) | ((q[j*stride] & mask_hi2) >> 2);
}
}
@@ -55,7 +57,6 @@ kernel void kernel_gemm_noshuffle_q4_k_f32(
half8 B;
half4 dequantized_weights;
int num_blocks_K = k / QK_K;
global const ushort * weight_ptr = src0_q + gx_2;
global const half * d_ptr = src0_d + gx_2;
@@ -68,16 +69,16 @@ kernel void kernel_gemm_noshuffle_q4_k_f32(
half4 d = vload4(0, d_ptr + sb_idx * m);
half4 dm = vload4(0, dm_ptr + sb_idx * m);
global const uchar * sc0 = src0_s + (gx_2+0) * num_blocks_K * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc1 = src0_s + (gx_2+1) * num_blocks_K * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc2 = src0_s + (gx_2+2) * num_blocks_K * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc3 = src0_s + (gx_2+3) * num_blocks_K * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc0 = src0_s + sb_idx * K_SCALE_SIZE * m + (gx_2+0);
global const uchar * sc1 = sc0 + 1;
global const uchar * sc2 = sc0 + 2;
global const uchar * sc3 = sc0 + 3;
uchar sv0, mn0, sv1, mn1, sv2, mn2, sv3, mn3;
get_scale_min_k4(sub_idx, sc0, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc1, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc2, &sv2, &mn2, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc3, &sv3, &mn3, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc0, m, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc1, m, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc2, m, &sv2, &mn2, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc3, m, &sv3, &mn3, mask_d6, mask_d4, mask_hi2);
half4 scale = convert_half4(convert_float4(d) * convert_float4((uchar4)(sv0, sv1, sv2, sv3)));
half4 mval = convert_half4(convert_float4(dm) * convert_float4((uchar4)(mn0, mn1, mn2, mn3)));
@@ -10,9 +10,11 @@
#define QK_K 256
#define K_SCALE_SIZE 12
// scales are transposed: consecutive codes of a row are `stride` apart
inline void get_scale_min_k4(
int j,
global const uchar * q,
uint stride,
uchar * d,
uchar * m,
uchar mask_d6,
@@ -20,11 +22,11 @@ inline void get_scale_min_k4(
uchar mask_hi2
) {
if (j < 4) {
*d = q[j] & mask_d6;
*m = q[j+4] & mask_d6;
*d = q[j*stride] & mask_d6;
*m = q[(j+4)*stride] & mask_d6;
} else {
*d = (q[j+4] & mask_d4) | ((q[j-4] & mask_hi2) >> 2);
*m = ((q[j+4] >> 4) & mask_d4) | ((q[j] & mask_hi2) >> 2);
*d = (q[(j+4)*stride] & mask_d4) | ((q[(j-4)*stride] & mask_hi2) >> 2);
*m = ((q[(j+4)*stride] >> 4) & mask_d4) | ((q[j*stride] & mask_hi2) >> 2);
}
}
@@ -79,7 +81,6 @@ kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a(
const bool row_valid = row < (uint)m;
const uint rrow = row_valid ? row : 0; // clamp OOB rows; their writes are masked
const uint num_superblocks = (uint)k / QK_K;
const uint k_u = (uint)k >> 2; // K in uint (int8x4) units
const uint k_b = (uint)k >> 5; // blocks-of-32 along K
@@ -101,9 +102,9 @@ kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a(
// weight scale/min for this WI's row, this subblock
const float dd = (float)src0_d [rrow + sb_idx * m];
const float dmm = (float)src0_dm[rrow + sb_idx * m];
global const uchar * sc = src0_s + rrow * num_superblocks * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc = src0_s + sb_idx * K_SCALE_SIZE * (uint)m + rrow;
uchar sv, mn;
get_scale_min_k4(sub_idx, sc, &sv, &mn, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc, (uint)m, &sv, &mn, mask_d6, mask_d4, mask_hi2);
const float scale = dd * (float)sv;
const float minv = dmm * (float)mn;
@@ -202,7 +203,6 @@ kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg(
const uint k_u = (uint)k >> 2; // K in uint (int8x4) units
const uint k_b = (uint)k >> 5; // blocks-of-32 along K
const uint num_superblocks = (uint)k / QK_K;
__local uint sh_qa[TILESIZE_N][8];
__local half sh_d[TILESIZE_N];
@@ -220,9 +220,9 @@ kernel void kernel_gemm_noshuffle_q4_k_q8_1_dp4a_wimg(
const float dd = (float)src0_d [rrow + sb_idx * m];
const float dmm = (float)src0_dm[rrow + sb_idx * m];
global const uchar * sc = src0_s + rrow * num_superblocks * K_SCALE_SIZE + sb_idx * K_SCALE_SIZE;
global const uchar * sc = src0_s + sb_idx * K_SCALE_SIZE * (uint)m + rrow;
uchar sv, mn;
get_scale_min_k4(sub_idx, sc, &sv, &mn, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(sub_idx, sc, (uint)m, &sv, &mn, mask_d6, mask_d4, mask_hi2);
const float scale = dd * (float)sv;
const float minv = dmm * (float)mn;
@@ -11,9 +11,11 @@
#define NSUBGROUPS 4
#define SUBGROUP_SIZE 64
// scales are transposed: consecutive codes of a row are `stride` apart
inline void get_scale_min_k4(
int j,
global const uchar * q,
uint stride,
uchar * d,
uchar * m,
uchar mask_d6,
@@ -21,11 +23,11 @@ inline void get_scale_min_k4(
uchar mask_hi2
) {
if (j < 4) {
*d = q[j] & mask_d6;
*m = q[j+4] & mask_d6;
*d = q[j*stride] & mask_d6;
*m = q[(j+4)*stride] & mask_d6;
} else {
*d = (q[j+4] & mask_d4) | ((q[j-4] & mask_hi2) >> 2);
*m = ((q[j+4] >> 4) & mask_d4) | ((q[j] & mask_hi2) >> 2);
*d = (q[(j+4)*stride] & mask_d4) | ((q[(j-4)*stride] & mask_hi2) >> 2);
*m = ((q[(j+4)*stride] >> 4) & mask_d4) | ((q[j*stride] & mask_hi2) >> 2);
}
}
@@ -232,7 +234,6 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
uint LINE_STRIDE_A = M / 2;
uint BLOCK_STRIDE_A = NSUBGROUPS * M;
uint scales_per_row = (K / QK_K) * 12;
private uint4 regA;
private half2 regS;
@@ -248,12 +249,12 @@ kernel void kernel_gemv_noshuffle_q4_k_f32(
half2 d = src0_d[gid + sb * LINE_STRIDE_A];
half2 dm = src0_m[gid + sb * LINE_STRIDE_A];
global const uchar * sc0 = src0_s + 2 * gid * scales_per_row + sb * 12;
global const uchar * sc1 = src0_s + (2 * gid + 1) * scales_per_row + sb * 12;
global const uchar * sc0 = src0_s + sb * 12 * M + 2 * gid;
global const uchar * sc1 = sc0 + 1;
uchar sv0, mn0, sv1, mn1;
get_scale_min_k4(j, sc0, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(j, sc1, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(j, sc0, M, &sv0, &mn0, mask_d6, mask_d4, mask_hi2);
get_scale_min_k4(j, sc1, M, &sv1, &mn1, mask_d6, mask_d4, mask_hi2);
regS = convert_half2(convert_float2(d) * convert_float2((uchar2)(sv0, sv1)));
regM = convert_half2(convert_float2(dm) * convert_float2((uchar2)(mn0, mn1)));
+2
View File
@@ -1378,3 +1378,5 @@ GGML_BACKEND_API ggml_backend_reg_t ggml_backend_openvino_reg(void) {
return &reg;
}
GGML_BACKEND_DL_IMPL(ggml_backend_openvino_reg)
+181 -58
View File
@@ -156,6 +156,24 @@ typedef struct VkPhysicalDeviceShaderFloat8FeaturesEXT {
} VkPhysicalDeviceShaderFloat8FeaturesEXT;
#endif
#ifndef VK_KHR_INTERNALLY_SYNCHRONIZED_QUEUES_EXTENSION_NAME
#define VK_KHR_INTERNALLY_SYNCHRONIZED_QUEUES_EXTENSION_NAME "VK_KHR_internally_synchronized_queues"
#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INTERNALLY_SYNCHRONIZED_QUEUES_FEATURES_KHR ((VkStructureType)1000504000)
#define VK_DEVICE_QUEUE_CREATE_INTERNALLY_SYNCHRONIZED_BIT_KHR ((VkDeviceQueueCreateFlagBits)0x00000004)
// Compile-time constant guaranteed; no runtime initialization overhead
static constexpr vk::DeviceQueueCreateFlagBits eInternallySynchronizedKHR =
static_cast<vk::DeviceQueueCreateFlagBits>(0x00000004);
typedef struct VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR {
VkStructureType sType;
void* pNext;
VkBool32 internallySynchronizedQueues;
} VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR;
#else
static constexpr vk::DeviceQueueCreateFlagBits eInternallySynchronizedKHR = vk::DeviceQueueCreateFlagBits::eInternallySynchronizedKHR;
#endif
#define ROUNDUP_POW2(M, N) (((M) + (N) - 1) & ~((N) - 1))
#define CEIL_DIV(M, N) (((M) / (N)) + (((M) % (N)) != 0))
static bool is_pow2(uint32_t x) { return x > 1 && (x & (x-1)) == 0; }
@@ -285,27 +303,41 @@ struct vk_command_pool {
};
// Prevent simultaneous submissions to the same queue.
// This could be per vk_queue if we stopped having two vk_queue structures
// sharing the same vk::Queue.
static std::mutex queue_mutex;
struct vk_queue_handle {
vk::Queue queue;
virtual void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) = 0;
virtual void lock() {} // no-op by default (internally synchronized case)
virtual void unlock() {}
virtual ~vk_queue_handle() = default;
};
struct vk_queue_handle_synchronized : vk_queue_handle {
std::mutex mutex;
void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) override {
std::lock_guard<std::mutex> guard(mutex);
queue.submit(submits, fence);
}
void lock() override { mutex.lock(); }
void unlock() override { mutex.unlock(); }
};
struct vk_queue_handle_unsynchronized : vk_queue_handle {
void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) override {
// Driver guarantees internal synchronization via VK_KHR_internally_synchronized_queues
queue.submit(submits, fence);
}
// lock()/unlock() inherited no-ops
};
struct vk_queue {
uint32_t queue_family_index;
vk::Queue queue;
std::shared_ptr<vk_queue_handle> handle;
vk_command_pool cmd_pool;
vk::PipelineStageFlags stage_flags;
bool transfer_only;
// copy everything except the cmd_pool
void copyFrom(vk_queue &other) {
queue_family_index = other.queue_family_index;
queue = other.queue;
stage_flags = other.stage_flags;
transfer_only = other.transfer_only;
}
};
static const char * ggml_backend_vk_buffer_type_name(ggml_backend_buffer_type_t buft);
@@ -712,11 +744,12 @@ struct vk_device_struct {
uint32_t vendor_id;
vk::DriverId driver_id;
vk_device_architecture architecture;
vk_queue compute_queue;
vk_queue transfer_queue;
std::unique_ptr<vk_queue> compute_queue;
std::unique_ptr<vk_queue> transfer_queue;
bool single_queue;
bool support_async;
bool async_use_transfer_queue;
bool has_internally_synchronized_queues = false;
uint32_t subgroup_size;
uint32_t subgroup_size_log2;
uint32_t shader_core_count;
@@ -1019,8 +1052,13 @@ struct vk_device_struct {
ggml_vk_destroy_buffer(sync_staging);
compute_queue.cmd_pool.destroy(device);
transfer_queue.cmd_pool.destroy(device);
if (compute_queue) compute_queue->cmd_pool.destroy(device);
if (transfer_queue) transfer_queue->cmd_pool.destroy(device);
// Explicitly clear to ensure queues drop their shared_ptrs to handles
// before the Vulkan logical device instance is destroyed
compute_queue.reset();
transfer_queue.reset();
for (auto& pipeline : all_pipelines) {
if (pipeline.expired()) {
@@ -2909,8 +2947,7 @@ static vk_command_buffer* ggml_vk_create_cmd_buffer(vk_device& device, vk_comman
static void ggml_vk_submit(vk_context& ctx, vk::Fence fence) {
if (ctx->seqs.empty()) {
if (fence) {
std::lock_guard<std::mutex> guard(queue_mutex);
ctx->p->q->queue.submit({}, fence);
ctx->p->q->handle->submit({}, fence);
}
return;
}
@@ -2979,8 +3016,7 @@ static void ggml_vk_submit(vk_context& ctx, vk::Fence fence) {
}
}
std::lock_guard<std::mutex> guard(queue_mutex);
ctx->p->q->queue.submit(submit_infos, fence);
ctx->p->q->handle->submit(submit_infos, fence);
ctx->seqs.clear();
}
@@ -3031,18 +3067,44 @@ static uint32_t ggml_vk_find_queue_family_index(std::vector<vk::QueueFamilyPrope
abort();
}
static void ggml_vk_create_queue(vk_device& device, vk_queue& q, uint32_t queue_family_index, uint32_t queue_index, vk::PipelineStageFlags&& stage_flags, bool transfer_only) {
static std::unique_ptr<vk_queue> ggml_vk_create_queue(vk_device& device, uint32_t queue_family_index, uint32_t queue_index, vk::PipelineStageFlags&& stage_flags, bool transfer_only) {
VK_LOG_DEBUG("ggml_vk_create_queue()");
std::lock_guard<std::recursive_mutex> guard(device->mutex);
q.queue_family_index = queue_family_index;
q.transfer_only = transfer_only;
auto q = std::make_unique<vk_queue>();
q->queue_family_index = queue_family_index;
q->transfer_only = transfer_only;
q.cmd_pool.init(device, &q);
std::shared_ptr<vk_queue_handle> h;
vk::DeviceQueueInfo2 queue_info2{};
queue_info2.queueFamilyIndex = queue_family_index;
queue_info2.queueIndex = queue_index;
q.queue = device->device.getQueue(queue_family_index, queue_index);
if (device->has_internally_synchronized_queues) {
h = std::make_shared<vk_queue_handle_unsynchronized>();
queue_info2.flags = eInternallySynchronizedKHR;
} else {
h = std::make_shared<vk_queue_handle_synchronized>();
}
q.stage_flags = stage_flags;
h->queue = device->device.getQueue2(queue_info2);
q->handle = h;
q->cmd_pool.init(device, q.get());
q->stage_flags = stage_flags;
return q;
}
static std::unique_ptr<vk_queue> ggml_vk_create_aliased_queue(vk_device& device, const std::unique_ptr<vk_queue>& source) {
std::lock_guard<std::recursive_mutex> guard(device->mutex);
auto q = std::make_unique<vk_queue>();
q->handle = source->handle;
q->queue_family_index = source->queue_family_index;
q->stage_flags = source->stage_flags;
q->transfer_only = source->transfer_only;
q->cmd_pool.init(device, q.get());
return q;
}
static vk_context ggml_vk_create_context(ggml_backend_vk_context * ctx, vk_command_pool& p) {
@@ -3107,11 +3169,11 @@ static void ggml_vk_queue_command_pools_cleanup(vk_device& device) {
// Arbitrary frequency to cleanup/reuse command buffers
static constexpr uint32_t cleanup_frequency = 10;
if (device->compute_queue.cmd_pool.buffers_in_use() >= cleanup_frequency) {
ggml_vk_command_pool_cleanup(device, device->compute_queue.cmd_pool);
if (device->compute_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
ggml_vk_command_pool_cleanup(device, device->compute_queue->cmd_pool);
}
if (device->transfer_queue.cmd_pool.buffers_in_use() >= cleanup_frequency) {
ggml_vk_command_pool_cleanup(device, device->transfer_queue.cmd_pool);
if (device->transfer_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
ggml_vk_command_pool_cleanup(device, device->transfer_queue->cmd_pool);
}
}
@@ -3735,6 +3797,7 @@ static bool ggml_vk_matmul_int_shmem_support(const vk_device& device, const std:
uint32_t block_a_size = 0;
switch (src0_type) {
case GGML_TYPE_Q2_0: block_a_size = std430_size({{32, 4}, {fp_size, fp_align}}); break; // qs[8] + dm
case GGML_TYPE_Q4_0: block_a_size = std430_size({{16, 4}, {fp_size, fp_align}}); break; // qs[16/4] + dm
case GGML_TYPE_Q4_1: block_a_size = std430_size({{16, 4}, {fp2_size, fp2_align}}); break; // qs[16/4] + dm(vec2)
case GGML_TYPE_Q5_0: block_a_size = std430_size({{16, 4}, {4, 4}, {fp_size, fp_align}}); break; // qs[16/4] + qh + dm
@@ -4339,6 +4402,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
}
#endif
CREATE_MM2(pipeline_dequant_mul_mat_mat_f16[GGML_TYPE_Q1_0], matmul_q1_0_f16, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3)
CREATE_MM2(pipeline_dequant_mul_mat_mat_f16[GGML_TYPE_Q2_0], matmul_q2_0_f16, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3)
CREATE_MM2(pipeline_dequant_mul_mat_mat_f16[GGML_TYPE_Q4_0], matmul_q4_0_f16, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3)
CREATE_MM2(pipeline_dequant_mul_mat_mat_f16[GGML_TYPE_Q4_1], matmul_q4_1_f16, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3)
CREATE_MM2(pipeline_dequant_mul_mat_mat_f16[GGML_TYPE_Q5_0], matmul_q5_0_f16, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3)
@@ -4378,6 +4442,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
}
#endif
CREATE_MM2(pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0], matmul_id_subgroup_q1_0_f16, mmqid_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, 5)
CREATE_MM2(pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0], matmul_id_subgroup_q2_0_f16, mmqid_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, 5)
CREATE_MM2(pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0], matmul_id_subgroup_q4_0_f16, mmqid_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, 5)
CREATE_MM2(pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1], matmul_id_subgroup_q4_1_f16, mmqid_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, 5)
CREATE_MM2(pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0], matmul_id_subgroup_q5_0_f16, mmqid_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, 5)
@@ -4448,6 +4513,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#endif
CREATE_MM2(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q1_0], matmul_q1_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q2_0], matmul_q2_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_0], matmul_q4_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_1], matmul_q4_1_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q5_0], matmul_q5_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
@@ -4492,6 +4558,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#endif
CREATE_MM2(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0], matmul_id_subgroup_q1_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id);
CREATE_MM2(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0], matmul_id_subgroup_q2_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id);
CREATE_MM2(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0], matmul_id_subgroup_q4_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id);
CREATE_MM2(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1], matmul_id_subgroup_q4_1_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id);
CREATE_MM2(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0], matmul_id_subgroup_q5_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id);
@@ -4581,6 +4648,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM_NODOT2(GGML_TYPE_BF16, pipeline_matmul_bf16, matmul_bf16, , wg_denoms, warptile, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM2(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q1_0], matmul_q1_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM2(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q2_0], matmul_q2_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM2(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_0], matmul_q4_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM2(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_1], matmul_q4_1_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM2(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q5_0], matmul_q5_0_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
@@ -4605,6 +4673,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
CREATE_MMQ(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q2_0], matmul_q2_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, , 0);
CREATE_MMQ(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q4_0], matmul_q4_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, , 0);
CREATE_MMQ(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q4_1], matmul_q4_1_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, , 0);
CREATE_MMQ(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q5_0], matmul_q5_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, , 0);
@@ -4627,6 +4696,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM2(GGML_TYPE_F16, pipeline_matmul_id_f16_f32, matmul_id_subgroup_f16_f32, wg_denoms, warptile_id, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size_16);
CREATE_MM_NODOT2(GGML_TYPE_BF16, pipeline_matmul_id_bf16, matmul_id_subgroup_bf16, , wg_denoms, warptile_id, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size_16);
CREATE_MM2(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0], matmul_id_subgroup_q1_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM2(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0], matmul_id_subgroup_q2_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM2(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0], matmul_id_subgroup_q4_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM2(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1], matmul_id_subgroup_q4_1_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM2(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0], matmul_id_subgroup_q5_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
@@ -4651,6 +4721,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
CREATE_MMQ(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q2_0], matmul_id_subgroup_q2_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MMQ(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q4_0], matmul_id_subgroup_q4_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MMQ(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q4_1], matmul_id_subgroup_q4_1_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MMQ(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q5_0], matmul_id_subgroup_q5_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
@@ -4672,6 +4743,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM2(GGML_TYPE_F16, pipeline_matmul_id_f16_f32, matmul_id_f16_f32, wg_denoms, warptile, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM_NODOT2(GGML_TYPE_BF16, pipeline_matmul_id_bf16, matmul_id_bf16, , wg_denoms, warptile, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM2(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0], matmul_id_q1_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM2(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0], matmul_id_q2_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM2(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0], matmul_id_q4_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM2(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1], matmul_id_q4_1_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM2(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0], matmul_id_q5_0_f32, mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
@@ -4696,6 +4768,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
CREATE_MMQ(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q2_0], matmul_id_q2_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MMQ(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q4_0], matmul_id_q4_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MMQ(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q4_1], matmul_id_q4_1_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MMQ(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id_q8_1[GGML_TYPE_Q5_0], matmul_id_q5_0_q8_1, mmq_wg_denoms, warptile_mmqid_int, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
@@ -4748,6 +4821,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM(GGML_TYPE_BF16, pipeline_matmul_bf16, matmul_bf16, , wg_denoms, warptile, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q1_0].f32acc, matmul_q1_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q2_0].f32acc, matmul_q2_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_0].f32acc, matmul_q4_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_1].f32acc, matmul_q4_1_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
CREATE_MM(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q5_0].f32acc, matmul_q5_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, , 0);
@@ -4773,6 +4847,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
CREATE_MMQ(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q2_0].f32acc, matmul_q2_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, );
CREATE_MMQ(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q4_0].f32acc, matmul_q4_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, );
CREATE_MMQ(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q4_1].f32acc, matmul_q4_1_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, );
CREATE_MMQ(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_q8_1[GGML_TYPE_Q5_0].f32acc, matmul_q5_0_q8_1, mmq_wg_denoms, warptile_mmq_int, vk_mat_mat_push_constants, 3, );
@@ -4794,6 +4869,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM(GGML_TYPE_BF16, pipeline_matmul_id_bf16, matmul_id_subgroup_bf16, , wg_denoms, warptile_id, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size_16);
CREATE_MM(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0].f32acc, matmul_id_subgroup_q1_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0].f32acc, matmul_id_subgroup_q2_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0].f32acc, matmul_id_subgroup_q4_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1].f32acc, matmul_id_subgroup_q4_1_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
CREATE_MM(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0].f32acc, matmul_id_subgroup_q5_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, mul_mat_subgroup_size);
@@ -4822,6 +4898,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
CREATE_MM(GGML_TYPE_BF16, pipeline_matmul_id_bf16, matmul_id_bf16, , wg_denoms, warptile, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM(GGML_TYPE_Q1_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q1_0].f32acc, matmul_id_q1_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM(GGML_TYPE_Q2_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q2_0].f32acc, matmul_id_q2_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0].f32acc, matmul_id_q4_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1].f32acc, matmul_id_q4_1_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
CREATE_MM(GGML_TYPE_Q5_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q5_0].f32acc, matmul_id_q5_0_f32, , mmq_wg_denoms, warptile_mmqid, vk_mat_mat_id_push_constants, mul_mat_id_param_count, _id, 0);
@@ -4924,6 +5001,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_F16 ][i], "mul_mat_vec_f16_f32_f32", arr_dmmv_f16_f32_f32_len[reduc], arr_dmmv_f16_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {wg_size_subgroup, 2, i+1}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_BF16][i], "mul_mat_vec_bf16_f32_f32", arr_dmmv_bf16_f32_f32_len[reduc], arr_dmmv_bf16_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {wg_size_subgroup, 2, i+1}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_Q1_0][i], "mul_mat_vec_q1_0_f32_f32", arr_dmmv_q1_0_f32_f32_len[reduc], arr_dmmv_q1_0_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_Q2_0][i], "mul_mat_vec_q2_0_f32_f32", arr_dmmv_q2_0_f32_f32_len[reduc], arr_dmmv_q2_0_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_Q4_0][i], "mul_mat_vec_q4_0_f32_f32", arr_dmmv_q4_0_f32_f32_len[reduc], arr_dmmv_q4_0_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_Q4_1][i], "mul_mat_vec_q4_1_f32_f32", arr_dmmv_q4_1_f32_f32_len[reduc], arr_dmmv_q4_1_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[w][GGML_TYPE_Q5_0][i], "mul_mat_vec_q5_0_f32_f32", arr_dmmv_q5_0_f32_f32_len[reduc], arr_dmmv_q5_0_f32_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
@@ -4950,6 +5028,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_F16 ][i], "mul_mat_vec_f16_f16_f32", arr_dmmv_f16_f16_f32_len[reduc], arr_dmmv_f16_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {wg_size_subgroup, 2, i+1}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_BF16][i], "mul_mat_vec_bf16_f16_f32", arr_dmmv_bf16_f16_f32_len[reduc], arr_dmmv_bf16_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {wg_size_subgroup, 2, i+1}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_Q1_0][i], "mul_mat_vec_q1_0_f16_f32", arr_dmmv_q1_0_f16_f32_len[reduc], arr_dmmv_q1_0_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_Q2_0][i], "mul_mat_vec_q2_0_f16_f32", arr_dmmv_q2_0_f16_f32_len[reduc], arr_dmmv_q2_0_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_Q4_0][i], "mul_mat_vec_q4_0_f16_f32", arr_dmmv_q4_0_f16_f32_len[reduc], arr_dmmv_q4_0_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_Q4_1][i], "mul_mat_vec_q4_1_f16_f32", arr_dmmv_q4_1_f16_f32_len[reduc], arr_dmmv_q4_1_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[w][GGML_TYPE_Q5_0][i], "mul_mat_vec_q5_0_f16_f32", arr_dmmv_q5_0_f16_f32_len[reduc], arr_dmmv_q5_0_f16_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq, i+1}, 1, true, use_subgroups, force_subgroup_size);
@@ -4977,6 +5056,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
const uint32_t subgroup_size_int = (device->vendor_id == VK_VENDOR_ID_INTEL && device->subgroup_size_control) ? device->subgroup_min_size : device->subgroup_size;
const uint32_t wg_size_subgroup_int = (w == DMMV_WG_SIZE_SUBGROUP) ? subgroup_size_int : (subgroup_size_int * 4);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_q8_1_f32[w][GGML_TYPE_Q2_0][i], "mul_mat_vec_q2_0_q8_1_f32", arr_dmmv_q2_0_q8_1_f32_len[reduc], arr_dmmv_q2_0_q8_1_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {2*rm_kq_int, 1, 1}, {wg_size_subgroup_int, 2*rm_kq_int, i+1}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_q8_1_f32[w][GGML_TYPE_Q4_0][i], "mul_mat_vec_q4_0_q8_1_f32", arr_dmmv_q4_0_q8_1_f32_len[reduc], arr_dmmv_q4_0_q8_1_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int, i+1}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_q8_1_f32[w][GGML_TYPE_Q4_1][i], "mul_mat_vec_q4_1_q8_1_f32", arr_dmmv_q4_1_q8_1_f32_len[reduc], arr_dmmv_q4_1_q8_1_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int, i+1}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_q8_1_f32[w][GGML_TYPE_Q5_0][i], "mul_mat_vec_q5_0_q8_1_f32", arr_dmmv_q5_0_q8_1_f32_len[reduc], arr_dmmv_q5_0_q8_1_f32_data[reduc], "main", mul_mat_vec_num_bindings, sizeof(vk_mat_vec_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int, i+1}, 1, true, use_subgroups, subgroup_size_int);
@@ -5002,6 +5082,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_F16 ], "mul_mat_vec_id_f16_f32", arr_dmmv_id_f16_f32_f32_len[reduc], arr_dmmv_id_f16_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2, 1, 1}, {wg_size_subgroup, 2}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_BF16], "mul_mat_vec_id_bf16_f32", arr_dmmv_id_bf16_f32_f32_len[reduc], arr_dmmv_id_bf16_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2, 1, 1}, {wg_size_subgroup, 2}, 1, false, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_Q1_0], "mul_mat_vec_id_q1_0_f32", arr_dmmv_id_q1_0_f32_f32_len[reduc], arr_dmmv_id_q1_0_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_Q2_0], "mul_mat_vec_id_q2_0_f32", arr_dmmv_id_q2_0_f32_f32_len[reduc], arr_dmmv_id_q2_0_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_Q4_0], "mul_mat_vec_id_q4_0_f32", arr_dmmv_id_q4_0_f32_f32_len[reduc], arr_dmmv_id_q4_0_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_Q4_1], "mul_mat_vec_id_q4_1_f32", arr_dmmv_id_q4_1_f32_f32_len[reduc], arr_dmmv_id_q4_1_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq}, 1, true, use_subgroups, force_subgroup_size);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[w][GGML_TYPE_Q5_0], "mul_mat_vec_id_q5_0_f32", arr_dmmv_id_q5_0_f32_f32_len[reduc], arr_dmmv_id_q5_0_f32_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_stdq, 1, 1}, {wg_size_subgroup, 2*rm_stdq}, 1, true, use_subgroups, force_subgroup_size);
@@ -5029,6 +5110,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
const uint32_t subgroup_size_int = (device->vendor_id == VK_VENDOR_ID_INTEL && device->subgroup_size_control) ? device->subgroup_min_size : device->subgroup_size;
const uint32_t wg_size_subgroup_int = (w == DMMV_WG_SIZE_SUBGROUP) ? subgroup_size_int : (subgroup_size_int * 4);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_q8_1_f32[w][GGML_TYPE_Q2_0], "mul_mat_vec_id_q2_0_q8_1_f32", arr_dmmv_id_q2_0_q8_1_f32_len[reduc], arr_dmmv_id_q2_0_q8_1_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {2*rm_kq_int, 1, 1}, {wg_size_subgroup_int, 2*rm_kq_int}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_q8_1_f32[w][GGML_TYPE_Q4_0], "mul_mat_vec_id_q4_0_q8_1_f32", arr_dmmv_id_q4_0_q8_1_f32_len[reduc], arr_dmmv_id_q4_0_q8_1_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_q8_1_f32[w][GGML_TYPE_Q4_1], "mul_mat_vec_id_q4_1_q8_1_f32", arr_dmmv_id_q4_1_q8_1_f32_len[reduc], arr_dmmv_id_q4_1_q8_1_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int}, 1, true, use_subgroups, subgroup_size_int);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_q8_1_f32[w][GGML_TYPE_Q5_0], "mul_mat_vec_id_q5_0_q8_1_f32", arr_dmmv_id_q5_0_q8_1_f32_len[reduc], arr_dmmv_id_q5_0_q8_1_f32_data[reduc], "main", mul_mat_vec_id_num_bindings, sizeof(vk_mat_vec_id_push_constants), {1*rm_stdq_int, 1, 1}, {wg_size_subgroup_int, 1*rm_stdq_int}, 1, true, use_subgroups, subgroup_size_int);
@@ -5061,6 +5143,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
// dequant shaders
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_F32 ], "f32_to_f16", dequant_f32_len, dequant_f32_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q1_0], "dequant_q1_0", dequant_q1_0_len, dequant_q1_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 8, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q2_0], "dequant_q2_0", dequant_q2_0_len, dequant_q2_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 4, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q4_0], "dequant_q4_0", dequant_q4_0_len, dequant_q4_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q4_1], "dequant_q4_1", dequant_q4_1_len, dequant_q4_1_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q5_0], "dequant_q5_0", dequant_q5_0_len, dequant_q5_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
@@ -5088,6 +5171,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_F16 ], "get_rows_f16", get_rows_f16_len, get_rows_f16_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_BF16], "get_rows_bf16", get_rows_bf16_len, get_rows_bf16_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_Q1_0], "get_rows_q1_0", get_rows_q1_0_len, get_rows_q1_0_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_Q2_0], "get_rows_q2_0", get_rows_q2_0_len, get_rows_q2_0_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_Q4_0], "get_rows_q4_0", get_rows_q4_0_len, get_rows_q4_0_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_Q4_1], "get_rows_q4_1", get_rows_q4_1_len, get_rows_q4_1_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_Q5_0], "get_rows_q5_0", get_rows_q5_0_len, get_rows_q5_0_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
@@ -5115,6 +5199,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_F16 ], "get_rows_f16_f32", get_rows_f16_f32_len, get_rows_f16_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_BF16], "get_rows_bf16_f32", get_rows_bf16_f32_len, get_rows_bf16_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_Q1_0], "get_rows_q1_0_f32", get_rows_q1_0_f32_len, get_rows_q1_0_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_Q2_0], "get_rows_q2_0_f32", get_rows_q2_0_f32_len, get_rows_q2_0_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_Q4_0], "get_rows_q4_0_f32", get_rows_q4_0_f32_len, get_rows_q4_0_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_Q4_1], "get_rows_q4_1_f32", get_rows_q4_1_f32_len, get_rows_q4_1_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_Q5_0], "get_rows_q5_0_f32", get_rows_q5_0_f32_len, get_rows_q5_0_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
@@ -5199,6 +5284,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_cpy_transpose_16, "cpy_transpose_16", cpy_transpose_16_len, cpy_transpose_16_data, "main", 2, sizeof(vk_op_unary_push_constants), {1, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q1_0], "cpy_f32_q1_0", cpy_f32_q1_0_len, cpy_f32_q1_0_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q2_0], "cpy_f32_q2_0", cpy_f32_q2_0_len, cpy_f32_q2_0_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q4_0], "cpy_f32_q4_0", cpy_f32_q4_0_len, cpy_f32_q4_0_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q4_1], "cpy_f32_q4_1", cpy_f32_q4_1_len, cpy_f32_q4_1_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_quant[GGML_TYPE_Q5_0], "cpy_f32_q5_0", cpy_f32_q5_0_len, cpy_f32_q5_0_data, "main", 2, sizeof(vk_op_unary_push_constants), {32, 1, 1}, {}, 1);
@@ -5211,6 +5297,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_F16], "set_rows_" #src "_f16" #itype, set_rows_ ## src ## _f16 ## itype ## _len, set_rows_ ## src ## _f16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_BF16], "set_rows_" #src "_bf16" #itype, set_rows_ ## src ## _bf16 ## itype ## _len, set_rows_ ## src ## _bf16 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q1_0], "set_rows_" #src "_q1_0" #itype, set_rows_ ## src ## _q1_0 ## itype ## _len, set_rows_ ## src ## _q1_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q2_0], "set_rows_" #src "_q2_0" #itype, set_rows_ ## src ## _q2_0 ## itype ## _len, set_rows_ ## src ## _q2_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q4_0], "set_rows_" #src "_q4_0" #itype, set_rows_ ## src ## _q4_0 ## itype ## _len, set_rows_ ## src ## _q4_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q4_1], "set_rows_" #src "_q4_1" #itype, set_rows_ ## src ## _q4_1 ## itype ## _len, set_rows_ ## src ## _q4_1 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
ggml_vk_create_pipeline(device, device->pipeline_set_rows ## itype [src_idx][GGML_TYPE_Q5_0], "set_rows_" #src "_q5_0" #itype, set_rows_ ## src ## _q5_0 ## itype ## _len, set_rows_ ## src ## _q5_0 ## itype ## _data, "main", 3, sizeof(vk_op_binary_push_constants), {1, 1, 1}, {1}, 1, true); \
@@ -5226,6 +5313,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
ggml_vk_create_pipeline(device, device->pipeline_cpy_quant_f32[GGML_TYPE_Q1_0], "cpy_q1_0_f32", cpy_q1_0_f32_len, cpy_q1_0_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {(uint32_t)ggml_blck_size(GGML_TYPE_Q1_0), 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_quant_f32[GGML_TYPE_Q2_0], "cpy_q2_0_f32", cpy_q2_0_f32_len, cpy_q2_0_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {(uint32_t)ggml_blck_size(GGML_TYPE_Q2_0), 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_quant_f32[GGML_TYPE_Q4_0], "cpy_q4_0_f32", cpy_q4_0_f32_len, cpy_q4_0_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {(uint32_t)ggml_blck_size(GGML_TYPE_Q4_0), 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_quant_f32[GGML_TYPE_Q4_1], "cpy_q4_1_f32", cpy_q4_1_f32_len, cpy_q4_1_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {(uint32_t)ggml_blck_size(GGML_TYPE_Q4_1), 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_cpy_quant_f32[GGML_TYPE_Q5_0], "cpy_q5_0_f32", cpy_q5_0_f32_len, cpy_q5_0_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {(uint32_t)ggml_blck_size(GGML_TYPE_Q5_0), 1, 1}, {}, 1);
@@ -5860,6 +5948,7 @@ static vk_device ggml_vk_get_device(size_t idx) {
bool coopmat2_support = false;
bool coopmat2_decode_vector_support = false;
bool pipeline_executable_properties_support = false;
bool internally_sync_support = false;
device->coopmat_support = false;
device->integer_dot_product = false;
device->shader_64b_indexing = false;
@@ -5931,6 +6020,8 @@ static vk_device ggml_vk_get_device(size_t idx) {
} else if (strcmp("VK_EXT_shader_64bit_indexing", properties.extensionName) == 0) {
device->shader_64b_indexing = true;
#endif
} else if (strcmp(VK_KHR_INTERNALLY_SYNCHRONIZED_QUEUES_EXTENSION_NAME, properties.extensionName) == 0) {
internally_sync_support = true;
}
}
@@ -6117,14 +6208,6 @@ static vk_device ggml_vk_get_device(size_t idx) {
device->single_queue = compute_queue_family_index == transfer_queue_family_index && queue_family_props[compute_queue_family_index].queueCount == 1;
std::vector<vk::DeviceQueueCreateInfo> device_queue_create_infos;
if (compute_queue_family_index != transfer_queue_family_index) {
device_queue_create_infos.push_back({vk::DeviceQueueCreateFlags(), compute_queue_family_index, 1, priorities});
device_queue_create_infos.push_back({vk::DeviceQueueCreateFlags(), transfer_queue_family_index, 1, priorities + 1});
} else if(!device->single_queue) {
device_queue_create_infos.push_back({vk::DeviceQueueCreateFlags(), compute_queue_family_index, 2, priorities});
} else {
device_queue_create_infos.push_back({vk::DeviceQueueCreateFlags(), compute_queue_family_index, 1, priorities});
}
vk::DeviceCreateInfo device_create_info{};
std::vector<const char *> device_extensions;
vk::PhysicalDeviceFeatures device_features = device->physical_device.getFeatures();
@@ -6146,6 +6229,17 @@ static vk_device ggml_vk_get_device(size_t idx) {
last_struct = (VkBaseOutStructure *)&vk12_features;
VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR internally_synchronized_queues_features{};
internally_synchronized_queues_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INTERNALLY_SYNCHRONIZED_QUEUES_FEATURES_KHR;
internally_synchronized_queues_features.pNext = nullptr;
internally_synchronized_queues_features.internallySynchronizedQueues = VK_FALSE;
if (internally_sync_support) {
last_struct->pNext = (VkBaseOutStructure *)&internally_synchronized_queues_features;
last_struct = (VkBaseOutStructure *)&internally_synchronized_queues_features;
device_extensions.push_back(VK_KHR_INTERNALLY_SYNCHRONIZED_QUEUES_EXTENSION_NAME);
}
VkPhysicalDevicePipelineRobustnessFeaturesEXT pl_robustness_features;
pl_robustness_features.pNext = nullptr;
pl_robustness_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT;
@@ -6284,6 +6378,23 @@ static vk_device ggml_vk_get_device(size_t idx) {
vkGetPhysicalDeviceFeatures2(device->physical_device, &device_features2);
device->has_internally_synchronized_queues = internally_synchronized_queues_features.internallySynchronizedQueues;
// Build queue create infos only after querying whether internally synchronized queues are enabled.
// getQueue2() later uses the same flag, so creation/retrieval must stay consistent.
vk::DeviceQueueCreateFlags queue_flags = device->has_internally_synchronized_queues ?
eInternallySynchronizedKHR :
vk::DeviceQueueCreateFlags();
if (compute_queue_family_index != transfer_queue_family_index) {
device_queue_create_infos.push_back({queue_flags, compute_queue_family_index, 1, priorities});
device_queue_create_infos.push_back({queue_flags, transfer_queue_family_index, 1, priorities + 1});
} else if(!device->single_queue) {
device_queue_create_infos.push_back({queue_flags, compute_queue_family_index, 2, priorities});
} else {
device_queue_create_infos.push_back({queue_flags, compute_queue_family_index, 1, priorities});
}
device->pipeline_executable_properties_support = pipeline_executable_properties_support;
device->fp16 = device->fp16 && vk12_features.shaderFloat16;
@@ -6566,7 +6677,7 @@ static vk_device ggml_vk_get_device(size_t idx) {
device->device = device->physical_device.createDevice(device_create_info);
// Queues
ggml_vk_create_queue(device, device->compute_queue, compute_queue_family_index, 0, { vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer }, false);
device->compute_queue = ggml_vk_create_queue(device, compute_queue_family_index, 0, { vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer }, false);
// Shaders
// Disable matmul tile sizes early if performance low or not supported
@@ -6668,13 +6779,11 @@ static vk_device ggml_vk_get_device(size_t idx) {
if (!device->single_queue) {
const uint32_t transfer_queue_index = compute_queue_family_index == transfer_queue_family_index ? 1 : 0;
ggml_vk_create_queue(device, device->transfer_queue, transfer_queue_family_index, transfer_queue_index, { vk::PipelineStageFlagBits::eTransfer }, true);
device->transfer_queue = ggml_vk_create_queue(device, transfer_queue_family_index, transfer_queue_index, { vk::PipelineStageFlagBits::eTransfer }, true);
device->async_use_transfer_queue = prefers_transfer_queue || (getenv("GGML_VK_ASYNC_USE_TRANSFER_QUEUE") != nullptr);
} else {
// TODO: Use pointer or reference to avoid copy
device->transfer_queue.copyFrom(device->compute_queue);
device->transfer_queue.cmd_pool.init(device, &device->transfer_queue);
device->transfer_queue = ggml_vk_create_aliased_queue(device, device->compute_queue);
device->async_use_transfer_queue = false;
}
@@ -7237,7 +7346,7 @@ static void ggml_vk_init(ggml_backend_vk_context * ctx, size_t idx) {
ctx->fence = ctx->device->device.createFence({});
ctx->almost_ready_fence = ctx->device->device.createFence({});
ctx->compute_cmd_pool.init(ctx->device, &ctx->device->compute_queue);
ctx->compute_cmd_pool.init(ctx->device, ctx->device->compute_queue.get());
if (ctx->device->async_use_transfer_queue) {
vk::SemaphoreTypeCreateInfo tci{ vk::SemaphoreType::eTimeline, 0 };
vk::SemaphoreCreateInfo ci{};
@@ -7245,7 +7354,7 @@ static void ggml_vk_init(ggml_backend_vk_context * ctx, size_t idx) {
ctx->transfer_semaphore.s = ctx->device->device.createSemaphore(ci);
ctx->transfer_semaphore.value = 0;
ctx->transfer_cmd_pool.init(ctx->device, &ctx->device->transfer_queue);
ctx->transfer_cmd_pool.init(ctx->device, ctx->device->transfer_queue.get());
}
if (vk_perf_logger_enabled) {
@@ -7265,6 +7374,7 @@ static vk_pipeline ggml_vk_get_to_fp16(ggml_backend_vk_context * ctx, ggml_type
switch (type) {
case GGML_TYPE_F32:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7338,6 +7448,7 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_pipeline(ggml_backend_vk_conte
switch (src0_type) {
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7381,6 +7492,7 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec(ggml_backend_vk_context *
if (b_type == GGML_TYPE_Q8_1) {
switch (a_type) {
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7405,6 +7517,7 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec(ggml_backend_vk_context *
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7497,6 +7610,7 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_id_pipeline(ggml_backend_vk_co
switch (src0_type) {
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7543,6 +7657,7 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec_id(ggml_backend_vk_context
if (b_type == GGML_TYPE_Q8_1) {
switch (a_type) {
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -7567,6 +7682,7 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec_id(ggml_backend_vk_context
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -8093,7 +8209,7 @@ static void ggml_vk_buffer_write_2d(vk_buffer& dst, size_t offset, const void *
} else {
std::lock_guard<std::recursive_mutex> guard(dst->device->mutex);
vk_context subctx = ggml_vk_create_temporary_context(dst->device->transfer_queue.cmd_pool);
vk_context subctx = ggml_vk_create_temporary_context(dst->device->transfer_queue->cmd_pool);
ggml_vk_ctx_begin(dst->device, subctx);
bool ret = ggml_vk_buffer_write_2d_async(subctx, dst, offset, src, spitch, dpitch, width, height, true);
GGML_ASSERT(ret);
@@ -8208,7 +8324,7 @@ static void ggml_vk_buffer_read_2d(vk_buffer& src, size_t offset, void * dst, si
GGML_ASSERT(src->memory_property_flags & vk::MemoryPropertyFlagBits::eHostCoherent);
std::lock_guard<std::recursive_mutex> guard(src->device->mutex);
vk_context subctx = ggml_vk_create_temporary_context(src->device->compute_queue.cmd_pool);
vk_context subctx = ggml_vk_create_temporary_context(src->device->compute_queue->cmd_pool);
ggml_vk_ctx_begin(src->device, subctx);
subctx->s->buffer->buf.pipelineBarrier(
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer,
@@ -8234,7 +8350,7 @@ static void ggml_vk_buffer_read_2d(vk_buffer& src, size_t offset, void * dst, si
} else {
std::lock_guard<std::recursive_mutex> guard(src->device->mutex);
vk_context subctx = ggml_vk_create_temporary_context(src->device->transfer_queue.cmd_pool);
vk_context subctx = ggml_vk_create_temporary_context(src->device->transfer_queue->cmd_pool);
ggml_vk_ctx_begin(src->device, subctx);
bool ret = ggml_vk_buffer_read_2d_async(subctx, src, offset, dst, spitch, dpitch, width, height, true);
GGML_ASSERT(ret);
@@ -8271,7 +8387,7 @@ static void ggml_vk_buffer_copy(vk_buffer& dst, size_t dst_offset, vk_buffer& sr
std::lock_guard<std::recursive_mutex> guard(src->device->mutex);
VK_LOG_DEBUG("ggml_vk_buffer_copy(SINGLE_DEVICE, " << size << ")");
// Copy within the device
vk_context subctx = ggml_vk_create_temporary_context(src->device->transfer_queue.cmd_pool);
vk_context subctx = ggml_vk_create_temporary_context(src->device->transfer_queue->cmd_pool);
ggml_vk_ctx_begin(src->device, subctx);
ggml_vk_buffer_copy_async(subctx, dst, dst_offset, src, src_offset, size);
ggml_vk_ctx_end(subctx);
@@ -8314,7 +8430,7 @@ static void ggml_vk_buffer_memset(vk_buffer& dst, size_t offset, uint32_t c, siz
}
std::lock_guard<std::recursive_mutex> guard(dst->device->mutex);
vk_context subctx = ggml_vk_create_temporary_context(dst->device->transfer_queue.cmd_pool);
vk_context subctx = ggml_vk_create_temporary_context(dst->device->transfer_queue->cmd_pool);
ggml_vk_ctx_begin(dst->device, subctx);
subctx->s->buffer->buf.fillBuffer(dst->buffer, offset, size, c);
ggml_vk_ctx_end(subctx);
@@ -8603,6 +8719,7 @@ static vk_pipeline ggml_vk_get_cpy_pipeline(ggml_backend_vk_context * ctx, const
if (src->type == GGML_TYPE_F32) {
switch (to) {
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -8618,6 +8735,7 @@ static vk_pipeline ggml_vk_get_cpy_pipeline(ggml_backend_vk_context * ctx, const
if (to == GGML_TYPE_F32) {
switch (src->type) {
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -9044,7 +9162,7 @@ static bool ggml_vk_should_use_mmvq(const vk_device& device, uint32_t m, uint32_
// Quantization overhead is not worth it for small k
switch (device->vendor_id) {
case VK_VENDOR_ID_NVIDIA:
if (src0_type == GGML_TYPE_Q2_K || src0_type == GGML_TYPE_Q3_K || src0_type == GGML_TYPE_IQ1_S || src0_type == GGML_TYPE_IQ1_M) {
if (src0_type == GGML_TYPE_Q2_0 || src0_type == GGML_TYPE_Q2_K || src0_type == GGML_TYPE_Q3_K || src0_type == GGML_TYPE_IQ1_S || src0_type == GGML_TYPE_IQ1_M) {
return true;
}
@@ -9072,7 +9190,7 @@ static bool ggml_vk_should_use_mmvq(const vk_device& device, uint32_t m, uint32_
}
case VK_VENDOR_ID_INTEL:
if (device->architecture == vk_device_architecture::INTEL_XE2) {
if (src0_type == GGML_TYPE_Q2_K || src0_type == GGML_TYPE_Q3_K || src0_type == GGML_TYPE_Q6_K) {
if (src0_type == GGML_TYPE_Q2_0 || src0_type == GGML_TYPE_Q2_K || src0_type == GGML_TYPE_Q3_K || src0_type == GGML_TYPE_Q6_K) {
return true;
}
}
@@ -15840,19 +15958,17 @@ static void ggml_vk_synchronize(ggml_backend_vk_context * ctx) {
1, &ctx->transfer_semaphore.value,
0, nullptr,
};
vk::PipelineStageFlags stage = ctx->device->transfer_queue.stage_flags;
vk::PipelineStageFlags stage = ctx->device->transfer_queue->stage_flags;
vk::SubmitInfo si{
1, &ctx->transfer_semaphore.s, &stage,
0, nullptr,
0, nullptr,
};
si.setPNext(&tl_info);
std::lock_guard<std::mutex> guard(queue_mutex);
ctx->device->compute_queue.queue.submit({ si }, ctx->fence);
ctx->device->compute_queue->handle->submit({ si }, ctx->fence);
ctx->transfer_semaphore_last_submitted = ctx->transfer_semaphore.value;
} else {
std::lock_guard<std::mutex> guard(queue_mutex);
ctx->device->compute_queue.queue.submit({}, ctx->fence);
ctx->device->compute_queue->handle->submit({}, ctx->fence);
}
ggml_vk_wait_for_fence(ctx);
ctx->submit_pending = false;
@@ -16414,7 +16530,9 @@ static ggml_status ggml_backend_vk_graph_compute(ggml_backend_t backend, ggml_cg
vk::DebugUtilsLabelEXT dul = {};
dul.pLabelName = "ggml_backend_vk_graph_compute";
dul.color = std::array<float,4>{1.0f, 1.0f, 1.0f, 1.0f};
vk_instance.pfn_vkQueueBeginDebugUtilsLabelEXT(ctx->device->compute_queue.queue, reinterpret_cast<VkDebugUtilsLabelEXT*>(&dul));
std::lock_guard<vk_queue_handle> guard(*ctx->device->compute_queue->handle);
vk_instance.pfn_vkQueueBeginDebugUtilsLabelEXT(ctx->device->compute_queue->handle->queue, reinterpret_cast<VkDebugUtilsLabelEXT*>(&dul));
}
ctx->prealloc_size_add_rms_partials_offset = 0;
@@ -17429,6 +17547,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -17534,6 +17653,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -17574,6 +17694,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -17598,6 +17719,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -17614,6 +17736,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
case GGML_TYPE_F16:
case GGML_TYPE_BF16:
case GGML_TYPE_Q1_0:
case GGML_TYPE_Q2_0:
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q5_0:
@@ -208,6 +208,36 @@ void quantize(uint dst_idx, uint src_idx)
}
#endif
#if defined(DATA_A_Q2_0)
uint quantize_q2_0(float x)
{
const int q = int(x >= 0.0f ? floor(x + 0.5f) : ceil(x - 0.5f)) + 1;
return uint(clamp(q, 0, 3));
}
void quantize(uint dst_idx, uint src_idx)
{
float amax = 0.0f;
[[unroll]] for (int j = 0; j < QUANT_K_Q2_0; ++j) {
amax = max(amax, abs(float(data_s[src_idx + j])));
}
const float d = amax;
const float id = d != 0.0f ? 1.0f / d : 0.0f;
data_q[dst_idx].d = float16_t(d);
[[unroll]] for (int j = 0; j < QUANT_K_Q2_0 / 4; ++j) {
const uint q0 = quantize_q2_0(float(data_s[src_idx + 4*j ]) * id);
const uint q1 = quantize_q2_0(float(data_s[src_idx + 4*j + 1]) * id);
const uint q2 = quantize_q2_0(float(data_s[src_idx + 4*j + 2]) * id);
const uint q3 = quantize_q2_0(float(data_s[src_idx + 4*j + 3]) * id);
data_q[dst_idx].qs[j] = uint8_t(q0 | (q1 << 2u) | (q2 << 4u) | (q3 << 6u));
}
}
#endif
#if defined(DATA_A_IQ4_NL)
uint best_index(float x) {
if (x <= kvalues_iq4nl[0]) return 0;
@@ -143,6 +143,17 @@ vec4 dequantize4(uint ib, uint iqs, uint a_offset) {
}
#endif
#if defined(DATA_A_Q2_0)
vec2 dequantize(uint ib, uint iqs, uint a_offset) {
const uint bits = uint(data_a[a_offset + ib].qs[iqs / 4u]) >> (2u * (iqs % 4u));
return vec2(bits & 3u, (bits >> 2u) & 3u) - 1.0f;
}
vec4 dequantize4(uint ib, uint iqs, uint a_offset) {
const uint bits = uint(data_a[a_offset + ib].qs[iqs / 4u]);
return vec4(bits & 3u, (bits >> 2u) & 3u, (bits >> 4u) & 3u, bits >> 6u) - 1.0f;
}
#endif
#if defined(DATA_A_IQ1_S)
vec2 dequantize(uint ib, uint iqs, uint a_offset) {
const uint ib32 = iqs / 32;
@@ -547,7 +558,7 @@ vec2 get_dm(uint ib, uint a_offset) {
}
#endif
#if defined(DATA_A_Q4_0) || defined(DATA_A_Q5_0) || defined(DATA_A_Q8_0) || defined(DATA_A_IQ1_S) || defined(DATA_A_IQ2_XXS) || defined(DATA_A_IQ2_XS) || defined(DATA_A_IQ2_S) || defined(DATA_A_IQ3_XXS) || defined(DATA_A_IQ3_S) || defined(DATA_A_IQ4_XS) || defined(DATA_A_IQ4_NL)
#if defined(DATA_A_Q2_0) || defined(DATA_A_Q4_0) || defined(DATA_A_Q5_0) || defined(DATA_A_Q8_0) || defined(DATA_A_IQ1_S) || defined(DATA_A_IQ2_XXS) || defined(DATA_A_IQ2_XS) || defined(DATA_A_IQ2_S) || defined(DATA_A_IQ3_XXS) || defined(DATA_A_IQ3_S) || defined(DATA_A_IQ4_XS) || defined(DATA_A_IQ4_NL)
vec2 get_dm(uint ib, uint a_offset) {
return vec2(float(data_a[a_offset + ib].d), 0);
}
@@ -46,6 +46,26 @@ f16vec4 dequantFuncQ1_0_v(const in decodeBufQ1_0 bl, const in uint blockCoords[2
(qs_nib & 8u) != 0u ? d : md);
}
layout(buffer_reference, std430, buffer_reference_align = 2) buffer decodeBufQ2_0 {
block_q2_0 block;
};
float16_t dequantFuncQ2_0(const in decodeBufQ2_0 bl, const in uint blockCoords[2], const in uint coordInBlock[2])
{
const float16_t d = bl.block.d;
const uint idx = coordInBlock[1];
const uint bits = uint(bl.block.qs[idx >> 2]) >> (2u * (idx & 3u));
return (float16_t(bits & 3u) - float16_t(1.0)) * d;
}
f16vec4 dequantFuncQ2_0_v(const in decodeBufQ2_0 bl, const in uint blockCoords[2], const in uint coordInBlock[2])
{
const float16_t d = bl.block.d;
const uint idx = coordInBlock[1];
const uint bits = uint(bl.block.qs[idx >> 2]);
return f16vec4((vec4(bits & 3u, (bits >> 2u) & 3u, (bits >> 4u) & 3u, bits >> 6u) - 1.0f) * float(d));
}
layout(buffer_reference, std430, buffer_reference_align = 2) buffer decodeBufQ4_0 {
block_q4_0_packed16 block;
};
@@ -1330,6 +1350,9 @@ f16vec4 dequantFuncNVFP4_v(const in decodeBufNVFP4 bl, const in uint blockCoords
#if defined(DATA_A_Q1_0)
#define dequantFuncA dequantFuncQ1_0
#define dequantFuncA_v dequantFuncQ1_0_v
#elif defined(DATA_A_Q2_0)
#define dequantFuncA dequantFuncQ2_0
#define dequantFuncA_v dequantFuncQ2_0_v
#elif defined(DATA_A_Q4_0)
#define dequantFuncA dequantFuncQ4_0
#define dequantFuncA_v dequantFuncQ4_0_v
@@ -0,0 +1,29 @@
#version 450
#include "dequant_head.glsl"
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
layout (binding = 0) readonly buffer A {block_q2_0 data_a[];};
layout (binding = 1) writeonly buffer D {D_TYPE data_b[];};
void main() {
const uint i = gl_WorkGroupID.x * 4 + gl_LocalInvocationID.x / 64;
const uint tid = gl_LocalInvocationID.x % 64;
const uint il = tid / 4;
const uint ir = tid % 4;
const uint ib = 4*i + ir;
if (ib >= p.nel / QUANT_K_Q2_0) {
return;
}
const uint b_idx = 256*i + QUANT_K_Q2_0*ir + 4*il;
const uint bits = uint(data_a[ib].qs[il]);
const float d = float(data_a[ib].d);
data_b[b_idx ] = D_TYPE(d * (float(bits & 3u) - 1.0f));
data_b[b_idx + 1] = D_TYPE(d * (float((bits >> 2u) & 3u) - 1.0f));
data_b[b_idx + 2] = D_TYPE(d * (float((bits >> 4u) & 3u) - 1.0f));
data_b[b_idx + 3] = D_TYPE(d * (float(bits >> 6u) - 1.0f));
}
@@ -11,10 +11,10 @@
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
#if defined(DATA_A_QUANT_LEGACY) || defined(DATA_A_MXFP4)
#define K_PER_ITER 8
#elif defined(DATA_A_QUANT_K)
#if defined(DATA_A_Q2_0) || defined(DATA_A_QUANT_K)
#define K_PER_ITER 16
#elif defined(DATA_A_QUANT_LEGACY) || defined(DATA_A_MXFP4)
#define K_PER_ITER 8
#elif defined(DATA_A_IQ1_S) || defined(DATA_A_IQ1_M)
#define K_PER_ITER 32
#else
@@ -4,7 +4,11 @@
#include "types.glsl"
#if defined(DATA_A_Q4_0) || defined(DATA_A_Q5_0) || defined(DATA_A_Q8_0) || defined(DATA_A_IQ1_S) || defined(DATA_A_IQ2_XXS) || defined(DATA_A_IQ2_XS) || defined(DATA_A_IQ2_S) || defined(DATA_A_IQ3_XXS) || defined(DATA_A_IQ3_S) || defined(DATA_A_IQ4_XS) || defined(DATA_A_IQ4_NL)
#if defined(DATA_A_Q2_0)
FLOAT_TYPE get_dm(uint ib) {
return FLOAT_TYPE(data_a[ib / 2].d);
}
#elif defined(DATA_A_Q4_0) || defined(DATA_A_Q5_0) || defined(DATA_A_Q8_0) || defined(DATA_A_IQ1_S) || defined(DATA_A_IQ2_XXS) || defined(DATA_A_IQ2_XS) || defined(DATA_A_IQ2_S) || defined(DATA_A_IQ3_XXS) || defined(DATA_A_IQ3_S) || defined(DATA_A_IQ4_XS) || defined(DATA_A_IQ4_NL)
FLOAT_TYPE get_dm(uint ib) {
return FLOAT_TYPE(data_a[ib].d);
}
@@ -30,6 +34,27 @@ FLOAT_TYPEV2 get_dm(uint ib) {
#endif
// Each iqs value maps to a 32-bit integer
#if defined(DATA_A_Q2_0)
uint unpack_q2_0(uint bits) {
// Move bit pairs [1:0], [3:2], [5:4], [7:6] to [1:0], [9:8], [17:16], [25:24].
bits &= 0xffu;
bits = (bits | (bits << 12u)) & 0x000f000fu;
return (bits | (bits << 6u)) & 0x03030303u;
}
i32vec4 repack4(uint ib, uint iqs) {
const uint qs_idx = (ib & 1u) * 4u + iqs * 2u;
const uint bits = pack32(u16vec2(data_a_packed16[ib / 2].qs[qs_idx],
data_a_packed16[ib / 2].qs[qs_idx + 1]));
return i32vec4(unpack_q2_0(bits), unpack_q2_0(bits >> 8u),
unpack_q2_0(bits >> 16u), unpack_q2_0(bits >> 24u));
}
FLOAT_TYPE mul_q8_1(const int32_t q_sum, const float da, const vec2 dsb, const int32_t sum_divisor) {
return FLOAT_TYPE(da * (float(q_sum) * dsb.x - dsb.y / float(sum_divisor)));
}
#endif
#if defined(DATA_A_Q4_0)
// 2-byte loads for Q4_0 blocks (18 bytes)
i32vec2 repack(uint ib, uint iqs) {
@@ -132,7 +157,19 @@ FLOAT_TYPE mul_q8_1(const int32_t q_sum, const float da, const vec2 dsb, const i
}
#endif
#if defined(DATA_A_QUANT_LEGACY) || defined(DATA_A_MXFP4)
#if defined(DATA_A_Q2_0)
FLOAT_TYPE mmvq_dot_product(const uint ib_a, const uint iqs) {
int32_t q_sum = 0;
const i32vec4 qs_a = repack4(ib_a, iqs);
q_sum += dotPacked4x8EXT(qs_a.x, cache_b_qs[0]);
q_sum += dotPacked4x8EXT(qs_a.y, cache_b_qs[1]);
q_sum += dotPacked4x8EXT(qs_a.z, cache_b_qs[2]);
q_sum += dotPacked4x8EXT(qs_a.w, cache_b_qs[3]);
// 16 quants per call => divide sums by 32/16 = 2
return mul_q8_1(q_sum, get_dm(ib_a), cache_b_ds, 2);
}
#elif defined(DATA_A_QUANT_LEGACY) || defined(DATA_A_MXFP4)
FLOAT_TYPE mmvq_dot_product(const uint ib_a, const uint iqs) {
int32_t q_sum = 0;
#if QUANT_R == 2
@@ -151,6 +151,18 @@ void load_a_to_shmem(const uint pos_a, const uint row, const uint col, const uin
buf_a[buf_idx + 1] = FLOAT_TYPEV2((bits & 0x04u) != 0u ? d : -d, (bits & 0x08u) != 0u ? d : -d);
buf_a[buf_idx + 2] = FLOAT_TYPEV2((bits & 0x10u) != 0u ? d : -d, (bits & 0x20u) != 0u ? d : -d);
buf_a[buf_idx + 3] = FLOAT_TYPEV2((bits & 0x40u) != 0u ? d : -d, (bits & 0x80u) != 0u ? d : -d);
#elif defined(DATA_A_Q2_0)
const uint idx = pos_a + col * p.stride_a / LOAD_VEC_A + row;
const uint buf_idx = col * SHMEM_STRIDE + row * LOAD_VEC_A / 2;
const uint ib = idx / 16;
const uint iqs = idx & 0xfu;
const FLOAT_TYPE d = FLOAT_TYPE(data_a[ib].d);
const uint bits = uint(data_a[ib].qs[iqs]);
buf_a[buf_idx ] = d * (FLOAT_TYPEV2(bits & 3u, (bits >> 2u) & 3u) - FLOAT_TYPEV2(1.0f));
buf_a[buf_idx + 1] = d * (FLOAT_TYPEV2((bits >> 4u) & 3u, bits >> 6u) - FLOAT_TYPEV2(1.0f));
#elif defined(DATA_A_Q2_K)
const uint idx = pos_a + col * p.stride_a / LOAD_VEC_A + row;
const uint buf_idx = col * SHMEM_STRIDE + row * LOAD_VEC_A / 2;
@@ -6,6 +6,40 @@
// Each iqs value maps to a 32-bit integer
#if defined(DATA_A_Q2_0)
void block_a_to_shmem(const uint buf_ib, const uint ib, const uint iqs) {
const uint block_idx = ib / 2;
const uint byte_idx = (ib & 1u) * 8u + iqs;
const uint bits = uint(data_a[block_idx].qs[byte_idx]);
buf_a[buf_ib].qs[iqs] = pack32(i8vec4(
int8_t(bits & 3u),
int8_t((bits >> 2u) & 3u),
int8_t((bits >> 4u) & 3u),
int8_t(bits >> 6u)));
if (iqs == 0) {
buf_a[buf_ib].dm = FLOAT_TYPE(data_a[block_idx].d);
}
}
void block_a_to_registers(const uint reg_ib, const uint buf_ib) {
cache_a[reg_ib].dm = buf_a[buf_ib].dm;
[[unroll]] for (uint iqs = 0; iqs < 8; ++iqs) {
cache_a[reg_ib].qs[iqs] = buf_a[buf_ib].qs[iqs];
}
}
ACC_TYPE mmq_dot_product(const uint ib_a) {
int32_t q_sum = 0;
[[unroll]] for (uint iqs = 0; iqs < 8; ++iqs) {
q_sum += dotPacked4x8EXT(cache_a[ib_a].qs[iqs], cache_b.qs[iqs]);
}
return ACC_TYPE(float(cache_a[ib_a].dm) * (float(q_sum) * float(cache_b.ds.x) - float(cache_b.ds.y)));
}
#endif
#if defined(DATA_A_Q4_0) || defined(DATA_A_Q4_1)
// 2-byte loads for Q4_0 blocks (18 bytes)
// 4-byte loads for Q4_1 blocks (20 bytes)
@@ -13,6 +13,12 @@ struct block_a_cache {
uint32_t qs[16/4];
FLOAT_TYPE dm;
};
#elif defined(DATA_A_Q2_0)
#define QUANT_R_MMQ 1
struct block_a_cache {
int32_t qs[8];
FLOAT_TYPE dm;
};
#elif defined(DATA_A_Q4_1)
#define QUANT_R_MMQ 2
struct block_a_cache {
@@ -211,6 +211,30 @@ struct block_q1_0
#define A_TYPE block_q1_0
#endif
#define QUANT_K_Q2_0 64
#define QUANT_R_Q2_0 1
struct block_q2_0
{
float16_t d;
uint8_t qs[QUANT_K_Q2_0 / 4];
};
struct block_q2_0_packed16
{
float16_t d;
uint16_t qs[QUANT_K_Q2_0 / 8];
};
#if defined(DATA_A_Q2_0)
#define QUANT_K QUANT_K_Q2_0
#define QUANT_R QUANT_R_Q2_0
#define QUANT_AUXF 1
#define A_TYPE block_q2_0
#define A_TYPE_PACKED16 block_q2_0_packed16
#define DATA_A_QUANT_LEGACY
#endif
#define QUANT_K_Q8_1 32
#define QUANT_R_Q8_1 1
@@ -50,6 +50,7 @@ const std::vector<std::string> type_names = {
"f32",
"f16",
"q1_0",
"q2_0",
"q4_0",
"q4_1",
"q5_0",
@@ -232,7 +233,7 @@ bool is_quantized_type(const std::string& type_name) {
}
bool is_legacy_quant(const std::string& type_name) {
return type_name == "q4_0" || type_name == "q4_1" || type_name == "q5_0" || type_name == "q5_1" || type_name == "q8_0";
return type_name == "q2_0" || type_name == "q4_0" || type_name == "q4_1" || type_name == "q5_0" || type_name == "q5_1" || type_name == "q8_0";
}
bool is_k_quant(const std::string& type_name) {
@@ -583,7 +584,7 @@ void matmul_shaders(bool fp16, MatMulIdType matmul_id_type, bool coopmat, bool c
std::string load_vec_quant = "2";
if ((tname == "q1_0") || (tname == "q4_0") || (tname == "q4_1") || (tname == "q5_1") || (tname == "iq1_s") || (tname == "iq1_m") || (tname == "iq2_xxs") || (tname == "iq2_xs") || (tname == "iq2_s"))
load_vec_quant = "8";
else if ((tname == "q5_0") || (tname == "q8_0") || (tname == "q2_k") || (tname == "q4_k") || (tname == "q5_k") || (tname == "iq3_xxs") || (tname == "iq3_s") || (tname == "iq4_xs") || (tname == "iq4_nl") || (tname == "mxfp4") || (tname == "nvfp4"))
else if ((tname == "q2_0") || (tname == "q5_0") || (tname == "q8_0") || (tname == "q2_k") || (tname == "q4_k") || (tname == "q5_k") || (tname == "iq3_xxs") || (tname == "iq3_s") || (tname == "iq4_xs") || (tname == "iq4_nl") || (tname == "mxfp4") || (tname == "nvfp4"))
load_vec_quant = "4";
if (tname == "bf16") {
@@ -823,13 +824,13 @@ void process_shaders() {
string_to_spv("cpy_transpose_16", "copy_transpose.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "uint16_t"}});
string_to_spv("cpy_transpose_32", "copy_transpose.comp", {{"A_TYPE", "uint"}, {"D_TYPE", "uint"}});
for (std::string t : {"q1_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
for (std::string t : {"q1_0", "q2_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
string_to_spv("cpy_f32_" + t, "copy_to_quant.comp", {{"DATA_A_" + to_uppercase(t), "1"}, {"S_TYPE", "float"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
string_to_spv("cpy_" + t + "_f32", "copy_from_quant.comp", {{"DATA_A_" + to_uppercase(t), "1"}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
}
for (auto src : {std::pair{"f32", "float"}, std::pair{"f16", "float16_t"}}) {
for (std::string dst : {"f32", "f16", "bf16", "q1_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
for (std::string dst : {"f32", "f16", "bf16", "q1_0", "q2_0", "q4_0", "q4_1", "q5_0", "q5_1", "q8_0", "iq4_nl"}) {
string_to_spv("set_rows_" + std::string(src.first) + "_" + dst + "_i32", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(dst), "1"}, {"B_TYPE", "uint"}, {"B_SIZE", "32"}, {"S_TYPE", src.second}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
string_to_spv("set_rows_" + std::string(src.first) + "_" + dst + "_i64", "copy_to_quant.comp", {{"SET_ROWS", "1"}, {"DATA_A_" + to_uppercase(dst), "1"}, {"B_TYPE", "uvec2"}, {"B_SIZE", "64"}, {"S_TYPE", src.second}, {"D_TYPE", "float"}, {"FLOAT_TYPE", "float"}});
}
+1 -1
View File
@@ -1 +1 @@
eaa0a74fa768bb72da623a61d9da3d436053ea91
9be313313c8ecb9488911bd64550190e3ed80f38
+79 -22
View File
@@ -22,7 +22,7 @@ static constexpr uint32_t DSV4_STATE_MAGIC = 0x34565344; // DSV4
static constexpr uint32_t DSV4_STATE_VERSION = 1;
static constexpr uint32_t DSV4_STATE_MODE_FULL = 0;
static constexpr uint32_t DSV4_STATE_MODE_PARTIAL = 1;
static constexpr uint32_t DSV4_K_CACHE_STATE_VER = 1;
static constexpr uint32_t DSV4_K_CACHE_STATE_VER = 2;
static constexpr uint32_t DSV4_COMP_STATE_VER = 1;
static uint32_t dsv4_comp_size(uint32_t kv_size, uint32_t ratio) {
@@ -38,6 +38,16 @@ static void dsv4_clear_tensor_stream(ggml_tensor * tensor, uint32_t stream) {
ggml_backend_tensor_memset(tensor, 0, stream*stream_size, stream_size);
}
static uint32_t dsv4_state_n_used_k_rows(llama_pos pos_max, uint32_t ratio, uint32_t kv_size) {
if (pos_max < 0) {
return 0;
}
const uint64_t n_rows = ((uint64_t) pos_max + 1)/ratio;
return (uint32_t) std::min<uint64_t>(kv_size, n_rows);
}
static int64_t dsv4_stream_offset(uint32_t n_stream, llama_seq_id seq_id, uint32_t size) {
if (n_stream <= 1) {
return 0;
@@ -239,6 +249,7 @@ static void dsv4_state_dst_stream_range(
static void dsv4_state_write_tensor_streams(
llama_io_write_i & io,
ggml_tensor * tensor,
uint32_t tensor_rows,
uint32_t n_rows,
uint32_t s0,
uint32_t ns) {
@@ -247,20 +258,31 @@ static void dsv4_state_write_tensor_streams(
const uint64_t rows = n_rows;
const uint64_t row_size = ggml_row_size(tensor->type, tensor->ne[0]);
if (n_rows > tensor_rows) {
throw std::runtime_error("DSV4 state tensor row count exceeds storage");
}
io.write(&type_i, sizeof(type_i));
io.write(&ne0, sizeof(ne0));
io.write(&rows, sizeof(rows));
io.write(&row_size, sizeof(row_size));
const size_t offset = (size_t) s0*n_rows*row_size;
const size_t size = (size_t) ns*n_rows*row_size;
const size_t stream_stride = (size_t) tensor_rows*row_size;
const size_t size = (size_t) n_rows*row_size;
if (size == 0) {
return;
}
io.write_tensor(tensor, offset, size);
for (uint32_t s = 0; s < ns; ++s) {
const size_t offset = (size_t) (s0 + s)*stream_stride;
io.write_tensor(tensor, offset, size);
}
}
static void dsv4_state_read_tensor_streams(
llama_io_read_i & io,
ggml_tensor * tensor,
uint32_t tensor_rows,
uint32_t n_rows,
uint32_t s0,
uint32_t ns) {
@@ -282,18 +304,28 @@ static void dsv4_state_read_tensor_streams(
if (type_i != type_i_ref || ne0 != ne0_ref || rows != rows_ref || row_size != row_size_ref) {
throw std::runtime_error("DSV4 state tensor metadata mismatch");
}
if (n_rows > tensor_rows) {
throw std::runtime_error("DSV4 state tensor row count exceeds storage");
}
const size_t offset = (size_t) s0*n_rows*row_size;
const size_t size = (size_t) ns*n_rows*row_size;
const size_t stream_stride = (size_t) tensor_rows*row_size;
const size_t size = (size_t) n_rows*row_size;
if (size == 0) {
return;
}
io.read_tensor(tensor, offset, size);
for (uint32_t s = 0; s < ns; ++s) {
const size_t offset = (size_t) (s0 + s)*stream_stride;
io.read_tensor(tensor, offset, size);
}
}
static void dsv4_state_write_k_cache(
llama_io_write_i & io,
const llama_kv_cache * kv,
llama_seq_id seq_id,
llama_state_seq_flags flags) {
llama_state_seq_flags flags,
uint32_t n_rows) {
GGML_UNUSED(flags);
uint32_t s0;
@@ -305,14 +337,18 @@ static void dsv4_state_write_k_cache(
const auto layer_ids = kv->get_layer_ids();
const uint32_t n_layer = layer_ids.size();
if (n_rows > kv_size) {
throw std::runtime_error("DSV4 K-cache state row count exceeds cache size");
}
io.write(&version, sizeof(version));
io.write(&kv_size, sizeof(kv_size));
io.write(&n_rows, sizeof(n_rows));
io.write(&ns, sizeof(ns));
io.write(&n_layer, sizeof(n_layer));
for (uint32_t il : layer_ids) {
io.write(&il, sizeof(il));
dsv4_state_write_tensor_streams(io, kv->get_k_storage(il), kv_size, s0, ns);
dsv4_state_write_tensor_streams(io, kv->get_k_storage(il), kv_size, n_rows, s0, ns);
}
}
@@ -324,19 +360,26 @@ static void dsv4_state_read_k_cache(
GGML_UNUSED(flags);
uint32_t version;
uint32_t kv_size_ref;
uint32_t n_rows_ref;
uint32_t ns;
uint32_t n_layer_ref;
io.read(&version, sizeof(version));
io.read(&kv_size_ref, sizeof(kv_size_ref));
io.read(&n_rows_ref, sizeof(n_rows_ref));
io.read(&ns, sizeof(ns));
io.read(&n_layer_ref, sizeof(n_layer_ref));
if (version != DSV4_K_CACHE_STATE_VER) {
if (version != 1 && version != DSV4_K_CACHE_STATE_VER) {
throw std::runtime_error("DSV4 K-cache state version mismatch");
}
if (kv_size_ref != kv->get_size()) {
const uint32_t kv_size = kv->get_size();
if (version == 1 && n_rows_ref != kv_size) {
LLAMA_LOG_INFO("kv size ref %d kv %d\n", n_rows_ref, kv_size);
throw std::runtime_error("DSV4 K-cache state size mismatch");
}
if (n_rows_ref > kv_size) {
LLAMA_LOG_INFO("kv rows ref %d kv %d\n", n_rows_ref, kv_size);
throw std::runtime_error("DSV4 K-cache state size mismatch");
}
@@ -355,7 +398,7 @@ static void dsv4_state_read_k_cache(
throw std::runtime_error("DSV4 K-cache layer id mismatch");
}
dsv4_state_read_tensor_streams(io, kv->get_k_storage(il), kv->get_size(), s0, ns);
dsv4_state_read_tensor_streams(io, kv->get_k_storage(il), kv_size, n_rows_ref, s0, ns);
}
}
@@ -882,8 +925,8 @@ void llama_dsv4_comp_state::state_write(llama_io_write_i & io, llama_seq_id seq_
for (const auto & layer : layers) {
io.write(&layer.il, sizeof(layer.il));
dsv4_state_write_tensor_streams(io, layer.kv, state_size, s0, ns);
dsv4_state_write_tensor_streams(io, layer.score, state_size, s0, ns);
dsv4_state_write_tensor_streams(io, layer.kv, state_size, state_size, s0, ns);
dsv4_state_write_tensor_streams(io, layer.score, state_size, state_size, s0, ns);
}
}
@@ -924,8 +967,8 @@ void llama_dsv4_comp_state::state_read(llama_io_read_i & io, llama_seq_id seq_id
throw std::runtime_error("DSV4 compressor state layer id mismatch");
}
dsv4_state_read_tensor_streams(io, layer.kv, state_size, s0, ns);
dsv4_state_read_tensor_streams(io, layer.score, state_size, s0, ns);
dsv4_state_read_tensor_streams(io, layer.kv, state_size, state_size, s0, ns);
dsv4_state_read_tensor_streams(io, layer.score, state_size, state_size, s0, ns);
}
}
@@ -1328,9 +1371,19 @@ void llama_kv_cache_dsv4::state_write(llama_io_write_i & io, llama_seq_id seq_id
kv_raw->state_write(io, seq_id, flags);
if (!partial_only) {
dsv4_state_write_k_cache(io, kv_csa.get(), seq_id, flags);
dsv4_state_write_k_cache(io, kv_hca.get(), seq_id, flags);
dsv4_state_write_k_cache(io, kv_lid.get(), seq_id, flags);
const llama_pos pos_max = seq_id >= 0 ? kv_raw->seq_pos_max(seq_id) : -1;
//FIXME : note that we conflate token positions with rows, which is not true for multi-modal case.
const uint32_t n_rows_csa = seq_id >= 0 ?
dsv4_state_n_used_k_rows(pos_max, DSV4_CSA_RATIO, kv_csa->get_size()) : kv_csa->get_size();
const uint32_t n_rows_hca = seq_id >= 0 ?
dsv4_state_n_used_k_rows(pos_max, DSV4_HCA_RATIO, kv_hca->get_size()) : kv_hca->get_size();
const uint32_t n_rows_lid = seq_id >= 0 ?
dsv4_state_n_used_k_rows(pos_max, DSV4_CSA_RATIO, kv_lid->get_size()) : kv_lid->get_size();
dsv4_state_write_k_cache(io, kv_csa.get(), seq_id, flags, n_rows_csa);
dsv4_state_write_k_cache(io, kv_hca.get(), seq_id, flags, n_rows_hca);
dsv4_state_write_k_cache(io, kv_lid.get(), seq_id, flags, n_rows_lid);
}
csa_state->state_write(io, seq_id, flags);
@@ -1366,6 +1419,10 @@ void llama_kv_cache_dsv4::state_read(llama_io_read_i & io, llama_seq_id seq_id,
kv_raw->state_read(io, seq_id, flags);
if (!partial_only) {
kv_csa->clear(true);
kv_hca->clear(true);
kv_lid->clear(true);
dsv4_state_read_k_cache(io, kv_csa.get(), seq_id, flags);
dsv4_state_read_k_cache(io, kv_hca.get(), seq_id, flags);
dsv4_state_read_k_cache(io, kv_lid.get(), seq_id, flags);
+3
View File
@@ -306,6 +306,9 @@ static bool tensor_allows_quantization(const llama_model_quantize_params * param
// NOTE: can't use LLM_TN here because the layer number is not known
quantize &= name.find("ffn_gate_inp.weight") == std::string::npos;
// do not quantize the i32 token-id -> expert-id routing table (DeepSeek-V4)
quantize &= name.find("ffn_gate_tid2eid.weight") == std::string::npos;
// these are very small (e.g. 4x4)
quantize &= name.find("altup") == std::string::npos;
quantize &= name.find("laurel") == std::string::npos;
+17
View File
@@ -1,5 +1,6 @@
#include "models.h"
#include "llama-impl.h"
#include "llama-kv-cache.h"
#include "llama-kv-cache-iswa.h"
@@ -164,9 +165,25 @@ llama_model_dflash::graph<false>::graph(const llama_model & model, const llm_gra
const auto * kv = is_swa ? inp_attn_iswa->mctx->get_swa() : inp_attn_iswa->mctx->get_base();
ggml_tensor * k_idxs = is_swa ? inp_attn_iswa->get_k_idxs_swa() : inp_attn_iswa->get_k_idxs();
ggml_tensor * v_idxs = is_swa ? inp_attn_iswa->get_v_idxs_swa() : inp_attn_iswa->get_v_idxs();
// rotate K/V into the cache's rotated space
ggml_tensor * k_rot = is_swa ? inp_attn_iswa->self_k_rot_swa : inp_attn_iswa->self_k_rot;
ggml_tensor * v_rot = is_swa ? inp_attn_iswa->self_v_rot_swa : inp_attn_iswa->self_v_rot;
if (k_rot) {
Kcur = llama_mul_mat_hadamard(ctx0, Kcur, k_rot);
}
if (v_rot) {
Vcur = llama_mul_mat_hadamard(ctx0, Vcur, v_rot);
}
ggml_build_forward_expand(gf, kv->cpy_k(ctx0, Kcur, k_idxs, il));
ggml_build_forward_expand(gf, kv->cpy_v(ctx0, Vcur, v_idxs, il));
} else {
// rotate K/V into the cache's rotated space
if (inp_attn->self_k_rot) {
Kcur = llama_mul_mat_hadamard(ctx0, Kcur, inp_attn->self_k_rot);
}
if (inp_attn->self_v_rot) {
Vcur = llama_mul_mat_hadamard(ctx0, Vcur, inp_attn->self_v_rot);
}
ggml_build_forward_expand(gf, inp_attn->mctx->cpy_k(ctx0, Kcur, inp_attn->get_k_idxs(), il));
ggml_build_forward_expand(gf, inp_attn->mctx->cpy_v(ctx0, Vcur, inp_attn->get_v_idxs(), il));
}
+13 -3
View File
@@ -2435,13 +2435,17 @@ struct test_set_rows : public test_case {
}
double max_nmse_err() override {
if (type_dst == GGML_TYPE_Q4_0 || type_dst == GGML_TYPE_Q4_1 || type_dst == GGML_TYPE_IQ4_NL ||
if (type_dst == GGML_TYPE_Q2_0 || type_dst == GGML_TYPE_Q4_0 || type_dst == GGML_TYPE_Q4_1 ||
type_dst == GGML_TYPE_IQ4_NL ||
type_dst == GGML_TYPE_Q5_0 || type_dst == GGML_TYPE_Q5_1 || type_dst == GGML_TYPE_Q8_0) {
// estimate what the max nmse error would be if one quantized value is
// off by one. The test values are distributed in [-1,1], so it'll be
// roughly (2.0 / 2^bits)^2, divided by the mean square value of the reference,
// which is roughly 0.25 times the number of elements.
double err_estimate = 1.0f/8.0f;
if (type_src == GGML_TYPE_F16 && type_dst == GGML_TYPE_Q2_0) {
err_estimate *= 4.0f;
}
if (type_dst == GGML_TYPE_Q5_0 || type_dst == GGML_TYPE_Q5_1) {
err_estimate /= 2.0f;
}
@@ -3798,6 +3802,7 @@ struct test_dsv4_hc : public test_case {
float lo;
float hi;
if (!tensor_range(name, lo, hi)) {
init_tensor_uniform(t);
continue;
}
@@ -5955,6 +5960,7 @@ enum MoeGatingFunc {
GATING_FUNC_SOFTMAX,
GATING_FUNC_SIGMOID,
GATING_FUNC_SOFTMAX_WEIGHT,
GATING_FUNC_SQRT_SOFTPLUS,
};
struct test_topk_moe : public test_case {
@@ -5998,7 +6004,8 @@ struct test_topk_moe : public test_case {
ggml_tensor * logits = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne.data());
ggml_tensor * probs =
(gating_func == GATING_FUNC_SOFTMAX) ? ggml_soft_max(ctx, logits) :
(gating_func == GATING_FUNC_SIGMOID) ? ggml_sigmoid(ctx, logits) : logits;
(gating_func == GATING_FUNC_SIGMOID) ? ggml_sigmoid(ctx, logits) :
(gating_func == GATING_FUNC_SQRT_SOFTPLUS) ? ggml_sqrt(ctx, ggml_softplus(ctx, logits)) : logits;
ggml_set_name(probs, "probs");
ggml_tensor * selection_probs = probs;
@@ -7964,6 +7971,7 @@ static const ggml_type all_types[] = {
GGML_TYPE_Q5_0, GGML_TYPE_Q5_1,
GGML_TYPE_Q8_0,
GGML_TYPE_Q1_0,
GGML_TYPE_Q2_0,
GGML_TYPE_MXFP4, GGML_TYPE_NVFP4,
GGML_TYPE_Q2_K, GGML_TYPE_Q3_K,
GGML_TYPE_Q4_K, GGML_TYPE_Q5_K,
@@ -7978,6 +7986,7 @@ static const ggml_type base_types[] = {
GGML_TYPE_F32, GGML_TYPE_F16,
GGML_TYPE_Q8_0, // for I8MM tests
GGML_TYPE_Q1_0,
GGML_TYPE_Q2_0,
GGML_TYPE_Q4_0,
GGML_TYPE_Q4_1, // for I8MM tests
GGML_TYPE_Q4_K,
@@ -9577,7 +9586,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
}
}
for (auto gate : {GATING_FUNC_SOFTMAX, GATING_FUNC_SIGMOID, GATING_FUNC_SOFTMAX_WEIGHT}) {
for (auto gate : {GATING_FUNC_SOFTMAX, GATING_FUNC_SIGMOID, GATING_FUNC_SOFTMAX_WEIGHT, GATING_FUNC_SQRT_SOFTPLUS}) {
for (bool with_norm : {false, true}) {
for (bool bias_probs : {false, true}) {
for (float scale_w : {0.0f, 2.0f}) {
@@ -9589,6 +9598,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
test_cases.emplace_back(new test_topk_moe({128, 1, 1, 1}, 128, with_norm, bias_probs, gate, scale_w));
test_cases.emplace_back(new test_topk_moe({129, 1, 1, 1}, 128, with_norm, bias_probs, gate, scale_w));
test_cases.emplace_back(new test_topk_moe({160, 4, 1, 1}, 160, with_norm, bias_probs, gate, scale_w));
test_cases.emplace_back(new test_topk_moe({256, 22, 1, 1}, 6, with_norm, bias_probs, gate, scale_w)); // Used by DeepSeek-V4
test_cases.emplace_back(new test_topk_moe({288, 22, 1, 1}, 8, with_norm, bias_probs, gate, scale_w)); // Used by StepFun 3.7
}
}
+6 -2
View File
@@ -54,6 +54,7 @@
| `-ctv, --cache-type-v TYPE` | KV cache data type for V<br/>allowed values: f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1<br/>(default: f16)<br/>(env: LLAMA_ARG_CACHE_TYPE_V) |
| `-dt, --defrag-thold N` | KV cache defragmentation threshold (DEPRECATED)<br/>(env: LLAMA_ARG_DEFRAG_THOLD) |
| `-np, --parallel N` | number of parallel sequences to decode (default: 1)<br/>(env: LLAMA_ARG_N_PARALLEL) |
| `--rpc SERVERS` | comma-separated list of RPC servers (host:port)<br/>(env: LLAMA_ARG_RPC) |
| `--mlock` | force system to keep model in RAM rather than swapping or compressing<br/>(env: LLAMA_ARG_MLOCK) |
| `--mmap, --no-mmap` | whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock) (default: enabled)<br/>(env: LLAMA_ARG_MMAP) |
| `-dio, --direct-io, -ndio, --no-direct-io` | use DirectIO if available. (default: disabled)<br/>(env: LLAMA_ARG_DIO) |
@@ -142,6 +143,7 @@
| Argument | Explanation |
| -------- | ----------- |
| `--server-base URL` | connect to this server instead of starting a new one, example: 'http://localhost:8080' (default: none) |
| `--verbose-prompt` | print a verbose prompt before generation (default: false) |
| `--display-prompt, --no-display-prompt` | whether to print prompt at generation (default: true) |
| `-co, --color [on\|off\|auto]` | Colorize output to distinguish prompt and user input from generations ('on', 'off', or 'auto', default: 'auto')<br/>'auto' enables colors when output is to a terminal |
@@ -164,17 +166,19 @@
| `--image, --audio, --video FILE` | path to an image, audio, or video file. use with multimodal models, use comma-separated values for multiple files |
| `--image-min-tokens N` | minimum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MIN_TOKENS) |
| `--image-max-tokens N` | maximum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MAX_TOKENS) |
| `-o, --output, --output-file FNAME` | output file (default: '') |
| `--chat-template-kwargs STRING` | sets additional params for the json template parser, must be a valid json object string, e.g. '{"key1":"value1","key2":"value2"}'<br/>(env: LLAMA_ARG_CHAT_TEMPLATE_KWARGS) |
| `--jinja, --no-jinja` | whether to use jinja template engine for chat (default: enabled)<br/>(env: LLAMA_ARG_JINJA) |
| `--reasoning-format FORMAT` | controls whether thought tags are allowed and/or extracted from the response, and in which format they're returned; one of:<br/>- none: leaves thoughts unparsed in `message.content`<br/>- deepseek: puts thoughts in `message.reasoning_content`<br/>- deepseek-legacy: keeps `<think>` tags in `message.content` while also populating `message.reasoning_content`<br/>(default: auto)<br/>(env: LLAMA_ARG_THINK) |
| `-rea, --reasoning [on\|off\|auto]` | Use reasoning/thinking in the chat ('on', 'off', or 'auto', default: 'auto' (detect from template))<br/>(env: LLAMA_ARG_REASONING) |
| `--reasoning-budget N` | token budget for thinking: -1 for unrestricted, 0 for immediate end, N>0 for token budget (default: -1)<br/>(env: LLAMA_ARG_THINK_BUDGET) |
| `--reasoning-budget-message MESSAGE` | message injected before the end-of-thinking tag when reasoning budget is exhausted (default: none)<br/>(env: LLAMA_ARG_THINK_BUDGET_MESSAGE) |
| `--reasoning-preserve, --no-reasoning-preserve` | preserve reasoning trace in the full history, not just the last assistant message (default: template default)<br/>compatible with certain templates having 'supports_preserve_reasoning' capability<br/>example: https://docs.z.ai/guides/capabilities/thinking-mode#preserved-thinking<br/>(env: LLAMA_ARG_REASONING_PRESERVE) |
| `--chat-template JINJA_TEMPLATE` | set custom jinja chat template (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE) |
| `--chat-template-file JINJA_TEMPLATE_FILE` | set custom jinja chat template file (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE_FILE) |
| `--skip-chat-parsing, --no-skip-chat-parsing` | force a pure content parser, even if a Jinja template is specified; model will output everything in the content section, including any reasoning and/or tool calls (default: disabled)<br/>(env: LLAMA_ARG_SKIP_CHAT_PARSING) |
| `--simple-io` | use basic IO for better compatibility in subprocesses and limited consoles |
| `--log-prompts-dir PATH` | Log prompts to directory (only used for debugging, default: disabled) |
| `--log-prompts-dir PATH` | Log prompts to directory (auto-created if not present; only used for debugging, default: disabled) |
| `--spec-draft-hf, -hfd, -hfrd, --hf-repo-draft <user>/<model>[:quant]` | Same as --hf-repo, but for the draft model (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_HF_REPO) |
| `--spec-draft-threads, -td, --threads-draft N` | number of threads to use during generation (default: same as --threads) |
| `--spec-draft-threads-batch, -tbd, --threads-batch-draft N` | number of threads to use during batch and prompt processing (default: same as --threads-draft) |
@@ -198,7 +202,7 @@
| `--spec-draft-device, -devd, --device-draft <dev1,dev2,..>` | comma-separated list of devices to use for offloading the draft model (none = don't offload)<br/>use --list-devices to see a list of available devices |
| `--spec-draft-ngl, -ngld, --gpu-layers-draft, --n-gpu-layers-draft N` | max. number of draft model layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS_DRAFT) |
| `--spec-draft-model, -md, --model-draft FNAME` | draft model for speculative decoding (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_MODEL) |
| `--spec-type none,draft-simple,draft-eagle3,draft-mtp,ngram-simple,ngram-map-k,ngram-map-k4v,ngram-mod,ngram-cache` | comma-separated list of types of speculative decoding to use (default: none)<br/><br/>(env: LLAMA_ARG_SPEC_TYPE) |
| `--spec-type none,draft-simple,draft-eagle3,draft-mtp,draft-dflash,ngram-simple,ngram-map-k,ngram-map-k4v,ngram-mod,ngram-cache` | comma-separated list of types of speculative decoding to use (default: none)<br/><br/>(env: LLAMA_ARG_SPEC_TYPE) |
| `--spec-ngram-mod-n-min N` | minimum number of ngram tokens to use for ngram-based speculative decoding (default: 48) |
| `--spec-ngram-mod-n-max N` | maximum number of ngram tokens to use for ngram-based speculative decoding (default: 64) |
| `--spec-ngram-mod-n-match N` | ngram-mod lookup length (default: 24) |
+2
View File
@@ -137,6 +137,7 @@ llama-completion.exe -m models\gemma-1.1-7b-it.Q4_K_M.gguf --ignore-eos -n -1
| `-ctv, --cache-type-v TYPE` | KV cache data type for V<br/>allowed values: f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1<br/>(default: f16)<br/>(env: LLAMA_ARG_CACHE_TYPE_V) |
| `-dt, --defrag-thold N` | KV cache defragmentation threshold (DEPRECATED)<br/>(env: LLAMA_ARG_DEFRAG_THOLD) |
| `-np, --parallel N` | number of parallel sequences to decode (default: 1)<br/>(env: LLAMA_ARG_N_PARALLEL) |
| `--rpc SERVERS` | comma-separated list of RPC servers (host:port)<br/>(env: LLAMA_ARG_RPC) |
| `--mlock` | force system to keep model in RAM rather than swapping or compressing<br/>(env: LLAMA_ARG_MLOCK) |
| `--mmap, --no-mmap` | whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock) (default: enabled)<br/>(env: LLAMA_ARG_MMAP) |
| `-dio, --direct-io, -ndio, --no-direct-io` | use DirectIO if available. (default: disabled)<br/>(env: LLAMA_ARG_DIO) |
@@ -253,6 +254,7 @@ llama-completion.exe -m models\gemma-1.1-7b-it.Q4_K_M.gguf --ignore-eos -n -1
| `-rea, --reasoning [on\|off\|auto]` | Use reasoning/thinking in the chat ('on', 'off', or 'auto', default: 'auto' (detect from template))<br/>(env: LLAMA_ARG_REASONING) |
| `--reasoning-budget N` | token budget for thinking: -1 for unrestricted, 0 for immediate end, N>0 for token budget (default: -1)<br/>(env: LLAMA_ARG_THINK_BUDGET) |
| `--reasoning-budget-message MESSAGE` | message injected before the end-of-thinking tag when reasoning budget is exhausted (default: none)<br/>(env: LLAMA_ARG_THINK_BUDGET_MESSAGE) |
| `--reasoning-preserve, --no-reasoning-preserve` | preserve reasoning trace in the full history, not just the last assistant message (default: template default)<br/>compatible with certain templates having 'supports_preserve_reasoning' capability<br/>example: https://docs.z.ai/guides/capabilities/thinking-mode#preserved-thinking<br/>(env: LLAMA_ARG_REASONING_PRESERVE) |
| `--chat-template JINJA_TEMPLATE` | set custom jinja chat template (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE) |
| `--chat-template-file JINJA_TEMPLATE_FILE` | set custom jinja chat template file (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE_FILE) |
| `--skip-chat-parsing, --no-skip-chat-parsing` | force a pure content parser, even if a Jinja template is specified; model will output everything in the content section, including any reasoning and/or tool calls (default: disabled)<br/>(env: LLAMA_ARG_SKIP_CHAT_PARSING) |
+10 -4
View File
@@ -71,6 +71,7 @@ For the full list of features, please refer to [server's changelog](https://gith
| `-ctk, --cache-type-k TYPE` | KV cache data type for K<br/>allowed values: f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1<br/>(default: f16)<br/>(env: LLAMA_ARG_CACHE_TYPE_K) |
| `-ctv, --cache-type-v TYPE` | KV cache data type for V<br/>allowed values: f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1<br/>(default: f16)<br/>(env: LLAMA_ARG_CACHE_TYPE_V) |
| `-dt, --defrag-thold N` | KV cache defragmentation threshold (DEPRECATED)<br/>(env: LLAMA_ARG_DEFRAG_THOLD) |
| `--rpc SERVERS` | comma-separated list of RPC servers (host:port)<br/>(env: LLAMA_ARG_RPC) |
| `--mlock` | force system to keep model in RAM rather than swapping or compressing<br/>(env: LLAMA_ARG_MLOCK) |
| `--mmap, --no-mmap` | whether to memory-map model. (if mmap disabled, slower load but may reduce pageouts if not using mlock) (default: enabled)<br/>(env: LLAMA_ARG_MMAP) |
| `-dio, --direct-io, -ndio, --no-direct-io` | use DirectIO if available. (default: disabled)<br/>(env: LLAMA_ARG_DIO) |
@@ -162,7 +163,7 @@ For the full list of features, please refer to [server's changelog](https://gith
| `-lcs, --lookup-cache-static FNAME` | path to static lookup cache to use for lookup decoding (not updated by generation) |
| `-lcd, --lookup-cache-dynamic FNAME` | path to dynamic lookup cache to use for lookup decoding (updated by generation) |
| `-ctxcp, --ctx-checkpoints, --swa-checkpoints N` | max number of context checkpoints to create per slot (default: 32)[(more info)](https://github.com/ggml-org/llama.cpp/pull/15293)<br/>(env: LLAMA_ARG_CTX_CHECKPOINTS) |
| `-cms, --checkpoint-min-step N` | minimum spacing between context checkpoints in tokens (default: 256, 0 = no minimum)<br/>(env: LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT) |
| `-cms, --checkpoint-min-step N` | minimum spacing between context checkpoints in tokens (default: 8192, 0 = no minimum)<br/>(env: LLAMA_ARG_CHECKPOINT_MIN_SPACING_NT) |
| `-cram, --cache-ram N` | set the maximum cache size in MiB (default: 8192, -1 - no limit, 0 - disable)[(more info)](https://github.com/ggml-org/llama.cpp/pull/16391)<br/>(env: LLAMA_ARG_CACHE_RAM) |
| `-kvu, --kv-unified, -no-kvu, --no-kv-unified` | use single unified KV buffer shared across all sequences (default: enabled if number of slots is auto)<br/>(env: LLAMA_ARG_KV_UNIFIED) |
| `--cache-idle-slots, --no-cache-idle-slots` | save idle slots to the prompt cache on new task, and clear them when using unified KV (default: enabled, requires cache-ram)<br/>(env: LLAMA_ARG_CACHE_IDLE_SLOTS) |
@@ -188,12 +189,16 @@ For the full list of features, please refer to [server's changelog](https://gith
| `--port PORT` | port to listen (default: 8080)<br/>(env: LLAMA_ARG_PORT) |
| `--reuse-port` | allow multiple sockets to bind to the same port (default: disabled)<br/>(env: LLAMA_ARG_REUSE_PORT) |
| `--path PATH` | path to serve static files from (default: )<br/>(env: LLAMA_ARG_STATIC_PATH) |
| `--cors-origins ORIGINS` | comma-separated list of allowed origins for CORS (default: *)<br/>if set to special value 'localhost', reflect the Origin header only if it is localhost<br/>(env: LLAMA_ARG_CORS_ORIGINS) |
| `--cors-methods METHODS` | comma-separated list of allowed methods for CORS (default: GET, POST, DELETE, OPTIONS)<br/>(env: LLAMA_ARG_CORS_METHODS) |
| `--cors-headers HEADERS` | comma-separated list of allowed headers for CORS (default: *)<br/>(env: LLAMA_ARG_CORS_HEADERS) |
| `--cors-credentials, --no-cors-credentials` | whether to allow credentials for CORS (default: enabled)<br/>note: if this is enabled and --cors-origins is set to * (default), the Origin header will be echoed back, and credentials will always be allowed<br/>(env: LLAMA_ARG_CORS_CREDENTIALS) |
| `--api-prefix PREFIX` | prefix path the server serves from, without the trailing slash (default: )<br/>(env: LLAMA_ARG_API_PREFIX) |
| `--ui-config, --webui-config JSON` | JSON that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG) |
| `--ui-config-file, --webui-config-file PATH` | JSON file that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG_FILE) |
| `--ui-mcp-proxy, --webui-mcp-proxy, --no-ui-mcp-proxy, --no-webui-mcp-proxy` | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_UI_MCP_PROXY) |
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, apply_diff, get_datetime<br/>(env: LLAMA_ARG_TOOLS) |
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_AGENT) |
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_datetime<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_TOOLS) |
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_AGENT) |
| `--ui, --webui, --no-ui, --no-webui` | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_UI) |
| `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) |
| `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) |
@@ -221,6 +226,7 @@ For the full list of features, please refer to [server's changelog](https://gith
| `-rea, --reasoning [on\|off\|auto]` | Use reasoning/thinking in the chat ('on', 'off', or 'auto', default: 'auto' (detect from template))<br/>(env: LLAMA_ARG_REASONING) |
| `--reasoning-budget N` | token budget for thinking: -1 for unrestricted, 0 for immediate end, N>0 for token budget (default: -1)<br/>(env: LLAMA_ARG_THINK_BUDGET) |
| `--reasoning-budget-message MESSAGE` | message injected before the end-of-thinking tag when reasoning budget is exhausted (default: none)<br/>(env: LLAMA_ARG_THINK_BUDGET_MESSAGE) |
| `--reasoning-preserve, --no-reasoning-preserve` | preserve reasoning trace in the full history, not just the last assistant message (default: template default)<br/>compatible with certain templates having 'supports_preserve_reasoning' capability<br/>example: https://docs.z.ai/guides/capabilities/thinking-mode#preserved-thinking<br/>(env: LLAMA_ARG_REASONING_PRESERVE) |
| `--chat-template JINJA_TEMPLATE` | set custom jinja chat template (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE) |
| `--chat-template-file JINJA_TEMPLATE_FILE` | set custom jinja chat template file (default: template taken from model's metadata)<br/>if suffix/prefix are specified, template will be disabled<br/>only commonly used templates are accepted (unless --jinja is set before this flag):<br/>list of built-in templates:<br/>bailing, bailing-think, bailing2, chatglm3, chatglm4, chatml, command-r, deepseek, deepseek-ocr, deepseek2, deepseek3, exaone-moe, exaone3, exaone4, falcon3, gemma, gigachat, glmedge, gpt-oss, granite, granite-4.0, granite-4.1, grok-2, hunyuan-dense, hunyuan-moe, hunyuan-vl, kimi-k2, llama2, llama2-sys, llama2-sys-bos, llama2-sys-strip, llama3, llama4, megrez, minicpm, mistral-v1, mistral-v3, mistral-v3-tekken, mistral-v7, mistral-v7-tekken, monarch, openchat, orion, pangu-embedded, phi3, phi4, rwkv-world, seed_oss, smolvlm, solar-open, vicuna, vicuna-orca, yandex, zephyr<br/>(env: LLAMA_ARG_CHAT_TEMPLATE_FILE) |
| `--skip-chat-parsing, --no-skip-chat-parsing` | force a pure content parser, even if a Jinja template is specified; model will output everything in the content section, including any reasoning and/or tool calls (default: disabled)<br/>(env: LLAMA_ARG_SKIP_CHAT_PARSING) |
@@ -252,7 +258,7 @@ For the full list of features, please refer to [server's changelog](https://gith
| `--spec-draft-device, -devd, --device-draft <dev1,dev2,..>` | comma-separated list of devices to use for offloading the draft model (none = don't offload)<br/>use --list-devices to see a list of available devices |
| `--spec-draft-ngl, -ngld, --gpu-layers-draft, --n-gpu-layers-draft N` | max. number of draft model layers to store in VRAM, either an exact number, 'auto', or 'all' (default: auto)<br/>(env: LLAMA_ARG_N_GPU_LAYERS_DRAFT) |
| `--spec-draft-model, -md, --model-draft FNAME` | draft model for speculative decoding (default: unused)<br/>(env: LLAMA_ARG_SPEC_DRAFT_MODEL) |
| `--spec-type none,draft-simple,draft-eagle3,draft-mtp,ngram-simple,ngram-map-k,ngram-map-k4v,ngram-mod,ngram-cache` | comma-separated list of types of speculative decoding to use (default: none)<br/><br/>(env: LLAMA_ARG_SPEC_TYPE) |
| `--spec-type none,draft-simple,draft-eagle3,draft-mtp,draft-dflash,ngram-simple,ngram-map-k,ngram-map-k4v,ngram-mod,ngram-cache` | comma-separated list of types of speculative decoding to use (default: none)<br/><br/>(env: LLAMA_ARG_SPEC_TYPE) |
| `--spec-ngram-mod-n-min N` | minimum number of ngram tokens to use for ngram-based speculative decoding (default: 48) |
| `--spec-ngram-mod-n-max N` | maximum number of ngram tokens to use for ngram-based speculative decoding (default: 64) |
| `--spec-ngram-mod-n-match N` | ngram-mod lookup length (default: 24) |
+5
View File
@@ -1152,6 +1152,11 @@ private:
return false;
}
if (ctx_tgt == nullptr) {
SRV_ERR("failed to create_context with model '%s'\n", params_base.model.path.c_str());
return false;
}
vocab = llama_model_get_vocab(model_tgt);
n_ctx = llama_n_ctx(ctx_tgt);
+7
View File
@@ -632,6 +632,13 @@ void server_res_spipe::on_complete() {
if (!spipe || next_finished) {
return;
}
// an empty next_orig means set_next() never ran: the request failed before streaming
// started, typically a params validation throw. evict the session installed by set_req()
// so the failed request leaves nothing behind for discovery or replay
if (!next_orig) {
g_stream_sessions.evict(server_stream_conv_id_from_headers(req->headers));
return;
}
std::string chunk;
while (!spipe->is_cancelled()) {
chunk.clear();
+4 -4
View File
@@ -12,7 +12,7 @@
"@eslint/compat": "1.4.1",
"@eslint/js": "9.39.2",
"@internationalized/date": "3.12.2",
"@lucide/svelte": "0.515.0",
"@lucide/svelte": "1.25.0",
"@modelcontextprotocol/sdk": "1.26.0",
"@playwright/test": "1.56.1",
"@storybook/addon-a11y": "10.2.4",
@@ -3065,9 +3065,9 @@
}
},
"node_modules/@lucide/svelte": {
"version": "0.515.0",
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz",
"integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==",
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.25.0.tgz",
"integrity": "sha512-v9m+dD68jxVnqkU3K59mG/RSRFlPGzmKCGSyMfnXcaGv9jODDQMyQkcp1CGvk3Y/cUj9v7f8rw1n//K0B53xGQ==",
"dev": true,
"license": "ISC",
"peerDependencies": {
+1 -1
View File
@@ -31,7 +31,7 @@
"@eslint/compat": "1.4.1",
"@eslint/js": "9.39.2",
"@internationalized/date": "3.12.2",
"@lucide/svelte": "0.515.0",
"@lucide/svelte": "1.25.0",
"@modelcontextprotocol/sdk": "1.26.0",
"@playwright/test": "1.56.1",
"@storybook/addon-a11y": "10.2.4",
@@ -66,7 +66,14 @@
<Tooltip.Trigger>
<!-- prevent another nested button element -->
{#snippet child({ props })}
{@render button(props)}
{#if disabled}
<!-- disabled buttons have pointer-events:none; wrap in a span so the tooltip hover surface stays alive -->
<span {...props}>
{@render button({})}
</span>
{:else}
{@render button(props)}
{/if}
{/snippet}
</Tooltip.Trigger>
@@ -157,10 +157,7 @@
>
{#if currentConfig.renderUserContentAsMarkdown}
<div bind:this={messageElement} class={isExpanded ? 'cursor-text' : ''}>
<MarkdownContent
class="markdown-system-content -my-4"
content={message.content}
/>
<MarkdownContent class="markdown-system-content" content={message.content} />
</div>
{:else}
<span
@@ -65,7 +65,7 @@
>
{#if renderMarkdown && currentConfig.renderUserContentAsMarkdown}
<div bind:this={messageElement}>
<MarkdownContent class="markdown-user-content -my-4" {content} />
<MarkdownContent class="markdown-user-content" {content} />
</div>
{:else}
<span bind:this={messageElement} class="text-md whitespace-pre-wrap">
@@ -43,6 +43,7 @@
const renderThinkingAsMarkdown = $derived(config().renderThinkingAsMarkdown as boolean);
const showThoughtInProgress = $derived(Boolean(config().showThoughtInProgress));
const alwaysShowToolCallContent = $derived(Boolean(config().alwaysShowToolCallContent));
const showMessageStats = $derived(Boolean(config().showMessageStats));
const showAgenticTurnStats = $derived(showMessageStats && Boolean(config().showAgenticTurnStats));
@@ -98,19 +99,6 @@
isStreaming ? agenticExecutingToolCallId(message.convId) : null
);
// Skip sections the user manually collapsed - we never override an explicit false.
let lastSeenExecutingToolCallId: string | null = null;
$effect(() => {
const current = currentlyExecutingToolCallId;
const previous = lastSeenExecutingToolCallId;
lastSeenExecutingToolCallId = current;
if (!current || current === previous) return;
const idx = sections.findIndex((s) => s.toolCallId === current);
if (idx >= 0 && expandedStates[idx] === undefined) {
expandedStates[idx] = true;
}
});
type TurnGroup = {
sections: AgenticSection[];
flatIndices: number[];
@@ -149,10 +137,11 @@
function getDefaultExpanded(section: AgenticSection): boolean {
if (
section.type === AgenticSectionType.TOOL_CALL ||
section.type === AgenticSectionType.TOOL_CALL_PENDING ||
section.type === AgenticSectionType.TOOL_CALL_STREAMING
) {
return false;
return alwaysShowToolCallContent;
}
if (section.type === AgenticSectionType.REASONING_PENDING) {
@@ -0,0 +1,85 @@
<script lang="ts">
import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Pencil } from '@lucide/svelte';
interface Props {
open: boolean;
currentTitle: string;
value: string;
onConfirm: () => void;
onCancel: () => void;
}
let {
open = $bindable(),
currentTitle,
value = $bindable(''),
onConfirm,
onCancel
}: Props = $props();
let inputRef = $state<HTMLInputElement | null>(null);
const canSubmit = $derived(value.trim().length > 0 && value.trim() !== currentTitle.trim());
$effect(() => {
if (open) {
value = currentTitle;
queueMicrotask(() => {
inputRef?.focus();
inputRef?.select();
});
}
});
function handleOpenChange(newOpen: boolean) {
if (!newOpen) {
onCancel();
}
}
function handleSubmit(event: Event) {
event.preventDefault();
if (!canSubmit) return;
value = value.trim();
onConfirm();
}
</script>
<AlertDialog.Root bind:open onOpenChange={handleOpenChange}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title class="flex items-center gap-2">
<Pencil class="h-5 w-5" />
Rename conversation
</AlertDialog.Title>
<AlertDialog.Description>Choose a new title for this conversation.</AlertDialog.Description>
</AlertDialog.Header>
<form onsubmit={handleSubmit} class="space-y-2 pt-2 pb-4">
<label for="conversation-rename-input" class="text-sm font-medium text-muted-foreground">
Conversation title
</label>
<Input
id="conversation-rename-input"
bind:ref={inputRef}
bind:value
placeholder="Conversation title"
maxlength={200}
autocomplete="off"
autocorrect="off"
spellcheck={false}
/>
</form>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<Button type="button" onclick={handleSubmit} disabled={!canSubmit}>Save</Button>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
@@ -37,9 +37,9 @@
<Dialog.Root bind:open>
<Dialog.Portal>
<Dialog.Overlay class="z-[1000000]" />
<Dialog.Overlay class="z-1000000" />
<Dialog.Content class="z-[1000001] max-w-2xl">
<Dialog.Content class="z-1000001 max-w-2xl">
<Dialog.Header>
<Dialog.Title>
Select Conversations to {mode === 'export' ? 'Export' : 'Import'}
@@ -58,6 +58,7 @@
<ConversationSelection
bind:this={conversationSelectionRef}
isOpen={open}
{conversations}
{messageCountMap}
{mode}
@@ -1,46 +0,0 @@
<script lang="ts">
import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { Button } from '$lib/components/ui/button';
interface Props {
open: boolean;
currentTitle: string;
newTitle: string;
onConfirm: () => void;
onCancel: () => void;
}
let { open = $bindable(), currentTitle, newTitle, onConfirm, onCancel }: Props = $props();
</script>
<AlertDialog.Root bind:open>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Update Conversation Title?</AlertDialog.Title>
<AlertDialog.Description>
Do you want to update the conversation title to match the first message content?
</AlertDialog.Description>
</AlertDialog.Header>
<div class="space-y-4 pt-2 pb-6">
<div class="space-y-2">
<p class="text-sm font-medium text-muted-foreground">Current title:</p>
<p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{currentTitle}</p>
</div>
<div class="space-y-2">
<p class="text-sm font-medium text-muted-foreground">New title would be:</p>
<p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{newTitle}</p>
</div>
</div>
<AlertDialog.Footer>
<Button variant="outline" onclick={onCancel}>Keep Current Title</Button>
<Button onclick={onConfirm}>Update Title</Button>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
@@ -68,6 +68,7 @@
<AlertDialog.Footer>
<AlertDialog.Cancel onclick={onCancel}>Cancel</AlertDialog.Cancel>
<AlertDialog.Action
onclick={onConfirm}
class="bg-destructive text-white hover:bg-destructive/80"
@@ -92,33 +92,36 @@ export { default as DialogExportSettings } from './DialogExportSettings.svelte';
export { default as DialogConfirmation } from './DialogConfirmation.svelte';
/**
* **DialogConversationTitleUpdate** - Conversation rename confirmation
* **DialogConversationRename** - Rename a conversation
*
* Confirmation dialog shown when editing the first user message in a conversation.
* Asks user whether to update the conversation title to match the new message content.
* Modal dialog for renaming a conversation. Replaces the prior
* `window.prompt()`-based flow with a styled, accessible AlertDialog
* containing an editable input. Triggered from the sidebar conversation
* item's "Edit" action.
*
* **Architecture:**
* - Uses ShadCN AlertDialog
* - Shows current vs proposed title comparison
* - Triggered by ChatMessages when first message is edited
* - Bindable `value` keeps the new title in sync with parent state
* - Submit is gated on a non-empty trimmed value that differs from the current title
*
* **Features:**
* - Side-by-side display of current and new title
* - "Keep Current Title" and "Update Title" action buttons
* - Styled title previews in muted background boxes
* - Autofocus on open with text selected for quick overwrite
* - Disabled Save button when value is empty or unchanged
* - Trim-on-submit normalization
* - Cancel via AlertDialog.Cancel or `onOpenChange(false)`
*
* @example
* ```svelte
* <DialogConversationTitleUpdate
* bind:open={showTitleUpdate}
* <DialogConversationRename
* bind:open={showRename}
* currentTitle={conversation.name}
* newTitle={truncatedMessageContent}
* onConfirm={updateTitle}
* onCancel={() => showTitleUpdate = false}
* bind:value={renameDraft}
* onConfirm={handleRenameConfirm}
* onCancel={() => (showRename = false)}
* />
* ```
*/
export { default as DialogConversationTitleUpdate } from './DialogConversationTitleUpdate.svelte';
export { default as DialogConversationRename } from './DialogConversationRename.svelte';
/**
*
@@ -4,6 +4,7 @@
import SearchInput from '$lib/components/app/forms/SearchInput.svelte';
import { ScrollArea } from '$lib/components/ui/scroll-area';
import { SvelteSet } from 'svelte/reactivity';
import { useMarqueeSelection } from '$lib/hooks/use-marquee-selection.svelte';
interface Props {
conversations: DatabaseConversation[];
@@ -11,13 +12,20 @@
mode: 'export' | 'import';
onCancel: () => void;
onConfirm: (selectedConversations: DatabaseConversation[]) => void;
isOpen?: boolean;
}
let { conversations, messageCountMap = new Map(), mode, onCancel, onConfirm }: Props = $props();
let {
conversations,
messageCountMap = new Map(),
mode,
onCancel,
onConfirm,
isOpen = true
}: Props = $props();
let searchQuery = $state('');
let selectedIds = $state.raw<SvelteSet<string>>(getInitialSelectedIds());
let lastClickedId = $state<string | null>(null);
function getInitialSelectedIds(): SvelteSet<string> {
return new SvelteSet(conversations.map((c) => c.id));
@@ -30,6 +38,8 @@
})
);
let orderedIds = $derived(filteredConversations.map((c) => c.id));
let allSelected = $derived(
filteredConversations.length > 0 &&
filteredConversations.every((conv) => selectedIds.has(conv.id))
@@ -39,54 +49,20 @@
filteredConversations.some((conv) => selectedIds.has(conv.id)) && !allSelected
);
function toggleConversation(id: string, shiftKey: boolean = false) {
const newSet = new SvelteSet(selectedIds);
if (shiftKey && lastClickedId !== null) {
const lastIndex = filteredConversations.findIndex((c) => c.id === lastClickedId);
const currentIndex = filteredConversations.findIndex((c) => c.id === id);
if (lastIndex !== -1 && currentIndex !== -1) {
const start = Math.min(lastIndex, currentIndex);
const end = Math.max(lastIndex, currentIndex);
const shouldSelect = !newSet.has(id);
for (let i = start; i <= end; i++) {
if (shouldSelect) {
newSet.add(filteredConversations[i].id);
} else {
newSet.delete(filteredConversations[i].id);
}
}
selectedIds = newSet;
return;
}
}
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
selectedIds = newSet;
lastClickedId = id;
}
const marquee = useMarqueeSelection({
selectedIds: () => selectedIds,
orderedIds: () => orderedIds,
enabled: () => isOpen
});
function toggleAll() {
const newSet = new SvelteSet(selectedIds);
if (allSelected) {
const newSet = new SvelteSet(selectedIds);
filteredConversations.forEach((conv) => newSet.delete(conv.id));
selectedIds = newSet;
} else {
const newSet = new SvelteSet(selectedIds);
filteredConversations.forEach((conv) => newSet.add(conv.id));
selectedIds = newSet;
}
selectedIds = newSet;
}
function handleConfirm() {
@@ -97,7 +73,7 @@
function handleCancel() {
selectedIds = getInitialSelectedIds();
searchQuery = '';
lastClickedId = null;
marquee.reset();
onCancel();
}
@@ -105,7 +81,7 @@
export function reset() {
selectedIds = getInitialSelectedIds();
searchQuery = '';
lastClickedId = null;
marquee.reset();
}
</script>
@@ -122,7 +98,7 @@
</div>
<div class="overflow-hidden rounded-md border">
<ScrollArea class="h-[400px]">
<ScrollArea class="h-100">
<table class="w-full">
<thead class="sticky top-0 z-10 bg-muted">
<tr class="border-b">
@@ -139,6 +115,7 @@
<th class="w-32 p-3 text-left text-sm font-medium">Messages</th>
</tr>
</thead>
<tbody>
{#if filteredConversations.length === 0}
<tr>
@@ -152,23 +129,28 @@
</tr>
{:else}
{#each filteredConversations as conv (conv.id)}
{@const checked = selectedIds.has(conv.id)}
<tr
class="cursor-pointer border-b transition-colors hover:bg-muted/50"
onclick={(event) => toggleConversation(conv.id, event.shiftKey)}
class="cursor-pointer border-b transition-colors hover:bg-muted/50 {checked
? 'bg-muted/75'
: ''}"
data-conversation-row={conv.id}
onmousedown={(event) => marquee.rowMouseDown(conv.id, event)}
onclick={(event) => marquee.rowClick(conv.id, event.shiftKey)}
>
<td class="p-3">
<Checkbox
checked={selectedIds.has(conv.id)}
{checked}
onclick={(event) => {
event.preventDefault();
event.stopPropagation();
toggleConversation(conv.id, event.shiftKey);
marquee.rowClick(conv.id, event.shiftKey);
}}
/>
</td>
<td class="p-3 text-sm">
<div class="max-w-[17rem] truncate" title={conv.name || 'Untitled conversation'}>
<div class="max-w-68 truncate" title={conv.name || 'Untitled conversation'}>
{conv.name || 'Untitled conversation'}
</div>
</td>
@@ -4,15 +4,22 @@
import { PanelLeftClose, PanelLeftOpen, X } from '@lucide/svelte';
import {
ActionIcon,
DialogConversationRename,
Logo,
SidebarNavigationConversationList,
SidebarNavigationActions
} from '$lib/components/app';
import { ROUTES } from '$lib/constants';
import { fade } from 'svelte/transition';
import { SvelteSet } from 'svelte/reactivity';
import { useMarqueeSelection } from '$lib/hooks/use-marquee-selection.svelte';
import { useKeyboardShortcuts } from '$lib/hooks/use-keyboard-shortcuts.svelte';
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
import {
buildConversationTree,
conversationsStore,
conversations
} from '$lib/stores/conversations.svelte';
import { chatStore } from '$lib/stores/chat.svelte';
import { config } from '$lib/stores/settings.svelte';
import { RouterService } from '$lib/services/router.service';
@@ -40,7 +47,6 @@
const isOnMobile = $derived(isMobile.current);
const alwaysShowOnDesktop = $derived(config().alwaysShowSidebarOnDesktop as boolean);
// Keep the sidebar expanded on desktop when the user pins it open
$effect(() => {
if (alwaysShowOnDesktop && !isOnMobile) {
isExpandedMode = true;
@@ -58,13 +64,11 @@
if (!isExpandedMode) {
isSearchModeActive = false;
searchQuery = '';
if (isSelectionMode) exitSelectionMode();
cancelMobileCollapse();
}
});
// On mobile the dedicated /search route hides the sidebar (see the aside
// render guard below). Collapse it as we enter /search so it doesn't
// reappear expanded when the user navigates back via the back button.
$effect(() => {
if (isMobile.current && page.url.hash.includes(ROUTES.SEARCH)) {
isExpandedMode = false;
@@ -89,6 +93,121 @@
return conversations();
});
let isSelectionMode = $state(false);
let selectedIds = new SvelteSet<string>();
let renameDialogOpen = $state(false);
let renameTargetConversationId = $state<string | null>(null);
let renameDraft = $state('');
let renameOriginalTitle = $state('');
const renderedOrderIds = $derived(
buildConversationTree(filteredConversations).map((t) => t.conversation.id)
);
const allSelectedArePinned = $derived.by(() => {
if (selectedIds.size === 0) return false;
const convs = conversations();
for (const id of selectedIds) {
const c = convs.find((conv) => conv.id === id);
if (c && !c.pinned) return false;
}
return true;
});
const pinStateIsMixed = $derived.by(() => {
if (selectedIds.size === 0) return false;
const convs = conversations();
let anyPinned = false;
let anyUnpinned = false;
for (const id of selectedIds) {
const c = convs.find((conv) => conv.id === id);
if (!c) continue;
if (c.pinned) anyPinned = true;
else anyUnpinned = true;
if (anyPinned && anyUnpinned) return true;
}
return false;
});
const visibleSelectionStats = $derived.by(() => {
const visibleIds = filteredConversations.map((c) => c.id);
let selectedVisible = 0;
for (const id of visibleIds) {
if (selectedIds.has(id)) selectedVisible++;
}
return {
visibleCount: visibleIds.length,
selectedVisibleCount: selectedVisible
};
});
function enterSelectionMode(id?: string) {
isSelectionMode = true;
if (id !== undefined) {
selectedIds.add(id);
}
}
function exitSelectionMode() {
isSelectionMode = false;
selectedIds.clear();
}
function toggleSelected(id: string) {
if (selectedIds.has(id)) {
selectedIds.delete(id);
} else {
selectedIds.add(id);
}
}
function toggleSelectAllVisible() {
const visibleIds = filteredConversations.map((c) => c.id);
const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selectedIds.has(id));
if (allSelected) {
for (const id of visibleIds) selectedIds.delete(id);
} else {
for (const id of visibleIds) selectedIds.add(id);
}
}
async function handleBulkDelete() {
const ids = Array.from(selectedIds);
if (ids.length === 0) return;
await conversationsStore.bulkDeleteConversations(ids);
exitSelectionMode();
}
async function handleBulkPinToggle() {
const ids = Array.from(selectedIds);
if (ids.length === 0) return;
await conversationsStore.bulkToggleConversationPin(ids);
}
async function handleBulkExport() {
const ids = Array.from(selectedIds);
if (ids.length === 0) return;
await conversationsStore.bulkExportConversations(ids);
}
const marquee = useMarqueeSelection({
selectedIds: () => selectedIds,
orderedIds: () => renderedOrderIds,
enabled: () => isSelectionMode
});
function handleRowMouseDown(id: string, event: MouseEvent) {
if (!isSelectionMode) return;
marquee.rowMouseDown(id, event);
}
function handleSelectionClick(id: string, options: { shiftKey: boolean }): void {
if (!isSelectionMode) return;
marquee.rowClick(id, options.shiftKey);
}
async function selectConversation(id: string) {
if (isMobile.current) {
scheduleMobileCollapse();
@@ -100,10 +219,30 @@
const conversation = conversations().find((conv) => conv.id === id);
if (!conversation) return;
const newName = window.prompt('Rename conversation', conversation.name);
if (newName && newName.trim()) {
await conversationsStore.updateConversationName(id, newName.trim());
}
renameTargetConversationId = id;
renameOriginalTitle = conversation.name;
renameDraft = conversation.name;
renameDialogOpen = true;
}
async function handleRenameConfirm() {
const id = renameTargetConversationId;
if (!id) return;
const nextName = renameDraft.trim();
if (!nextName || nextName === renameOriginalTitle.trim()) return;
await conversationsStore.updateConversationName(id, nextName);
renameDialogOpen = false;
renameTargetConversationId = null;
}
function handleRenameCancel() {
renameDialogOpen = false;
renameTargetConversationId = null;
renameDraft = '';
renameOriginalTitle = '';
}
async function handleDeleteConversation(id: string) {
@@ -148,9 +287,7 @@
{#if innerWidth > 768 || (!page.url.hash.includes(ROUTES.SETTINGS) && !page.url.hash.includes(ROUTES.MCP_SERVERS) && !page.url.hash.includes(ROUTES.SEARCH))}
<aside
class={[
// Layout & positioning
'fixed md:sticky top-2 left-2 md:left-0 md:ml-2 md:mt-2 pt-2 z-10 w-[calc(100dvw-1rem)]',
// Dimensions & overflow
'md:h-[calc(100dvh-1.125rem)]',
isExpandedMode &&
(device.isStandalone
@@ -158,17 +295,11 @@
: device.isIOSDevice
? 'h-[calc(100dvh-0.5rem)]'
: 'h-[calc(100dvh-1rem)]'),
// Shape & depth
'rounded-3xl md:rounded-2xl',
// Flex layout
'flex flex-col justify-between',
// Transition
'md:transition-[width,padding] duration-200 ease-out',
// Expanded state: width, surface, depth
isStripExpanded && 'md:w-72 md:bg-muted/60 md:backdrop-blur-xl border-border shadow-md',
// Collapsed state
!isStripExpanded && 'md:w-12',
// Expanded mode flag (for mobile ::before overlay)
isExpandedMode && 'is-expanded'
]}
>
@@ -218,52 +349,78 @@
{/if}
</div>
<div class="mt-2 flex min-h-0 flex-1 flex-col gap-4 md:gap-1 overflow-y-auto">
<div
class="flex min-h-0 flex-1 flex-col gap-4 md:gap-1 {isMobile.current
? 'transition-[opacity,height] duration-200 ease-out'
: ''} {isMobile.current && !isExpandedMode ? 'opacity-0 !h-0' : ''}"
in:fade={{ duration: 200 }}
out:fade={{ duration: 200 }}
>
<SidebarNavigationActions
isExpandedMode={innerWidth > 768 ? isExpandedMode : true}
class="px-2"
bind:isSearchModeActive
bind:searchQuery
onSearchDeactivated={() => {
isSearchModeActive = false;
searchQuery = '';
}}
onSearchClick={() => {
isExpandedMode = true;
isSearchModeActive = true;
}}
onNewChat={() => {
if (isMobile.current) {
scheduleMobileCollapse();
}
}}
/>
<div
class="mt-2 flex min-h-0 flex-1 flex-col gap-4 md:gap-1 {isMobile.current
? 'transition-[opacity,height] duration-200 ease-out'
: ''} {isMobile.current && !isExpandedMode ? 'opacity-0 !h-0' : ''}"
in:fade={{ duration: 200 }}
out:fade={{ duration: 200 }}
>
<SidebarNavigationActions
isExpandedMode={innerWidth > 768 ? isExpandedMode : true}
class="px-2"
bind:isSearchModeActive
bind:searchQuery
onSearchDeactivated={() => {
isSearchModeActive = false;
searchQuery = '';
}}
onSearchClick={() => {
isExpandedMode = true;
isSearchModeActive = true;
}}
onNewChat={() => {
if (isMobile.current) {
scheduleMobileCollapse();
}
}}
/>
{#if isExpandedMode || isOnMobile}
{#if isExpandedMode || isOnMobile}
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
<SidebarNavigationConversationList
class="px-2"
{filteredConversations}
{currentChatId}
{isSearchModeActive}
{searchQuery}
{isSelectionMode}
{selectedIds}
onSelect={selectConversation}
onEdit={handleEditConversation}
onDelete={handleDeleteConversation}
onStop={handleStopGeneration}
onToggleSelect={toggleSelected}
onEnterSelectionMode={enterSelectionMode}
onSelectionClick={handleSelectionClick}
onRowMouseDown={handleRowMouseDown}
visibleCount={visibleSelectionStats.visibleCount}
allVisibleSelected={visibleSelectionStats.visibleCount > 0 &&
visibleSelectionStats.selectedVisibleCount === visibleSelectionStats.visibleCount}
someVisibleSelected={visibleSelectionStats.selectedVisibleCount > 0 &&
visibleSelectionStats.selectedVisibleCount < visibleSelectionStats.visibleCount}
{allSelectedArePinned}
{pinStateIsMixed}
onSelectAllToggle={toggleSelectAllVisible}
onBulkPinToggle={handleBulkPinToggle}
onBulkExport={handleBulkExport}
onBulkDelete={handleBulkDelete}
onCloseSelection={exitSelectionMode}
/>
{/if}
</div>
</div>
{/if}
</div>
</aside>
{/if}
<DialogConversationRename
bind:open={renameDialogOpen}
currentTitle={renameOriginalTitle}
bind:value={renameDraft}
onConfirm={handleRenameConfirm}
onCancel={handleRenameCancel}
/>
<style>
aside {
@media (max-width: 768px) {
@@ -119,9 +119,7 @@
: onSearchClick}
{@const itemTransition = {
duration: ICON_STRIP_TRANSITION_DURATION,
delay: !initialized
? ICON_STRIP_TRANSITION_DELAY_MULTIPLIER + i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER
: 0,
delay: !initialized ? i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER : 0,
easing: circIn
}}
@@ -140,10 +138,8 @@
{@render itemIcon(item.icon)}
{#if showIcons}
<span
in:fade={{ duration: 150, easing: circIn, delay: 50 }}
out:fade={{ duration: 100 }}
class="min-w-0 truncate">{item.tooltip}</span
<span in:fade={itemTransition} out:fade={itemTransition} class="min-w-0 truncate"
>{item.tooltip}</span
>
{/if}
</span>
@@ -171,9 +167,7 @@
: onSearchClick}
{@const itemTransition = {
duration: ICON_STRIP_TRANSITION_DURATION,
delay: !initialized
? ICON_STRIP_TRANSITION_DELAY_MULTIPLIER + i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER
: 0,
delay: !initialized ? i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER : 0,
easing: circIn
}}
@@ -9,10 +9,12 @@
Square,
GitBranch,
Pin,
PinOff
PinOff,
ListChecks
} from '@lucide/svelte';
import { DropdownMenuActions } from '$lib/components/app';
import * as Tooltip from '$lib/components/ui/tooltip';
import { Checkbox } from '$lib/components/ui/checkbox';
import { FORK_TREE_DEPTH_PADDING } from '$lib/constants';
import { RouterService } from '$lib/services/router.service';
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
@@ -24,10 +26,16 @@
isActive?: boolean;
depth?: number;
conversation: DatabaseConversation;
isSelectionMode?: boolean;
isSelected?: boolean;
onDelete?: (id: string) => void;
onEdit?: (id: string) => void;
onSelect?: (id: string) => void;
onStop?: (id: string) => void;
onToggleSelect?: (id: string) => void;
onEnterSelectionMode?: (id: string) => void;
onSelectionClick?: (id: string, options: { shiftKey: boolean }) => void;
onRowMouseDown?: (id: string, event: MouseEvent) => void;
}
let {
@@ -36,7 +44,13 @@
onEdit,
onSelect,
onStop,
onToggleSelect,
onEnterSelectionMode,
onSelectionClick,
onRowMouseDown,
isActive = false,
isSelectionMode = false,
isSelected = false,
depth = 0
}: Props = $props();
@@ -64,6 +78,11 @@
conversationsStore.toggleConversationPin(conversation.id);
}
function handleEnterSelectionMode(event: Event) {
event.stopPropagation();
onEnterSelectionMode?.(conversation.id);
}
function handleGlobalEditEvent(event: Event) {
const customEvent = event as CustomEvent<{ conversationId: string }>;
@@ -79,11 +98,40 @@
}
function handleMouseOver() {
if (isSelectionMode) return;
renderActionsDropdown = true;
}
function handleSelect() {
onSelect?.(conversation.id);
function handleSelect(event: MouseEvent) {
if (isSelectionMode) {
onSelectionClick?.(conversation.id, { shiftKey: event.shiftKey });
} else {
onSelect?.(conversation.id);
}
}
function handleCheckboxClick(event: MouseEvent) {
event.stopPropagation();
if (isSelectionMode) {
onSelectionClick?.(conversation.id, { shiftKey: event.shiftKey });
} else {
onToggleSelect?.(conversation.id);
}
}
function handleRowMouseDown(event: MouseEvent) {
onRowMouseDown?.(conversation.id, event);
}
function handleCheckboxKeydown(event: KeyboardEvent) {
if (event.key !== ' ' && event.key !== 'Enter') return;
event.stopPropagation();
event.preventDefault();
if (isSelectionMode) {
onSelectionClick?.(conversation.id, { shiftKey: event.shiftKey });
} else {
onToggleSelect?.(conversation.id);
}
}
$effect(() => {
@@ -108,10 +156,14 @@
<button
class="group flex min-h-9 w-full cursor-pointer items-center justify-between space-x-3 rounded-lg py-1.5 text-left transition-colors hover:bg-foreground/10 {isActive
? 'bg-foreground/5 text-accent-foreground'
: ''} px-3"
onclick={handleSelect}
: ''} {isSelected ? 'bg-primary/10 hover:bg-primary/15' : ''} {isSelectionMode
? 'is-selection-mode'
: ''} px-2"
data-conversation-row={conversation.id}
onclick={(e) => handleSelect(e)}
onmouseover={handleMouseOver}
onmouseleave={handleMouseLeave}
onmousedown={(e) => handleRowMouseDown(e)}
onfocusin={handleMouseOver}
onfocusout={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
@@ -123,6 +175,23 @@
class="flex min-w-0 flex-1 items-center gap-2"
style:padding-left="{depth * FORK_TREE_DEPTH_PADDING}px"
>
{#if isSelectionMode}
<div
class="shrink-0"
onclick={(e) => handleCheckboxClick(e)}
onkeydown={handleCheckboxKeydown}
role="checkbox"
aria-checked={isSelected}
aria-label={isSelected ? `Deselect ${conversation.name}` : `Select ${conversation.name}`}
tabindex="-1"
>
<Checkbox
checked={isSelected}
aria-label={isSelected ? `Deselect ${conversation.name}` : `Select ${conversation.name}`}
/>
</div>
{/if}
{#if depth > 0}
<Tooltip.Root>
<Tooltip.Trigger>
@@ -170,7 +239,7 @@
<TruncatedText text={conversation.name} class="text-sm font-medium" showTooltip={false} />
</div>
{#if renderActionsDropdown}
{#if !isSelectionMode && renderActionsDropdown}
<div class="actions flex items-center">
<DropdownMenuActions
triggerIcon={MoreHorizontal}
@@ -200,6 +269,11 @@
},
shortcut: ['shift', 'cmd', 's']
},
{
icon: ListChecks,
label: 'Select',
onclick: handleEnterSelectionMode
},
{
icon: Trash2,
label: 'Delete',
@@ -230,6 +304,10 @@
}
}
&.is-selection-mode :global([data-slot='dropdown-menu-trigger']) {
display: none !important;
}
.stop-button {
:global(.stop-icon) {
display: none;
@@ -3,6 +3,7 @@
import { buildConversationTree } from '$lib/stores/conversations.svelte';
import SidebarNavigationConversationItem from './SidebarNavigationConversationItem.svelte';
import SidebarNavigationSearchResults from './SidebarNavigationSearchResults.svelte';
import SidebarNavigationSelectionBar from './SidebarNavigationSelectionBar.svelte';
interface Props {
class: string;
@@ -10,10 +11,26 @@
currentChatId: string | undefined;
isSearchModeActive: boolean;
searchQuery: string;
isSelectionMode?: boolean;
selectedIds?: Set<string>;
onSelect: (id: string) => void;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
onStop: (id: string) => void;
onToggleSelect?: (id: string) => void;
onEnterSelectionMode?: (id: string) => void;
onSelectionClick?: (id: string, options: { shiftKey: boolean }) => void;
onRowMouseDown?: (id: string, event: MouseEvent) => void;
visibleCount: number;
allVisibleSelected: boolean;
someVisibleSelected: boolean;
allSelectedArePinned: boolean;
pinStateIsMixed: boolean;
onSelectAllToggle: () => void;
onBulkPinToggle: () => void;
onBulkExport: () => void;
onBulkDelete: () => void;
onCloseSelection: () => void;
}
let {
@@ -22,10 +39,26 @@
currentChatId,
isSearchModeActive,
searchQuery,
isSelectionMode = false,
selectedIds = new Set<string>(),
onSelect,
onEdit,
onDelete,
onStop
onStop,
onToggleSelect,
onEnterSelectionMode,
onSelectionClick,
onRowMouseDown,
visibleCount,
allVisibleSelected,
someVisibleSelected,
allSelectedArePinned,
pinStateIsMixed,
onSelectAllToggle,
onBulkPinToggle,
onBulkExport,
onBulkDelete,
onCloseSelection
}: Props = $props();
let conversationTree = $derived(buildConversationTree(filteredConversations));
@@ -43,65 +76,38 @@
);
</script>
{#if isSearchModeActive}
<SidebarNavigationSearchResults
class={className}
{searchQuery}
{filteredConversations}
{currentChatId}
{onSelect}
{onEdit}
{onDelete}
{onStop}
/>
{:else}
{#if pinnedConversations.length > 0}
<div class="py-2 flex whitespace-nowrap {className}">
<div
class="text-muted-foreground inline-flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium gap-1"
>
<Pin class="h-3.5 w-3.5" />
<div class="flex min-h-0 flex-1 flex-col">
{#if isSearchModeActive}
<SidebarNavigationSearchResults
class={className}
{searchQuery}
{filteredConversations}
{currentChatId}
{onSelect}
{onEdit}
{onDelete}
{onStop}
{isSelectionMode}
{selectedIds}
{onToggleSelect}
{onEnterSelectionMode}
{onSelectionClick}
{onRowMouseDown}
/>
{:else}
{#if pinnedConversations.length > 0}
<div class="py-2 flex whitespace-nowrap {className}">
<div
class="text-muted-foreground inline-flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium gap-1"
>
<Pin class="h-3.5 w-3.5" />
<span>Pinned</span>
<span>Pinned</span>
</div>
</div>
</div>
<ul class="flex w-full min-w-0 flex-col gap-4 md:gap-1 {className}">
{#each pinnedConversations as { conversation, depth } (conversation.id)}
<li class="group/item relative mb-1 p-0">
<SidebarNavigationConversationItem
conversation={{
id: conversation.id,
name: conversation.name,
lastModified: conversation.lastModified,
currNode: conversation.currNode,
forkedFromConversationId: conversation.forkedFromConversationId,
pinned: conversation.pinned
}}
{depth}
isActive={currentChatId === conversation.id}
{onSelect}
{onEdit}
{onDelete}
{onStop}
/>
</li>
{/each}
</ul>
{/if}
<div class="mt-2 flex min-h-0 flex-1 flex-col gap-4 md:gap-2 whitespace-nowrap {className}">
{#if filteredConversations.length > 0}
<div
class="text-muted-foreground flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium"
>
Recent conversations
</div>
{/if}
<div class="min-h-0 flex-1 md:overflow-y-auto">
<ul class="flex w-full min-w-0 flex-col gap-4 md:gap-1">
{#each unpinnedConversations as { conversation, depth } (conversation.id)}
<ul class="flex w-full min-w-0 flex-col gap-4 md:gap-1 {className}">
{#each pinnedConversations as { conversation, depth } (conversation.id)}
<li class="group/item relative mb-1 p-0">
<SidebarNavigationConversationItem
conversation={{
@@ -114,22 +120,86 @@
}}
{depth}
isActive={currentChatId === conversation.id}
{isSelectionMode}
isSelected={selectedIds.has(conversation.id)}
{onSelect}
{onEdit}
{onDelete}
{onStop}
{onToggleSelect}
{onEnterSelectionMode}
{onSelectionClick}
{onRowMouseDown}
/>
</li>
{/each}
{#if unpinnedConversations.length === 0}
<li class="px-2 py-4 text-center">
<p class="mb-4 p-4 text-sm text-muted-foreground">
{recentEmptyMessage}
</p>
</li>
{/if}
</ul>
{/if}
<div class="mt-2 flex min-h-0 flex-1 flex-col gap-4 md:gap-0 whitespace-nowrap {className}">
{#if filteredConversations.length > 0}
<div
class="text-muted-foreground flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium"
>
Recent conversations
</div>
{/if}
<div class="min-h-0 flex-1 md:overflow-y-auto">
<ul class="flex w-full min-w-0 flex-col gap-4 md:gap-0">
{#each unpinnedConversations as { conversation, depth } (conversation.id)}
<li class="group/item relative mb-1 p-0">
<SidebarNavigationConversationItem
conversation={{
id: conversation.id,
name: conversation.name,
lastModified: conversation.lastModified,
currNode: conversation.currNode,
forkedFromConversationId: conversation.forkedFromConversationId,
pinned: conversation.pinned
}}
{depth}
isActive={currentChatId === conversation.id}
{isSelectionMode}
isSelected={selectedIds.has(conversation.id)}
{onSelect}
{onEdit}
{onDelete}
{onStop}
{onToggleSelect}
{onEnterSelectionMode}
{onSelectionClick}
{onRowMouseDown}
/>
</li>
{/each}
{#if unpinnedConversations.length === 0}
<li class="px-2 py-4 text-center">
<p class="mb-4 p-4 text-sm text-muted-foreground">
{recentEmptyMessage}
</p>
</li>
{/if}
</ul>
</div>
</div>
</div>
{/if}
{#if isSelectionMode}
<SidebarNavigationSelectionBar
class="sticky top-0 z-10 m-2 mt-0"
selectedCount={selectedIds.size}
{visibleCount}
{allVisibleSelected}
{someVisibleSelected}
someSelectedPinned={allSelectedArePinned}
{pinStateIsMixed}
{onSelectAllToggle}
{onBulkPinToggle}
{onBulkExport}
{onBulkDelete}
onClose={onCloseSelection}
/>
{/if}
{/if}
</div>
@@ -7,10 +7,16 @@
searchQuery: string;
filteredConversations: DatabaseConversation[];
currentChatId: string | undefined;
isSelectionMode?: boolean;
selectedIds?: Set<string>;
onSelect: (id: string) => void;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
onStop: (id: string) => void;
onToggleSelect?: (id: string) => void;
onEnterSelectionMode?: (id: string) => void;
onSelectionClick?: (id: string, options: { shiftKey: boolean }) => void;
onRowMouseDown?: (id: string, event: MouseEvent) => void;
}
let {
@@ -18,10 +24,16 @@
searchQuery,
filteredConversations,
currentChatId,
isSelectionMode = false,
selectedIds = new Set<string>(),
onSelect,
onEdit,
onDelete,
onStop
onStop,
onToggleSelect,
onEnterSelectionMode,
onSelectionClick,
onRowMouseDown
}: Props = $props();
let tree = $derived(buildConversationTree(filteredConversations));
@@ -56,10 +68,16 @@
}}
{depth}
isActive={currentChatId === conversation.id}
{isSelectionMode}
isSelected={selectedIds.has(conversation.id)}
{onSelect}
{onEdit}
{onDelete}
{onStop}
{onToggleSelect}
{onEnterSelectionMode}
{onSelectionClick}
{onRowMouseDown}
/>
</li>
{/each}
@@ -0,0 +1,163 @@
<script lang="ts">
import { Download, Pin, PinOff, Trash2, X } from '@lucide/svelte';
import { ActionIcon, DialogConfirmation } from '$lib/components/app';
import { Checkbox } from '$lib/components/ui/checkbox';
import { TooltipSide } from '$lib/enums';
interface Props {
class?: string;
selectedCount: number;
visibleCount: number;
allVisibleSelected: boolean;
someVisibleSelected: boolean;
someSelectedPinned: boolean;
pinStateIsMixed: boolean;
onSelectAllToggle: () => void;
onBulkPinToggle: () => void;
onBulkExport: () => void;
onBulkDelete: () => void;
onClose: () => void;
}
let {
class: className = '',
selectedCount,
visibleCount,
allVisibleSelected,
someVisibleSelected,
someSelectedPinned,
pinStateIsMixed,
onSelectAllToggle,
onBulkPinToggle,
onBulkExport,
onBulkDelete,
onClose
}: Props = $props();
let showDeleteDialog = $state(false);
function handleDeleteClick() {
showDeleteDialog = true;
}
function handleDeleteConfirm() {
showDeleteDialog = false;
onBulkDelete();
}
function handleDeleteCancel() {
showDeleteDialog = false;
}
const hasSelection = $derived(selectedCount > 0);
const isMasterChecked = $derived(allVisibleSelected);
const isMasterIndeterminate = $derived(!allVisibleSelected && someVisibleSelected);
const pinTooltip = $derived(
hasSelection
? pinStateIsMixed
? 'Unavailable for mixed state selection'
: someSelectedPinned
? selectedCount === 1
? 'Unpin'
: 'Unpin all'
: selectedCount === 1
? 'Pin'
: 'Pin all'
: 'Pin'
);
const pinDisabled = $derived(!hasSelection || pinStateIsMixed);
</script>
<div
role="toolbar"
aria-label="Bulk actions for selected conversations"
class="flex items-center gap-1.5 rounded-xl border border-border/50 bg-background/50 px-2 py-1.5 shadow-sm backdrop-blur-xl {className}"
>
<label class="flex min-w-0 cursor-pointer items-center gap-2">
<Checkbox
checked={isMasterChecked}
indeterminate={isMasterIndeterminate}
onCheckedChange={onSelectAllToggle}
aria-label={isMasterChecked ? 'Deselect all' : 'Select all'}
/>
<span class="truncate text-xs font-medium text-muted-foreground">
{selectedCount} / {visibleCount} selected
</span>
</label>
<div class="ml-auto flex items-center gap-0.75">
<ActionIcon
icon={someSelectedPinned ? PinOff : Pin}
tooltip={pinTooltip}
tooltipSide={TooltipSide.TOP}
disabled={pinDisabled}
ariaLabel={pinTooltip}
size="sm"
iconSize="h-3.5 w-3.5"
class="h-7 w-7 rounded-md bg-transparent backdrop-blur-none hover:bg-accent! {pinDisabled
? 'cursor-not-allowed'
: ''} {!pinDisabled ? 'opacity-100' : 'opacity-40'}"
onclick={onBulkPinToggle}
/>
<ActionIcon
icon={Download}
tooltip={hasSelection ? 'Export' : 'Export'}
tooltipSide={TooltipSide.TOP}
disabled={!hasSelection}
ariaLabel="Export selected"
size="sm"
iconSize="h-3.5 w-3.5"
class="h-7 w-7 rounded-md bg-transparent backdrop-blur-none hover:bg-accent! {hasSelection
? 'opacity-100'
: 'opacity-40'}"
onclick={onBulkExport}
/>
<ActionIcon
icon={Trash2}
tooltip="Delete selected"
tooltipSide={TooltipSide.TOP}
disabled={!hasSelection}
ariaLabel="Delete selected"
size="sm"
iconSize="h-3.5 w-3.5 text-destructive"
class="h-7 w-7 rounded-md bg-transparent backdrop-blur-none hover:bg-destructive/10! dark:hover:bg-destructive/20! disabled:hover:bg-transparent {hasSelection
? 'opacity-100'
: 'opacity-40'}"
onclick={handleDeleteClick}
/>
<div class="mx-1 h-4 w-px bg-border" aria-hidden="true"></div>
<ActionIcon
icon={X}
tooltip="Exit bulk selection mode"
tooltipSide={TooltipSide.TOP}
ariaLabel="Exit bulk selection mode"
size="sm"
iconSize="h-3.5 w-3.5"
class="h-7 w-7 rounded-md bg-transparent backdrop-blur-none hover:bg-accent!"
onclick={onClose}
/>
</div>
</div>
<DialogConfirmation
bind:open={showDeleteDialog}
title="Delete {selectedCount} conversation{selectedCount === 1 ? '' : 's'}"
description="This action cannot be undone. The selected conversation{selectedCount === 1
? ''
: 's'} and {selectedCount === 1
? 'its'
: 'their'} messages will be permanently removed, including any forks."
confirmText={selectedCount === 1 ? 'Delete' : `Delete ${selectedCount}`}
cancelText="Cancel"
variant="destructive"
icon={Trash2}
onConfirm={handleDeleteConfirm}
onCancel={handleDeleteCancel}
/>
@@ -114,6 +114,36 @@ export { default as SidebarNavigation } from './SidebarNavigation/SidebarNavigat
*/
export { default as SidebarNavigationConversationItem } from './SidebarNavigation/SidebarNavigationConversationItem.svelte';
/**
* **SidebarNavigationSelectionBar** - Bulk action toolbar for selection mode
*
* Rendered above the conversation list when the sidebar enters selection mode.
* Hosts a master checkbox (with select-all / clear-all semantics over the
* currently-visible items), a selected-count caption, and bulk actions for
* pin/unpin, export, and delete. Delete uses
* {@link DialogConfirmation} before invoking the bulk store method.
*
* Pure-presentational; all operations are delegated via callbacks so the
* sidebar owns selection state and persistence.
*
* @example
* ```svelte
* <SidebarNavigationSelectionBar
* selectedCount={selectedIds.size}
* visibleCount={visibleConversations.length}
* allVisibleSelected={...}
* someVisibleSelected={...}
* someSelectedPinned={...}
* onSelectAllToggle={toggleSelectAll}
* onBulkPinToggle={handleBulkPin}
* onBulkExport={handleBulkExport}
* onBulkDelete={handleBulkDelete}
* onClose={exitSelectionMode}
* />
* ```
*/
export { default as SidebarNavigationSelectionBar } from './SidebarNavigation/SidebarNavigationSelectionBar.svelte';
/**
* **SidebarNavigationConversationList** - Grouped conversation list
*
@@ -4,6 +4,7 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import { Input } from '$lib/components/ui/input';
import Label from '$lib/components/ui/label/label.svelte';
import * as RadioGroup from '$lib/components/ui/radio-group';
import * as Select from '$lib/components/ui/select';
import { Textarea } from '$lib/components/ui/textarea';
import { SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants';
@@ -84,10 +85,7 @@
type={field.isPositiveInteger ? 'number' : 'text'}
{...field.isPositiveInteger ? { min: '1', step: '1' } : {}}
value={currentValue}
oninput={(e) => {
// Update local config immediately for real-time badge feedback
onConfigChange(field.key, e.currentTarget.value);
}}
oninput={(e) => onConfigChange(field.key, e.currentTarget.value)}
placeholder={currentModelParams[field.key] != null
? `Default: ${normalizeFloatingPoint(currentModelParams[field.key])}`
: ''}
@@ -236,6 +234,52 @@
{field.help || SETTING_CONFIG_INFO[field.key]}
</p>
{/if}
{:else if field.type === SettingsFieldType.RADIO && field.radioOptions}
{@const radioOptions = field.radioOptions}
{@const currentMode =
radioOptions.find((o: { key: string }) => Boolean(localConfig[o.key]))?.value ??
radioOptions[0].value}
<Label class="flex items-center gap-1.5 text-sm font-medium mb-4">
{field.label}
{#if field.isExperimental}
<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
{/if}
</Label>
<RadioGroup.Root
class="gap-4"
value={currentMode}
onValueChange={(value) => {
for (const opt of radioOptions) {
onConfigChange(opt.key, opt.value === value);
}
}}
>
{#each radioOptions as opt (opt.value)}
{@const itemId = `${field.key}-${opt.value}`}
<div class="flex items-center gap-2">
<RadioGroup.Item value={opt.value} id={itemId} />
<Label
for={itemId}
class="flex cursor-pointer items-center gap-1.5 text-sm font-normal"
>
{opt.label}
{#if opt.isExperimental}
<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
{/if}
</Label>
</div>
{/each}
</RadioGroup.Root>
{#if field.help || SETTING_CONFIG_INFO[field.key]}
<p class="text-xs text-muted-foreground">
{field.help || SETTING_CONFIG_INFO[field.key]}
</p>
{/if}
{:else if field.type === SettingsFieldType.CHECKBOX}
<div class="flex items-start space-x-3">
<Checkbox
@@ -7,6 +7,8 @@
import { toolsStore } from '$lib/stores/tools.svelte';
import { permissionsStore } from '$lib/stores/permissions.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { getBuiltinToolUi } from '$lib/constants/built-in-tools';
import { ToolSource } from '$lib/enums/tools.enums';
import { SvelteSet } from 'svelte/reactivity';
let expandedGroups = new SvelteSet<string>();
@@ -69,12 +71,23 @@
{#each group.tools as entry (entry.key)}
{@const toolName = entry.definition.function.name}
{@const builtinUi =
entry.source === ToolSource.BUILTIN || entry.source === ToolSource.FRONTEND
? getBuiltinToolUi(toolName)
: null}
{@const displayLabel = builtinUi?.label ?? toolName}
{@const IconComponent = builtinUi?.icon ?? null}
{@const isEnabled = toolsStore.isToolEnabled(entry.key)}
{@const permissionKey = entry.key}
{@const isAlwaysAllowed = permissionsStore.hasTool(permissionKey)}
<div class="flex items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50">
<TruncatedText text={toolName} class="flex-1" showTooltip={true} />
<span class="flex min-w-0 flex-1 items-center gap-1.5">
{#if IconComponent}
<IconComponent class={ICON_CLASS_DEFAULT} />
{/if}
<TruncatedText text={displayLabel} class="min-w-0" showTooltip={true} />
</span>
<div class="flex w-16 shrink-0 justify-center">
<Checkbox
@@ -17,7 +17,7 @@
bind:ref
data-slot="checkbox"
class={cn(
'peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
'peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input bg-background shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
className
)}
bind:checked
@@ -0,0 +1,10 @@
import Root from './radio-group.svelte';
import Item from './radio-group-item.svelte';
export {
Root,
Item,
//
Root as RadioGroup,
Item as RadioGroupItem
};
@@ -0,0 +1,31 @@
<script lang="ts">
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
import CircleIcon from '@lucide/svelte/icons/circle';
import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> = $props();
</script>
<RadioGroupPrimitive.Item
bind:ref
data-slot="radio-group-item"
class={cn(
'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 flex size-4 rounded-full focus-visible:ring-3 aria-invalid:ring-3 group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...restProps}
>
{#snippet children({ checked })}
<div data-slot="radio-group-indicator" class="flex size-4 items-center justify-center">
{#if checked}
<CircleIcon
class="bg-primary-foreground absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full"
/>
{/if}
</div>
{/snippet}
</RadioGroupPrimitive.Item>
@@ -0,0 +1,19 @@
<script lang="ts">
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
import { cn } from '$lib/components/ui/utils.js';
let {
ref = $bindable(null),
class: className,
value = $bindable(''),
...restProps
}: RadioGroupPrimitive.RootProps = $props();
</script>
<RadioGroupPrimitive.Root
bind:ref
bind:value
data-slot="radio-group"
class={cn('grid gap-2 w-full', className)}
{...restProps}
/>
+2 -3
View File
@@ -14,7 +14,6 @@ export const SETTINGS_KEYS = {
SEND_ON_ENTER: 'sendOnEnter',
ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration',
PDF_AS_IMAGE: 'pdfAsImage',
ASK_FOR_TITLE_CONFIRMATION: 'askForTitleConfirmation',
TITLE_GENERATION_USE_FIRST_LINE: 'titleGenerationUseFirstLine',
TITLE_GENERATION_USE_LLM: 'titleGenerationUseLLM',
TITLE_GENERATION_PROMPT: 'titleGenerationPrompt',
@@ -60,15 +59,15 @@ export const SETTINGS_KEYS = {
MCP_SERVERS: 'mcpServers',
MCP_REQUEST_TIMEOUT_SECONDS: 'mcpRequestTimeoutSeconds',
AGENTIC_MAX_TURNS: 'agenticMaxTurns',
SHOW_TOOL_CALL_IN_PROGRESS: 'showToolCallInProgress',
ALWAYS_SHOW_TOOL_CALL_CONTENT: 'alwaysShowToolCallContent',
// Performance
PRE_ENCODE_CONVERSATION: 'preEncodeConversation',
// Developer
DISABLE_REASONING_PARSING: 'disableReasoningParsing',
EXCLUDE_REASONING_FROM_CONTEXT: 'excludeReasoningFromContext',
SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch',
// PY_INTERPRETER_ENABLED: 'pyInterpreterEnabled',
JS_SANDBOX_ENABLED: 'jsSandboxEnabled',
// PY_INTERPRETER_ENABLED: 'pyInterpreterEnabled',
CUSTOM_JSON: 'customJson',
CUSTOM_CSS: 'customCss'
} as const;
+78 -63
View File
@@ -54,6 +54,34 @@ const COLOR_MODE_OPTIONS: Array<{ value: string; label: string; icon: Component
{ value: ColorMode.DARK, label: 'Dark', icon: Moon }
];
// Shared options for the title-generation radio group. Both paired registry entries
// (USE_FIRST_LINE, USE_LLM) reference this list so labels stay in lockstep.
const TITLE_GENERATION_RADIO_OPTIONS: Array<{
value: string;
label: string;
key: string;
isExperimental?: boolean;
}> = [
{
value: 'firstLine',
label: 'Use first non-empty line for the conversation title',
key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE
},
{
value: 'llm',
label: 'Generate title with LLM',
key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
isExperimental: true
}
];
// Common shape for the conversation title radio entry.
const TITLE_GENERATION_BASE = {
type: SettingsFieldType.RADIO,
section: SETTINGS_SECTION_SLUGS.GENERAL,
radioOptions: TITLE_GENERATION_RADIO_OPTIONS
} as const;
const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
[SETTINGS_SECTION_SLUGS.GENERAL]: {
title: SETTINGS_SECTION_TITLES.GENERAL,
@@ -115,14 +143,15 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
}
},
{
key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
label: 'Copy text attachments as plain text',
help: 'When copying a message with text attachments, combine them into a single plain text string instead of a special format that can be pasted back as attachments.',
key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
label: 'Show microphone on empty input',
help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
isExperimental: true,
sync: {
serverKey: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
serverKey: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
paramType: SyncableParameterType.BOOLEAN
}
},
@@ -140,54 +169,16 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
}
},
{
key: SETTINGS_KEYS.PDF_AS_IMAGE,
label: 'Parse PDF as image',
help: 'Parse PDF as image instead of text. Automatically falls back to text processing for non-vision models.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
sync: {
serverKey: SETTINGS_KEYS.PDF_AS_IMAGE,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.ASK_FOR_TITLE_CONFIRMATION,
label: 'Ask for confirmation before changing conversation title',
help: 'Ask for confirmation before automatically changing conversation title when editing the first message.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
sync: {
serverKey: SETTINGS_KEYS.ASK_FOR_TITLE_CONFIRMATION,
paramType: SyncableParameterType.BOOLEAN
}
},
{
...TITLE_GENERATION_BASE,
key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE,
label: 'Use first non-empty line for conversation title',
help: 'Use only the first non-empty line of the prompt to generate the conversation title.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
label: 'Conversation title',
help: 'Choose how conversation titles are generated. The first non-empty line uses a fast deterministic rule; the LLM option uses a model-generated title from the first message exchange.',
defaultValue: true,
sync: {
serverKey: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
label: 'Use LLM to generate conversation title',
help: 'Use the LLM to automatically generate conversation titles based on the first message exchange.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
isExperimental: true,
sync: {
serverKey: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.TITLE_GENERATION_PROMPT,
label: 'LLM title generation prompt',
@@ -195,11 +186,36 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
defaultValue: TITLE_GENERATION.DEFAULT_PROMPT,
type: SettingsFieldType.TEXTAREA,
section: SETTINGS_SECTION_SLUGS.GENERAL,
dependsOn: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
sync: {
serverKey: SETTINGS_KEYS.TITLE_GENERATION_PROMPT,
paramType: SyncableParameterType.STRING
}
},
{
key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
label: 'Copy text attachments as plain text',
help: 'When copying a message with text attachments, combine them into a single plain text string instead of a special format that can be pasted back as attachments.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
sync: {
serverKey: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.PDF_AS_IMAGE,
label: 'Parse PDF as image',
help: 'Parse PDF as image instead of text. Automatically falls back to text processing for non-vision models.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.GENERAL,
sync: {
serverKey: SETTINGS_KEYS.PDF_AS_IMAGE,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.MAX_IMAGE_RESOLUTION,
label: 'Maximum image resolution (megapixels)',
@@ -253,27 +269,14 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
}
},
{
key: SETTINGS_KEYS.SHOW_TOOL_CALL_IN_PROGRESS,
label: 'Show tool call in progress',
key: SETTINGS_KEYS.ALWAYS_SHOW_TOOL_CALL_CONTENT,
label: 'Always show tool call content',
help: 'Automatically expand tool call details while executing and keep them expanded after completion.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.DISPLAY,
sync: {
serverKey: SETTINGS_KEYS.SHOW_TOOL_CALL_IN_PROGRESS,
paramType: SyncableParameterType.BOOLEAN
}
},
{
key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
label: 'Show microphone on empty input',
help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
section: SETTINGS_SECTION_SLUGS.DISPLAY,
isExperimental: true,
sync: {
serverKey: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
serverKey: SETTINGS_KEYS.ALWAYS_SHOW_TOOL_CALL_CONTENT,
paramType: SyncableParameterType.BOOLEAN
}
},
@@ -764,6 +767,17 @@ const NON_UI_SETTINGS: SettingsEntry[] = [
defaultValue: '[]',
type: SettingsFieldType.INPUT,
sync: { serverKey: SETTINGS_KEYS.MCP_SERVERS, paramType: SyncableParameterType.STRING }
},
{
key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
label: 'Generate title with LLM',
help: 'Counterpart of the conversation title radio; stored and synced without a dedicated UI field.',
defaultValue: false,
type: SettingsFieldType.CHECKBOX,
sync: {
serverKey: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM,
paramType: SyncableParameterType.BOOLEAN
}
}
// {
// key: SETTINGS_KEYS.PY_INTERPRETER_ENABLED,
@@ -812,7 +826,8 @@ export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [
isPositiveInteger: s.isPositiveInteger,
dependsOn: s.dependsOn,
help: s.help,
options: s.options
options: s.options,
radioOptions: s.radioOptions
}))
})),
...STANDALONE_SECTIONS
+2 -1
View File
@@ -22,5 +22,6 @@ export enum SettingsFieldType {
INPUT = 'input',
TEXTAREA = 'textarea',
CHECKBOX = 'checkbox',
SELECT = 'select'
SELECT = 'select',
RADIO = 'radio'
}
@@ -0,0 +1,239 @@
/**
* Reusable selection state-machine: shift+click/shift+drag range select plus
* rubber-band marquee drag, anchored on the last clicked row. Both the sidebar
* conversation list and the dialog conversations table share this.
*
* The hook mutates the consumer's SvelteSet<string> directly; the consumer
* owns the source of truth and reads it like any other $state. orderedIds
* must reflect the current visual order of selectable rows so the range
* matches what the user sees on screen.
*/
import { SvelteSet } from 'svelte/reactivity';
interface UseMarqueeSelectionOptions {
/** Latest selected-IDs set. Re-read per selection event so consumer-side reassignment works. */
selectedIds: () => SvelteSet<string>;
/** IDs in the current rendered order; used to compute shift+click ranges and gate marquee visibility. */
orderedIds: () => string[];
/** Document listeners attach only while the getter returns true. */
enabled: () => boolean;
/** DOM attribute key (after the `data-` prefix) that marks selectable rows. */
attributeName?: () => string;
/** Minimum pixel distance before a press becomes a marquee drag. */
dragThresholdPx?: number;
}
export function useMarqueeSelection(options: UseMarqueeSelectionOptions) {
const dragThresholdPx = options.dragThresholdPx ?? 5;
let dragAnchorId = $state<string | null>(null);
let isMarqueeDragging = $state(false);
let mouseDownActive = false;
let dragStartX = 0;
let dragStartY = 0;
let mousedownRowId: string | null = null;
let dragMode: 'add' | 'remove' | null = null;
let suppressNextClick = false;
function resolveAttributeName(): string {
return options.attributeName?.() ?? 'conversation-row';
}
/**
* `dataset` keys are camelCased. `data-conversation-row` -> `conversationRow`.
* We resolve the attribute name once per call and read via the camelCase key.
*/
function datasetKey(key: string = resolveAttributeName()): string {
return key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
}
function decideDragMode(startingRowId: string | null, currentlySelected: ReadonlySet<string>) {
return startingRowId !== null && currentlySelected.has(startingRowId) ? 'remove' : 'add';
}
/**
* Range-select uses Finder-style toggle-by-target semantics: if the target
* row is currently selected the range becomes deselected, otherwise it
* becomes selected. Anchor moves to `toId` so chained shift+clicks keep
* extending from the previous endpoint.
*/
function rangeSelect(fromId: string, toId: string) {
const selected = options.selectedIds();
const order = options.orderedIds();
const fromIdx = order.indexOf(fromId);
const toIdx = order.indexOf(toId);
if (fromIdx === -1 || toIdx === -1) return;
const [lo, hi] = fromIdx < toIdx ? [fromIdx, toIdx] : [toIdx, fromIdx];
const shouldSelect = !selected.has(toId);
for (let i = lo; i <= hi; i++) {
const id = order[i];
if (shouldSelect) selected.add(id);
else selected.delete(id);
}
}
function findRowAtPoint(x: number, y: number): string | null {
const attr = resolveAttributeName();
const selector = `[data-${attr}]`;
const key = datasetKey(attr);
let bestMatch: HTMLElement | null = null;
let bestCenterDistance = Infinity;
for (const row of document.querySelectorAll<HTMLElement>(selector)) {
const rect = row.getBoundingClientRect();
if (y >= rect.top && y <= rect.bottom && x >= rect.left && x <= rect.right) {
return row.dataset[key] ?? null;
}
if (x >= rect.left && x <= rect.right) {
const centerDistance = Math.abs(y - (rect.top + rect.height / 2));
if (centerDistance < bestCenterDistance) {
bestCenterDistance = centerDistance;
bestMatch = row;
}
}
}
return bestMatch ? (bestMatch.dataset[key] ?? null) : null;
}
function updateMarqueeRect(currentX: number, currentY: number) {
const attr = resolveAttributeName();
const selector = `[data-${attr}]`;
const key = datasetKey(attr);
const selected = options.selectedIds();
const left = Math.min(dragStartX, currentX);
const top = Math.min(dragStartY, currentY);
const right = Math.max(dragStartX, currentX);
const bottom = Math.max(dragStartY, currentY);
const visibleIds = new SvelteSet(options.orderedIds());
for (const row of document.querySelectorAll<HTMLElement>(selector)) {
const id = row.dataset[key];
if (!id || !visibleIds.has(id)) continue;
const rect = row.getBoundingClientRect();
const intersects = !(
rect.right < left ||
rect.left > right ||
rect.bottom < top ||
rect.top > bottom
);
if (dragMode === 'add') {
if (intersects) selected.add(id);
} else if (dragMode === 'remove') {
if (intersects && selected.has(id)) selected.delete(id);
}
}
}
function handleDocumentMouseMove(event: MouseEvent) {
if (!mouseDownActive) return;
if (event.shiftKey && dragAnchorId !== null) {
const target = findRowAtPoint(event.clientX, event.clientY);
if (target && target !== mousedownRowId) rangeSelect(dragAnchorId, target);
return;
}
if (!isMarqueeDragging) {
const dx = event.clientX - dragStartX;
const dy = event.clientY - dragStartY;
if (Math.hypot(dx, dy) < dragThresholdPx) return;
isMarqueeDragging = true;
dragMode = decideDragMode(mousedownRowId, options.selectedIds());
}
updateMarqueeRect(event.clientX, event.clientY);
}
function handleDocumentMouseUp(event: MouseEvent) {
if (isMarqueeDragging) {
suppressNextClick = true;
const target = findRowAtPoint(event.clientX, event.clientY);
if (target) dragAnchorId = target;
}
isMarqueeDragging = false;
mouseDownActive = false;
mousedownRowId = null;
dragMode = null;
dragStartX = 0;
dragStartY = 0;
}
function handleClickCapture(event: MouseEvent) {
if (suppressNextClick) {
event.stopPropagation();
event.preventDefault();
suppressNextClick = false;
}
}
$effect(() => {
if (!options.enabled()) {
reset();
return;
}
document.addEventListener('mousemove', handleDocumentMouseMove);
document.addEventListener('mouseup', handleDocumentMouseUp);
document.addEventListener('click', handleClickCapture, { capture: true });
return () => {
document.removeEventListener('mousemove', handleDocumentMouseMove);
document.removeEventListener('mouseup', handleDocumentMouseUp);
document.removeEventListener('click', handleClickCapture, { capture: true });
};
});
function rowMouseDown(id: string, event: MouseEvent) {
if (!options.enabled()) return;
if (event.button !== 0) return;
event.preventDefault();
mouseDownActive = true;
mousedownRowId = id;
dragStartX = event.clientX;
dragStartY = event.clientY;
isMarqueeDragging = false;
dragMode = null;
}
function rowClick(id: string, shiftKey: boolean) {
if (!options.enabled()) return;
const selected = options.selectedIds();
if (shiftKey) {
const anchor = dragAnchorId;
if (anchor !== null && anchor !== id) {
rangeSelect(anchor, id);
} else if (selected.has(id)) {
selected.delete(id);
} else {
selected.add(id);
}
dragAnchorId = id;
return;
}
if (selected.has(id)) selected.delete(id);
else selected.add(id);
dragAnchorId = id;
}
function reset() {
dragAnchorId = null;
isMarqueeDragging = false;
mouseDownActive = false;
suppressNextClick = false;
mousedownRowId = null;
dragMode = null;
dragStartX = 0;
dragStartY = 0;
}
return {
rowMouseDown,
rowClick,
reset,
get dragAnchorId() {
return dragAnchorId;
}
};
}
+166 -12
View File
@@ -3,6 +3,7 @@ import { findDescendantMessages, uuid, filterByLeafNodeId } from '$lib/utils';
import { IDXDB_TABLES, IDXDB_STORES, STORAGE_APP_NAME } from '$lib/constants';
import { MessageRole } from '$lib/enums';
import type { McpServerOverride } from '$lib/types/database';
import type { ExportedConversation } from '$lib/types/database';
class LlamaUiDatabase extends Dexie {
[IDXDB_TABLES.conversations]!: EntityTable<DatabaseConversation, string>;
@@ -206,18 +207,7 @@ export class DatabaseService {
await db[IDXDB_TABLES.messages].where('convId').equals(forkId).delete();
}
} else {
// Reparent direct children to deleted conv's parent
const conv = await db[IDXDB_TABLES.conversations].get(id);
const newParent = conv?.forkedFromConversationId;
const directChildren = await db[IDXDB_TABLES.conversations]
.filter((c) => c.forkedFromConversationId === id)
.toArray();
for (const child of directChildren) {
await db[IDXDB_TABLES.conversations].update(child.id, {
forkedFromConversationId: newParent ?? undefined
});
}
await this.reparentDirectChildren(id);
}
await db[IDXDB_TABLES.conversations].delete(id);
@@ -226,6 +216,101 @@ export class DatabaseService {
);
}
/**
* Reparents direct children of `parentId` to the nearest surviving
* ancestor (or promotes them to top-level when the immediate parent was
* top-level). Walking skips any ancestor listed in `excludeIds`, since
* those will be deleted in the same batch leaving a grandchild pointing
* at an `excludeIds` entry would orphan it. Children whose own id is in
* `excludeIds` are dropped from the updates (the bulk-delete pass will
* remove them). `prefetched` may carry a pre-fetched ancestor map to
* avoid repeat reads inside a bulk transaction.
*/
private static async reparentDirectChildren(
parentId: string,
excludeIds: ReadonlySet<string> = new Set(),
prefetched?: ReadonlyMap<string, DatabaseConversation>
): Promise<void> {
const conv = prefetched?.get(parentId) ?? (await db[IDXDB_TABLES.conversations].get(parentId));
if (!conv) return;
let newParent = conv.forkedFromConversationId;
const visited = new Set<string>([parentId]);
while (newParent && excludeIds.has(newParent)) {
if (visited.has(newParent)) {
newParent = undefined;
break;
}
visited.add(newParent);
const next =
prefetched?.get(newParent) ?? (await db[IDXDB_TABLES.conversations].get(newParent));
if (!next) {
newParent = undefined;
break;
}
newParent = next.forkedFromConversationId;
}
const directChildren = await db[IDXDB_TABLES.conversations]
.filter((c) => c.forkedFromConversationId === parentId)
.toArray();
const updates: DatabaseConversation[] = [];
for (const child of directChildren) {
if (excludeIds.has(child.id)) continue;
updates.push({ ...child, forkedFromConversationId: newParent });
}
if (updates.length === 0) return;
await db[IDXDB_TABLES.conversations].bulkPut(updates);
}
/**
* Deletes multiple conversations in a single transaction. Each deleted
* conversation has its direct children reparented to the nearest surviving
* ancestor (or promoted to top-level). Children also in `ids` are dropped
* entirely rather than reparented.
*
* @param ids - Conversation IDs to delete
*/
static async bulkDeleteConversations(ids: string[]): Promise<void> {
const cleanIds = ids.filter((id): id is string => typeof id === 'string' && id.length > 0);
if (cleanIds.length === 0) return;
const idSet = new Set(cleanIds);
await db.transaction(
'rw',
[db[IDXDB_TABLES.conversations], db[IDXDB_TABLES.messages]],
async () => {
// Pre-load each to-delete conversation so the per-id reparent
// walk-up doesn't ping-pong the same ancestry chain.
const prefetched = new Map<string, DatabaseConversation>();
let frontier = [...cleanIds];
const requested = new Set<string>(frontier);
while (frontier.length > 0) {
const fetched = await db[IDXDB_TABLES.conversations].bulkGet(frontier);
frontier = [];
for (let i = 0; i < fetched.length; i++) {
const conv = fetched[i];
if (!conv || !conv.id) continue;
prefetched.set(conv.id, conv);
const ancestor = conv.forkedFromConversationId;
if (ancestor && !prefetched.has(ancestor) && !requested.has(ancestor)) {
frontier.push(ancestor);
requested.add(ancestor);
}
}
}
for (const id of cleanIds) {
await this.reparentDirectChildren(id, idSet, prefetched);
}
await db[IDXDB_TABLES.conversations].bulkDelete(cleanIds);
await db[IDXDB_TABLES.messages].where('convId').anyOf(cleanIds).delete();
}
);
}
/**
* Deletes a message and removes it from its parent's children array.
*
@@ -319,6 +404,43 @@ export class DatabaseService {
return await db[IDXDB_TABLES.messages].where('convId').equals(convId).sortBy('timestamp');
}
/**
* Loads multiple conversations with all of their messages in two bulk
* reads. Missing conversations are silently omitted from the result.
*
* @param convIds - Conversation IDs to load
* @returns Map of id -> { conv, messages }. Messages are sorted ascending by timestamp.
*/
static async getConversationsWithMessages(
convIds: string[]
): Promise<Map<string, ExportedConversation>> {
const result = new Map<string, ExportedConversation>();
const cleanIds = convIds.filter((id): id is string => typeof id === 'string' && id.length > 0);
if (cleanIds.length === 0) return result;
const [convs, allMessages] = await Promise.all([
db[IDXDB_TABLES.conversations].bulkGet(cleanIds),
db[IDXDB_TABLES.messages].where('convId').anyOf(cleanIds).toArray()
]);
const messagesByConv = new Map<string, DatabaseMessage[]>();
for (const msg of allMessages) {
const bucket = messagesByConv.get(msg.convId);
if (bucket) bucket.push(msg);
else messagesByConv.set(msg.convId, [msg]);
}
for (let i = 0; i < cleanIds.length; i++) {
const conv = convs[i];
if (!conv) continue;
const messages = (messagesByConv.get(conv.id) ?? []).sort(
(a, b) => a.timestamp - b.timestamp
);
result.set(conv.id, { conv, messages });
}
return result;
}
/**
* Updates a conversation.
*
@@ -360,6 +482,38 @@ export class DatabaseService {
return newPinnedState;
}
/**
* Toggles the pinned status of each conversation in `ids` inside a single
* transaction. Treats `pinned === undefined` as `false`, matching the
* semantics of {@link toggleConversationPin} where `!undefined` evaluates
* to `true`. Returns the resulting pinned state for every id that was
* updated; missing ids are omitted from the map.
*
* @param ids - Conversation IDs to toggle
* @returns Map of id -> new pinned state
*/
static async bulkToggleConversationPins(ids: string[]): Promise<Map<string, boolean>> {
const cleanIds = ids.filter((id): id is string => typeof id === 'string' && id.length > 0);
const result = new Map<string, boolean>();
if (cleanIds.length === 0) return result;
const now = Date.now();
await db.transaction('rw', db[IDXDB_TABLES.conversations], async () => {
const convs = await db[IDXDB_TABLES.conversations].bulkGet(cleanIds);
const updates: DatabaseConversation[] = [];
for (let i = 0; i < cleanIds.length; i++) {
const conv = convs[i];
if (!conv) continue;
const newPinned = !conv.pinned;
updates.push({ ...conv, pinned: newPinned, lastModified: now });
result.set(cleanIds[i], newPinned);
}
if (updates.length === 0) return;
await db[IDXDB_TABLES.conversations].bulkPut(updates);
});
return result;
}
/**
* Updates the conversation's current node (active branch).
* This determines which conversation path is currently being viewed.
@@ -288,6 +288,7 @@ class AgenticStore {
const hasTools =
mcpStore.hasEnabledServers(perChatOverrides) ||
toolsStore.builtinTools.length > 0 ||
toolsStore.frontendTools.length > 0 ||
toolsStore.customTools.length > 0;
return {
enabled: hasTools && DEFAULT_AGENTIC_CONFIG.enabled,
+5 -4
View File
@@ -1596,7 +1596,7 @@ class ChatStore {
conversationsStore.updateMessageAtIndex(messageIndex, { content: newContent });
await DatabaseService.updateMessage(messageId, { content: newContent });
if (isFirstUserMessage && newContent.trim())
await conversationsStore.updateConversationTitleWithConfirmation(
await conversationsStore.updateConversationName(
activeConv.id,
generateConversationTitle(newContent, Boolean(config().titleGenerationUseFirstLine))
);
@@ -2113,7 +2113,7 @@ class ChatStore {
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) {
await conversationsStore.updateConversationTitleWithConfirmation(
await conversationsStore.updateConversationName(
activeConv.id,
generateConversationTitle(newContent, Boolean(config().titleGenerationUseFirstLine))
);
@@ -2187,7 +2187,7 @@ class ChatStore {
conversationsStore.updateConversationTimestamp();
if (isFirstUserMessage && newContent.trim())
await conversationsStore.updateConversationTitleWithConfirmation(
await conversationsStore.updateConversationName(
activeConv.id,
generateConversationTitle(newContent, Boolean(config().titleGenerationUseFirstLine))
);
@@ -2428,7 +2428,8 @@ class ChatStore {
if (currentConfig.samplers) apiOptions.samplers = currentConfig.samplers;
apiOptions.backend_sampling = currentConfig.backend_sampling;
if (hasValue(currentConfig.backend_sampling))
apiOptions.backend_sampling = currentConfig.backend_sampling;
if (currentConfig.customJson) apiOptions.custom = currentConfig.customJson;
+122 -45
View File
@@ -108,9 +108,6 @@ class ConversationsStore {
localStorage.setItem(REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY, this.pendingReasoningEffort);
}
/** Callback for title update confirmation dialog */
titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise<boolean>;
/**
* Callback for updating message content in chatStore.
* Registered by chatStore to enable cross-store updates without circular dependency.
@@ -209,15 +206,6 @@ class ConversationsStore {
return undefined;
}
/**
* Sets the callback function for title update confirmations
*/
setTitleUpdateConfirmationCallback(
callback: (currentTitle: string, newTitle: string) => Promise<boolean>
): void {
this.titleUpdateConfirmationCallback = callback;
}
/**
*
*
@@ -386,6 +374,123 @@ class ConversationsStore {
}
}
/**
* Deletes multiple conversations in sequence.
* Mirrors deleteConversation() per-id; navigates to NEW_CHAT only if the
* currently-open chat was among the deleted ones.
* @param convIds - Conversation IDs to delete
*/
async bulkDeleteConversations(convIds: string[]): Promise<void> {
if (convIds.length === 0) return;
try {
const idsToRemove = new SvelteSet(convIds);
// Collect all descendants recursively so the local cache stays consistent
// even when deleteWithForks is omitted.
const queue = [...convIds];
while (queue.length > 0) {
const parentId = queue.pop()!;
for (const c of this.conversations) {
if (c.forkedFromConversationId === parentId && !idsToRemove.has(c.id)) {
idsToRemove.add(c.id);
queue.push(c.id);
}
}
}
const activeWasDeleted =
this.activeConversation !== null && idsToRemove.has(this.activeConversation.id);
await DatabaseService.bulkDeleteConversations([...idsToRemove]);
this.conversations = this.conversations.filter((c) => !idsToRemove.has(c.id));
if (activeWasDeleted) {
this.clearActiveConversation();
await goto(ROUTES.NEW_CHAT);
}
toast.success(
convIds.length === 1 ? 'Conversation deleted' : `${convIds.length} conversations deleted`
);
} catch (error) {
console.error('Failed to bulk delete conversations:', error);
toast.error('Failed to delete conversations');
}
}
/**
* Toggles the pinned state of each conversation individually.
* Mixed-pin selections are intentionally not normalised here; the bulk
* action UI surfaces them as a disabled mixed-state instead.
* @param convIds - Conversation IDs to toggle
*/
async bulkToggleConversationPin(convIds: string[]): Promise<void> {
if (convIds.length === 0) return;
try {
const updates = await DatabaseService.bulkToggleConversationPins(convIds);
const activeId = this.activeConversation?.id;
if (activeId && updates.has(activeId)) {
this.activeConversation = {
...this.activeConversation!,
pinned: updates.get(activeId)!
};
}
for (let i = 0; i < this.conversations.length; i++) {
const newPinned = updates.get(this.conversations[i].id);
if (newPinned !== undefined) this.conversations[i].pinned = newPinned;
}
this.conversations = [...this.conversations];
toast.success(
convIds.length === 1
? 'Conversation pin toggled'
: `Updated pin state for ${convIds.length} conversations`
);
} catch (error) {
console.error('Failed to bulk toggle pin:', error);
toast.error('Failed to update pin state');
}
}
/**
* Bundles the given conversations into a single zip archive and triggers a
* browser download (one JSONL file per conversation).
* @param convIds - Conversation IDs to export
*/
async bulkExportConversations(convIds: string[]): Promise<void> {
if (convIds.length === 0) return;
try {
const fetched = await DatabaseService.getConversationsWithMessages(convIds);
const activeId = this.activeConversation?.id;
const overridden = fetched.get(activeId ?? '');
if (overridden && activeId) {
overridden.conv = { ...this.activeConversation! };
}
const exported = [...fetched.values()];
if (exported.length === 0) {
toast.error('No conversations to export');
return;
}
this.downloadConversationsArchive(exported);
toast.success(
exported.length === 1
? 'Conversation exported'
: `${exported.length} conversations exported`
);
} catch (error) {
console.error('Failed to bulk export conversations:', error);
toast.error('Failed to export conversations');
}
}
/**
*
*
@@ -484,38 +589,6 @@ class ConversationsStore {
}
}
/**
* Updates conversation title with optional confirmation dialog based on settings
* @param convId - The conversation ID to update
* @param newTitle - The new title content
* @returns True if title was updated, false if cancelled
*/
async updateConversationTitleWithConfirmation(
convId: string,
newTitle: string
): Promise<boolean> {
try {
const currentConfig = config();
if (currentConfig.askForTitleConfirmation && this.titleUpdateConfirmationCallback) {
const conversation = await DatabaseService.getConversation(convId);
if (!conversation) return false;
const shouldUpdate = await this.titleUpdateConfirmationCallback(
conversation.name,
newTitle
);
if (!shouldUpdate) return false;
}
await this.updateConversationName(convId, newTitle);
return true;
} catch (error) {
console.error('Failed to update conversation title with confirmation:', error);
return false;
}
}
/**
* Updates conversation lastModified timestamp and moves it to top of list
*/
@@ -581,7 +654,7 @@ class ConversationsStore {
newFirstUserMessage.id !== currentFirstUserMessage.id ||
newFirstUserMessage.content.trim() !== currentFirstUserMessage.content.trim())
) {
await this.updateConversationTitleWithConfirmation(
await this.updateConversationName(
this.activeConversation.id,
generateConversationTitle(
newFirstUserMessage.content,
@@ -1162,6 +1235,10 @@ export const isConversationsInitialized = () => conversationsStore.isInitialized
/**
* Builds a flat tree of conversations with depth levels for nested forks.
* Accepts a pre-filtered list so search filtering stays in the component.
*
* Output order matches the sidebar render exactly: pinned first, then
* unpinned by lastModified desc, with forks interleaved under their parents.
* Range-select / marquee in the sidebar rely on this alignment.
*/
// Pinned conversations first, then by lastModified descending
+4
View File
@@ -27,6 +27,8 @@ export interface SettingsEntry {
type: SettingsFieldType;
section?: string;
options?: Array<{ value: string; label: string; icon: Component }>;
/** Options rendered for RADIO fields. Each entry maps a `value` (the radio's selected value) to the underlying config `key` whose boolean state mirrors it. */
radioOptions?: Array<{ value: string; label: string; key: string; isExperimental?: boolean }>;
isExperimental?: boolean;
isPositiveInteger?: boolean;
dependsOn?: string;
@@ -53,6 +55,8 @@ export interface SettingsFieldConfig {
dependsOn?: string;
help?: string;
options?: Array<{ value: string; label: string; icon?: typeof Icon }>;
/** Options rendered for RADIO fields. Each entry maps a `value` (the radio's selected value) to the underlying config `key` whose boolean state mirrors it. */
radioOptions?: Array<{ value: string; label: string; key: string; isExperimental?: boolean }>;
}
/** Re-exported for backward compatibility. */
+1 -47
View File
@@ -7,12 +7,11 @@
import { untrack } from 'svelte';
import { onMount } from 'svelte';
import { SidebarNavigation, DialogConversationTitleUpdate } from '$lib/components/app';
import { SidebarNavigation } from '$lib/components/app';
import { PwaMetaTags, PwaRefreshAlert } from '$lib/components/pwa';
import { pwaAssetsHead } from 'virtual:pwa-assets/head';
import { chatStore } from '$lib/stores/chat.svelte';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
import { config, settingsStore } from '$lib/stores/settings.svelte';
@@ -46,11 +45,6 @@
let showBuildVersion = $derived(config()[SETTINGS_KEYS.SHOW_BUILD_VERSION] as boolean);
let titleUpdateDialogOpen = $state(false);
let titleUpdateCurrentTitle = $state('');
let titleUpdateNewTitle = $state('');
let titleUpdateResolve: ((value: boolean) => void) | null = null;
// Keep the hook object intact: destructuring needRefreshByStorage reads the getter once and freezes it
const pwa = usePwa();
const { needRefresh, updateServiceWorker } = pwa;
@@ -135,24 +129,6 @@
});
}
function handleTitleUpdateCancel() {
titleUpdateDialogOpen = false;
if (titleUpdateResolve) {
titleUpdateResolve(false);
titleUpdateResolve = null;
}
}
function handleTitleUpdateConfirm() {
titleUpdateDialogOpen = false;
if (titleUpdateResolve) {
titleUpdateResolve(true);
titleUpdateResolve = null;
}
}
onMount(() => {
updateFavicon();
// snapshot of every backend running stream on first load, populates the sidebar spinners
@@ -264,20 +240,6 @@
$effect(() => {
checkApiKey();
});
// Set up title update confirmation callback
$effect(() => {
conversationsStore.setTitleUpdateConfirmationCallback(
async (currentTitle: string, newTitle: string) => {
return new Promise<boolean>((resolve) => {
titleUpdateCurrentTitle = currentTitle;
titleUpdateNewTitle = newTitle;
titleUpdateResolve = resolve;
titleUpdateDialogOpen = true;
});
}
);
});
</script>
<svelte:head>
@@ -319,14 +281,6 @@
<ModeWatcher />
<Toaster richColors />
<DialogConversationTitleUpdate
bind:open={titleUpdateDialogOpen}
currentTitle={titleUpdateCurrentTitle}
newTitle={titleUpdateNewTitle}
onConfirm={handleTitleUpdateConfirm}
onCancel={handleTitleUpdateCancel}
/>
</Tooltip.Provider>
<!-- PWA update prompt + version -->