Compare commits

..

10 Commits

Author SHA1 Message Date
Georgi Gerganov 04a134c70b ci : make release workflows use a deply key 2026-08-17 09:58:11 +03:00
Georgi Gerganov 4197155add sync : ggml 2026-08-17 09:50:06 +03:00
Georgi Gerganov cea66f4c5a ggml : bump version to 0.20.1 (ggml/1587) 2026-08-17 09:50:06 +03:00
Fathi Boudra 4695f001fe llama-bench: fix deprecation warnings missing trailing newline (#27179)
The log output does not append a newline, so the warning ran into the
next line printed on stdout, corrupting the benchmark table header.

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
2026-08-17 07:33:24 +03:00
Titaniumtown f275595dd1 sycl: fix thread/block count in quantized cpy kernel launches (#27160)
Adjusts the thread/block count to be proportional to the size
of the quant, reducing under/over subscription.

Largest perf improvement is the q4_0 -> f32 path, with, on
a Arc 70, throughput goes from 20.21 GB/s to 158.19 GB/s

The rest of the quants are flat in performance uplift.
2026-08-17 07:32:16 +03:00
Neo Zhang 37a215c9e9 [SYCL] support OP OPT_STEP_ADAMW, OPT_STEP_SGD (#25268)
* fix conflict

* fix conflict of ops.md

* fix conflict of ops.md

* update the ops.md

---------

Co-authored-by: Neo Zhang Jianyu <jianyu.zhang@intel.com>
2026-08-17 07:31:29 +03:00
Daniel Bevenius 4df29be4f4 ci : fix dry-run reporting in make-release job [no ci] (#27167)
This commit fixes the reporting in the make-release CI job when
--dry-run is used. It will currently incorrectly report that all checks
pass even if there are steps that fail.

Refs: https://github.com/ggml-org/llama.cpp/pull/26839#issuecomment-5306189828
2026-08-16 14:53:13 +02:00
fairydreaming 3cb7ffb1a1 model : remove some ggml_concat (#27176)
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-08-16 14:12:55 +02:00
Xuan-Son Nguyen b94041a98e chat: refactor handling supports_string_content / supports_typed_content (#27130)
* better supports_string_content cap detect

* test: add "skip"

* messages_inp_normalizer
2026-08-16 12:45:33 +02:00
Oğuzhan Akkaya 10bf611e53 llama : check LoRA tensor data is within file bounds (#27056)
* llama : check LoRA tensor data is within file bounds

* Update src/llama-adapter.cpp

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

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
2026-08-16 09:38:01 +03:00
27 changed files with 1096 additions and 21537 deletions
+9 -2
View File
@@ -22,6 +22,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ssh-key: ${{ secrets.DEPLOY_KEY_RELEASE }}
- name: Run release checks
id: checks
@@ -42,5 +44,10 @@ jobs:
- name: Dry run summary
if: ${{ github.event.inputs.dry_run == 'true' }}
run: |
echo "Dry run complete - all checks passed."
echo "Would have created tag: ${{ steps.checks.outputs.version }}"
if [[ "${{ steps.checks.outputs.checks_passed }}" == "true" ]]; then
echo "Dry run complete - all checks passed."
echo "Would have created tag: ${{ steps.checks.outputs.version }}"
else
echo "::error::Dry run found release check failures. A release tag would not be created."
exit 1
fi
+1
View File
@@ -1598,6 +1598,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY_RELEASE }}
- name: Determine tag name
id: tag
+72 -27
View File
@@ -470,36 +470,80 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
return msgs;
}
struct messages_inp_normalizer {
const jinja::caps & caps;
messages_inp_normalizer(const jinja::caps & c) : caps(c) {}
// handle supports_string_content / supports_typed_content
// if string=true and array=false, convert array to string
// if string=false and array=true, convert string to array
// if both are true, do nothing
json normalize(const json & messages) {
bool only_string = caps.supports_string_content && !caps.supports_typed_content;
bool only_typed = !caps.supports_string_content && caps.supports_typed_content;
if ((!only_string && !only_typed) || !messages.is_array()) {
return messages;
}
json normalized = json::array();
for (const auto & msg : messages) {
json copy = msg;
auto it = copy.find("content");
if (it != copy.end()) {
if (only_typed && it->is_string()) {
*it = json::array({
json{
{"type", "text"},
{"text", it->get<std::string>()},
}
});
} else if (only_string && it->is_array()) {
*it = concat_content_parts(*it);
}
}
normalized.push_back(std::move(copy));
}
return normalized;
}
// join parts with newline, do not add newline before or after media markers
static std::string concat_content_parts(const json & parts) {
std::string text;
bool last_was_media_marker = false;
for (const auto & part : parts) {
std::string type = part.value("type", "");
bool add_new_line = true;
if (type == "text") {
add_new_line = !last_was_media_marker && !text.empty();
last_was_media_marker = false;
} else if (type == "media_marker") {
add_new_line = false;
last_was_media_marker = true;
} else {
LOG_WRN("Ignoring content part type: %s\n", type.c_str());
continue;
}
if (add_new_line) {
text += '\n';
}
text += part.value("text", "");
}
return text;
}
};
static json render_message_to_json(const std::vector<common_chat_msg> & msgs, const jinja::caps & c) {
if (!c.supports_string_content && !c.supports_typed_content) {
LOG_WRN("%s: Neither string content nor typed content is supported by the template. This is unexpected and may lead to issues.\n", __func__);
}
bool only_string_accepted = c.supports_string_content && !c.supports_typed_content;
bool only_typed_accepted = !c.supports_string_content && c.supports_typed_content;
json messages = json::array();
for (const auto & msg : msgs) {
if (only_string_accepted) {
json jmsg = msg.to_json_oaicompat(/* concat_typed_text= */ true);
messages.push_back(jmsg);
} else if (only_typed_accepted) {
json jmsg = msg.to_json_oaicompat(/* concat_typed_text= */ false);
if (jmsg.at("content").is_string()) {
jmsg["content"] = json::array({
json{
{"type", "text"},
{"text", jmsg.at("content").get<std::string>()},
}
});
}
messages.push_back(jmsg);
} else {
json jmsg = msg.to_json_oaicompat(/* concat_typed_text= */ false);
messages.push_back(jmsg);
}
messages.push_back(msg.to_json_oaicompat(/* concat_typed_text= */ false));
}
return messages;
return messages_inp_normalizer(c).normalize(messages);
}
// DEPRECATED: only used in tests
@@ -892,8 +936,11 @@ static std::string common_chat_template_direct_apply_impl(
const std::optional<json> & additional_context = std::nullopt) {
jinja::context ctx(tmpl.source());
// messages_override is already built for this template, do not touch its content parts
nlohmann::ordered_json inp = nlohmann::ordered_json{
{"messages", messages_override.has_value() ? *messages_override : inputs.messages},
{"messages", messages_override.has_value()
? *messages_override
: messages_inp_normalizer(tmpl.original_caps()).normalize(inputs.messages)},
{"bos_token", tmpl.bos_token()},
{"eos_token", tmpl.eos_token()},
{"enable_thinking", inputs.enable_thinking},
@@ -957,14 +1004,12 @@ static std::string common_chat_template_generation_prompt_impl(
const std::optional<json> & tools_override = std::nullopt,
const std::optional<json> & additional_context = std::nullopt) {
auto adjusted_messages = messages_override ? *messages_override : inputs.messages;
autoparser::generation_params params = inputs;
params.add_generation_prompt = false;
params.continue_final_message = COMMON_CHAT_CONTINUATION_NONE;
std::string no_gen_prompt = common_chat_template_direct_apply_impl(tmpl, params, adjusted_messages, tools_override, additional_context);
std::string no_gen_prompt = common_chat_template_direct_apply_impl(tmpl, params, messages_override, tools_override, additional_context);
params.add_generation_prompt = true;
std::string gen_prompt = common_chat_template_direct_apply_impl(tmpl, params, adjusted_messages, tools_override, additional_context);
std::string gen_prompt = common_chat_template_direct_apply_impl(tmpl, params, messages_override, tools_override, additional_context);
size_t prefix_len = 0;
size_t min_size = std::min(no_gen_prompt.size(), gen_prompt.size());
+10 -4
View File
@@ -23,7 +23,7 @@ void caps_apply_preserve_reasoning(jinja::context & ctx, bool enabled) {
ctx.set_val("preserve_thinking", mk_val<value_bool>(enabled));
ctx.set_val("clear_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("truncate_history_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("drop_thinking", mk_val<value_bool>(!enabled));
ctx.set_val("drop_thinking", mk_val<value_bool>(!enabled));
}
void caps_apply_reasoning_effort(jinja::context & ctx, const std::string & effort) {
@@ -117,6 +117,8 @@ caps caps_get(jinja::program & prog) {
JJ_DEBUG("%s\n", ">>> Running capability check: typed content");
static const std::string content_marker = "STRING_MARKER";
// case: typed content support
caps_try_execute(
prog,
@@ -125,22 +127,26 @@ caps caps_get(jinja::program & prog) {
return json::array({
{
{"role", "user"},
{"content", "content"}
{"content", content_marker}
}
});
},
nullptr, // ctx_fn
nullptr, // tools_fn
[&](context &, bool success, value & messages, value &, const std::string &) {
[&](context &, bool success, value & messages, value &, const std::string & rendered) {
auto & content = messages->at(0)->at("content");
caps_print_stats(content, "messages[0].content");
if (has_op(content, "selectattr") || has_op(content, "array_access")) {
bool used_as_array = has_op(content, "selectattr") || has_op(content, "array_access");
if (used_as_array) {
// accessed as an array
result.supports_typed_content = true;
}
if (!success) {
// failed to execute with content as string
result.supports_string_content = false;
} else if (used_as_array && rendered.find(content_marker) == std::string::npos) {
// edge case: string may be accessed for checking, but does not appear in the output
result.supports_string_content = false;
}
}
);
+3 -3
View File
@@ -77,8 +77,8 @@ Legend:
| MUL_MAT_ID | ❌ | 🟡 | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | 🟡 | 🟡 | ❌ |
| NEG | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| NORM | ❌ | ✅ | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | ✅ | 🟡 | ❌ | ❌ |
| OPT_STEP_ADAMW | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | | ✅ | ❌ | ❌ | ❌ |
| OPT_STEP_SGD | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | | ✅ | ❌ | ❌ | ❌ |
| OPT_STEP_ADAMW | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | | ✅ | ❌ | ❌ | ❌ |
| OPT_STEP_SGD | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | | ✅ | ❌ | ❌ | ❌ |
| OUT_PROD | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ | ❌ | 🟡 | 🟡 | ❌ | ❌ | 🟡 |
| PAD | ❌ | 🟡 | ✅ | 🟡 | ❌ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | ❌ | ❌ |
| PAD_REFLECT_1D | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
@@ -98,7 +98,7 @@ Legend:
| RWKV_WKV7 | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| SCALE | ❌ | 🟡 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SET | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ |
| SET_ROWS | ❌ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
| SET_ROWS | ❌ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | | 🟡 | 🟡 | ❌ | ❌ |
| SGN | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SIGMOID | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
| SILU | ❌ | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | ✅ | ✅ | ✅ | ❌ | ❌ |
+640 -20006
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,6 +4,6 @@
# Copyright (C) 2026 Intel Corporation
# SPDX-License-Identifier: MIT
./build/bin/test-backend-ops support --output csv > docs/ops/SYCL.csv
./build/bin/test-backend-ops -b SYCL0 support --output csv > docs/ops/SYCL.csv
./scripts/create_ops_docs.py
+1 -1
View File
@@ -5,7 +5,7 @@ project("ggml" C CXX ASM)
### GGML Version
set(GGML_VERSION_MAJOR 0)
set(GGML_VERSION_MINOR 20)
set(GGML_VERSION_PATCH 0)
set(GGML_VERSION_PATCH 1)
set(GGML_VERSION_BASE "${GGML_VERSION_MAJOR}.${GGML_VERSION_MINOR}.${GGML_VERSION_PATCH}")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
+102 -56
View File
@@ -349,8 +349,9 @@ static void ggml_cpy_f32_q8_0_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK8_0 == 0);
const int num_blocks = ne / QK8_0;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK8_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f32_q8_0, QK8_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -361,8 +362,10 @@ static void ggml_cpy_q8_0_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
GGML_ASSERT(ne % QK8_0 == 0);
const int num_blocks = ceil_div(ne / QK8_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_f32<cpy_blck_q8_0_f32, QK8_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -373,9 +376,11 @@ static void ggml_cpy_q2_0_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK2_0 == 0);
const int num_blocks = ceil_div(ne / QK2_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
cpy_q_f32<cpy_blck_q2_0_f32, QK2_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03, ne10, ne11,
ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -387,8 +392,9 @@ static void ggml_cpy_f32_q4_0_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK4_0 == 0);
const int num_blocks = ne / QK4_0;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK4_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f32_q4_0, QK4_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -399,9 +405,11 @@ static void ggml_cpy_q4_0_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK4_0 == 0);
const int num_blocks = ceil_div(ne / QK4_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_f32<cpy_blck_q_f32<dequantize_q4_0, QK4_0>, QK4_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02,
nb03, ne10, ne11, ne12, nb10, nb11, nb12, nb13,
@@ -414,8 +422,9 @@ static void ggml_cpy_f32_q4_1_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK4_1 == 0);
const int num_blocks = ne / QK4_1;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK4_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f32_q4_1, QK4_1>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -426,9 +435,11 @@ static void ggml_cpy_q4_1_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK4_1 == 0);
const int num_blocks = ceil_div(ne / QK4_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_f32<cpy_blck_q_f32<dequantize_q4_1, QK4_1>, QK4_1>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02,
nb03, ne10, ne11, ne12, nb10, nb11, nb12, nb13,
@@ -441,8 +452,9 @@ static void ggml_cpy_f32_q5_0_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK5_0 == 0);
const int num_blocks = ne / QK5_0;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK5_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
cpy_f32_q<cpy_blck_f32_q5_0, QK5_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -453,9 +465,11 @@ static void ggml_cpy_q5_0_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK5_0 == 0);
const int num_blocks = ceil_div(ne / QK5_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_f32<cpy_blck_q_f32<dequantize_q5_0, QK5_0>, QK5_0>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02,
nb03, ne10, ne11, ne12, nb10, nb11, nb12, nb13,
@@ -468,8 +482,9 @@ static void ggml_cpy_f32_q5_1_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK5_1 == 0);
const int num_blocks = ne / QK5_1;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK5_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f32_q5_1, QK5_1>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03,
ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -480,9 +495,11 @@ static void ggml_cpy_q5_1_f32_sycl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK5_1 == 0);
const int num_blocks = ceil_div(ne / QK5_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_f32<cpy_blck_q_f32<dequantize_q5_1, QK5_1>, QK5_1>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02,
nb03, ne10, ne11, ne12, nb10, nb11, nb12, nb13,
@@ -494,9 +511,11 @@ static void ggml_cpy_mxfp4_f32_sycl(const char * cx, char * cdst, const int ne,
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ne;
GGML_ASSERT(ne % QK_MXFP4 == 0);
const int num_blocks = ceil_div(ne / QK_MXFP4, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
cpy_q_f32<cpy_blck_q_f32<dequantize_mxfp4, QK_MXFP4>, QK_MXFP4>(cx, cdst, ne, ne00, ne01, ne02, nb00,
nb01, nb02, nb03, ne10, ne11, ne12,
@@ -509,9 +528,10 @@ static void ggml_cpy_f32_iq4_nl_sycl(const char * cx, char * cdst, const int ne,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK4_NL == 0);
const int num_blocks = ne / QK4_NL;
const int num_blocks = ceil_div(ne / QK4_NL, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
cpy_f32_q<cpy_blck_f32_iq4_nl, QK4_NL>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03, ne10, ne11,
ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -556,8 +576,9 @@ static void ggml_cpy_f16_q4_0_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK4_0 == 0);
const int num_blocks = ne / QK4_0;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK4_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f16_q4_0, QK4_0>(cx, cdst, ne, ne00, ne01, ne02,
nb00, nb01, nb02, nb03,
@@ -570,8 +591,9 @@ static void ggml_cpy_f16_q4_1_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK4_1 == 0);
const int num_blocks = ne / QK4_1;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK4_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f16_q4_1, QK4_1>(cx, cdst, ne, ne00, ne01, ne02,
nb00, nb01, nb02, nb03,
@@ -584,8 +606,9 @@ static void ggml_cpy_f16_q5_0_sycl(const char * cx, char * cdst, const int ne, c
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
GGML_ASSERT(ne % QK5_0 == 0);
const int num_blocks = ne / QK5_0;
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks), sycl::range<3>(1, 1, 1)),
const int num_blocks = ceil_div(ne / QK5_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_f32_q<cpy_blck_f16_q5_0, QK5_0>(cx, cdst, ne, ne00, ne01, ne02,
nb00, nb01, nb02, nb03,
@@ -849,7 +872,8 @@ static void ggml_cpy_q8_0_q8_0(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK8_0 == 0);
const int num_blocks = ceil_div(ne / QK8_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
@@ -863,7 +887,8 @@ static void ggml_cpy_q5_0_q5_0(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK5_0 == 0);
const int num_blocks = ceil_div(ne / QK5_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
@@ -877,7 +902,8 @@ static void ggml_cpy_q5_1_q5_1(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK5_1 == 0);
const int num_blocks = ceil_div(ne / QK5_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE),
@@ -892,7 +918,8 @@ static void ggml_cpy_q4_0_q4_0(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK4_0 == 0);
const int num_blocks = ceil_div(ne / QK4_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -906,8 +933,9 @@ static void ggml_cpy_q4_1_q4_1(const char * cx, char * cdst, const int ne, const
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
GGML_ASSERT(ne % QK4_1 == 0);
const int num_blocks = ceil_div(ne / QK4_1, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
cpy_q_q<block_q4_1, QK4_1>(cx, cdst, ne, ne00, ne01, ne02, nb00, nb01, nb02, nb03, ne10, ne11, ne12, nb10, nb11, nb12, nb13, item_ct1);
@@ -918,7 +946,8 @@ static void ggml_cpy_q1_0_q1_0(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK1_0 == 0);
const int num_blocks = ceil_div(ne / QK1_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
@@ -930,7 +959,8 @@ static void ggml_cpy_q2_0_q2_0(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK2_0 == 0);
const int num_blocks = ceil_div(ne / QK2_0, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -942,7 +972,8 @@ static void ggml_cpy_mxfp4_mxfp4(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_MXFP4 == 0);
const int num_blocks = ceil_div(ne / QK_MXFP4, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]] {
@@ -954,7 +985,8 @@ static void ggml_cpy_nvfp4_nvfp4(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_NVFP4 == 0);
const int num_blocks = ceil_div(ne / QK_NVFP4, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -966,7 +998,8 @@ static void ggml_cpy_q2_K_q2_K(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -978,7 +1011,8 @@ static void ggml_cpy_q3_K_q3_K(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -990,7 +1024,8 @@ static void ggml_cpy_q4_K_q4_K(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1002,7 +1037,8 @@ static void ggml_cpy_q5_K_q5_K(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1014,7 +1050,8 @@ static void ggml_cpy_q6_K_q6_K(const char * cx, char * cdst, const int ne, const
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1026,7 +1063,8 @@ static void ggml_cpy_iq2_xxs_iq2_xxs(const char * cx, char * cdst, const int ne,
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1038,7 +1076,8 @@ static void ggml_cpy_iq2_xs_iq2_xs(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1050,7 +1089,8 @@ static void ggml_cpy_iq2_s_iq2_s(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1062,7 +1102,8 @@ static void ggml_cpy_iq3_xxs_iq3_xxs(const char * cx, char * cdst, const int ne,
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1074,7 +1115,8 @@ static void ggml_cpy_iq1_s_iq1_s(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1086,7 +1128,8 @@ static void ggml_cpy_iq1_m_iq1_m(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1098,7 +1141,8 @@ static void ggml_cpy_iq4_nl_iq4_nl(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK4_NL == 0);
const int num_blocks = ceil_div(ne / QK4_NL, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1110,7 +1154,8 @@ static void ggml_cpy_iq3_s_iq3_s(const char * cx, char * cdst, const int ne, con
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
@@ -1122,7 +1167,8 @@ static void ggml_cpy_iq4_xs_iq4_xs(const char * cx, char * cdst, const int ne, c
const int ne02, const int nb00, const int nb01, const int nb02, const int nb03,
const int ne10, const int ne11, const int ne12, const int nb10, const int nb11,
const int nb12, const int nb13, queue_ptr stream) {
const int num_blocks = ceil_div(ne, SYCL_CPY_BLOCK_SIZE);
GGML_ASSERT(ne % QK_K == 0);
const int num_blocks = ceil_div(ne / QK_K, SYCL_CPY_BLOCK_SIZE);
stream->parallel_for(
sycl::nd_range<3>(sycl::range<3>(1, 1, num_blocks) * sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE), sycl::range<3>(1, 1, SYCL_CPY_BLOCK_SIZE)),
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
+9
View File
@@ -77,6 +77,7 @@
#include "ggml-sycl/fill.hpp"
#include "ggml-sycl/cumsum.hpp"
#include "ggml-sycl/diag.hpp"
#include "ggml-sycl/opt-step.hpp"
#include "ggml-sycl/solve_tri.hpp"
#include "ggml-sycl/gated_delta_net.hpp"
#include "ggml-sycl/pool.hpp"
@@ -5355,6 +5356,12 @@ static bool ggml_sycl_compute_forward(ggml_backend_sycl_context & ctx, struct gg
case GGML_OP_GATED_DELTA_NET:
ggml_sycl_gated_delta_net(ctx, dst);
break;
case GGML_OP_OPT_STEP_ADAMW:
ggml_sycl_opt_step_adamw(ctx, dst);
break;
case GGML_OP_OPT_STEP_SGD:
ggml_sycl_opt_step_sgd(ctx, dst);
break;
case GGML_OP_SSM_CONV:
ggml_sycl_ssm_conv(ctx, dst);
break;
@@ -6263,6 +6270,8 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
case GGML_OP_RWKV_WKV7:
case GGML_OP_GATED_LINEAR_ATTN:
case GGML_OP_GATED_DELTA_NET:
case GGML_OP_OPT_STEP_ADAMW:
case GGML_OP_OPT_STEP_SGD:
return true;
case GGML_OP_SSM_CONV:
return op->type == GGML_TYPE_F32 &&
+131
View File
@@ -0,0 +1,131 @@
#include "opt-step.hpp"
#define SYCL_OPT_STEP_BLOCK_SIZE 256
template <typename T>
static void opt_step_adamw_f32_kernel(
T * __restrict__ x,
const T * __restrict__ g,
T * __restrict__ g_m,
T * __restrict__ g_v,
const T * __restrict__ pars,
const int64_t k,
const sycl::nd_item<1> & item) {
const int64_t i = (int64_t) item.get_global_id(0);
if (i >= k) {
return;
}
const float alpha = pars[0];
const float beta1 = pars[1];
const float beta2 = pars[2];
const float eps = pars[3];
const float wd = pars[4];
const float beta1h = pars[5];
const float beta2h = pars[6];
const float gi = g[i];
const float gmi = g_m[i] * beta1 + gi * (1.0f - beta1);
const float gvi = g_v[i] * beta2 + gi * gi * (1.0f - beta2);
g_m[i] = gmi;
g_v[i] = gvi;
const float mh = gmi * beta1h;
const float vh = sycl::sqrt(gvi * beta2h) + eps;
x[i] = x[i] * (1.0f - alpha * wd) - alpha * mh / vh;
}
template <typename T>
static void opt_step_sgd_f32_kernel(
T * __restrict__ x,
const T * __restrict__ g,
const T * __restrict__ pars,
const int64_t k,
const sycl::nd_item<1> & item) {
const int64_t i = (int64_t) item.get_global_id(0);
if (i >= k) {
return;
}
x[i] = x[i] * (1.0f - pars[0] * pars[1]) - pars[0] * g[i];
}
void ggml_sycl_opt_step_adamw(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
scope_op_debug_print scope_dbg_print(__func__, dst, /*num_src=*/5);
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src0_grad = dst->src[1];
const ggml_tensor * src0_grad_m = dst->src[2];
const ggml_tensor * src0_grad_v = dst->src[3];
const ggml_tensor * adamw_params = dst->src[4];
GGML_ASSERT(src0->type == GGML_TYPE_F32);
GGML_ASSERT(src0_grad->type == GGML_TYPE_F32);
GGML_ASSERT(src0_grad_m->type == GGML_TYPE_F32);
GGML_ASSERT(src0_grad_v->type == GGML_TYPE_F32);
GGML_ASSERT(adamw_params->type == GGML_TYPE_F32);
GGML_ASSERT(ggml_is_contiguous(src0));
GGML_ASSERT(ggml_is_contiguous(src0_grad));
GGML_ASSERT(ggml_is_contiguous(src0_grad_m));
GGML_ASSERT(ggml_is_contiguous(src0_grad_v));
GGML_ASSERT(ggml_is_contiguous(adamw_params));
GGML_ASSERT(ggml_are_same_shape(src0, src0_grad));
GGML_ASSERT(ggml_are_same_shape(src0, src0_grad_m));
GGML_ASSERT(ggml_are_same_shape(src0, src0_grad_v));
GGML_ASSERT(ggml_nelements(adamw_params) == 7);
dpct::queue_ptr stream = ctx.stream();
SYCL_CHECK(ggml_sycl_set_device(ctx.device));
float * src0_d = (float *) src0->data;
const float * src0_grad_d = (const float *) src0_grad->data;
float * src0_grad_m_d = (float *) src0_grad_m->data;
float * src0_grad_v_d = (float *) src0_grad_v->data;
const float * adamw_params_d = (const float *) adamw_params->data;
const int64_t ne = ggml_nelements(src0);
const int64_t num_blocks = (ne + SYCL_OPT_STEP_BLOCK_SIZE - 1) / SYCL_OPT_STEP_BLOCK_SIZE;
stream->parallel_for(
sycl::nd_range<1>(num_blocks * SYCL_OPT_STEP_BLOCK_SIZE, SYCL_OPT_STEP_BLOCK_SIZE),
[=](sycl::nd_item<1> item) {
opt_step_adamw_f32_kernel(src0_d, src0_grad_d, src0_grad_m_d, src0_grad_v_d, adamw_params_d, ne, item);
});
}
void ggml_sycl_opt_step_sgd(ggml_backend_sycl_context & ctx, ggml_tensor * dst) {
scope_op_debug_print scope_dbg_print(__func__, dst, /*num_src=*/3);
const ggml_tensor * src0 = dst->src[0];
const ggml_tensor * src0_grad = dst->src[1];
const ggml_tensor * sgd_params = dst->src[2];
GGML_ASSERT(src0->type == GGML_TYPE_F32);
GGML_ASSERT(src0_grad->type == GGML_TYPE_F32);
GGML_ASSERT(sgd_params->type == GGML_TYPE_F32);
GGML_ASSERT(ggml_is_contiguous(src0));
GGML_ASSERT(ggml_is_contiguous(src0_grad));
GGML_ASSERT(ggml_is_contiguous(sgd_params));
GGML_ASSERT(ggml_are_same_shape(src0, src0_grad));
GGML_ASSERT(ggml_nelements(sgd_params) == 2);
dpct::queue_ptr stream = ctx.stream();
SYCL_CHECK(ggml_sycl_set_device(ctx.device));
float * src0_d = (float *) src0->data;
const float * src0_grad_d = (const float *) src0_grad->data;
const float * sgd_params_d = (const float *) sgd_params->data;
const int64_t ne = ggml_nelements(src0);
const int64_t num_blocks = (ne + SYCL_OPT_STEP_BLOCK_SIZE - 1) / SYCL_OPT_STEP_BLOCK_SIZE;
stream->parallel_for(
sycl::nd_range<1>(num_blocks * SYCL_OPT_STEP_BLOCK_SIZE, SYCL_OPT_STEP_BLOCK_SIZE),
[=](sycl::nd_item<1> item) {
opt_step_sgd_f32_kernel(src0_d, src0_grad_d, sgd_params_d, ne, item);
});
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "common.hpp"
void ggml_sycl_opt_step_adamw(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
void ggml_sycl_opt_step_sgd(ggml_backend_sycl_context & ctx, ggml_tensor * dst);
+7
View File
@@ -11,6 +11,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
DRY_RUN=false
CHECKS_PASSED=true
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
@@ -44,6 +45,7 @@ else
if [[ "$RUNS" -eq 0 ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
echo "Warning: no successful release.yml run found for HEAD (${SHA}) (dry run, continuing)."
CHECKS_PASSED=false
else
echo "Error: no successful release.yml run found for HEAD (${SHA})"
echo "The nightly build must complete successfully before making a release."
@@ -73,6 +75,7 @@ else
echo "$DIFF"
if [[ "$DRY_RUN" == "true" ]]; then
echo "Warning: would abort release due to ggml mismatch (dry run, continuing)."
CHECKS_PASSED=false
else
echo "Error: ggml must match upstream before making a release."
exit 1
@@ -81,3 +84,7 @@ else
echo "local ggml/ matches upstream ${GGML_VERSION}"
fi
fi
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
echo "checks_passed=${CHECKS_PASSED}" >> "$GITHUB_OUTPUT"
fi
+1 -1
View File
@@ -1 +1 @@
2d191b5dee1a591c41ee8a653ce42bfcd9c8716d
3834fd814e74e8af277939dabd69ecc780affd21
+5 -2
View File
@@ -396,8 +396,11 @@ static void llama_adapter_lora_init_impl(llama_model & model, const char * path_
llama_file gguf_file(path_lora, "rb");
std::vector<uint8_t> read_buf;
auto set_tensor = [&](ggml_tensor * orig, ggml_tensor * dev) {
size_t offs = gguf_get_data_offset(ctx_gguf.get()) + gguf_get_tensor_offset(ctx_gguf.get(), gguf_find_tensor(ctx_gguf.get(), orig->name));
size_t size = ggml_nbytes(orig);
const size_t offs = gguf_get_data_offset(ctx_gguf.get()) + gguf_get_tensor_offset(ctx_gguf.get(), gguf_find_tensor(ctx_gguf.get(), orig->name));
const size_t size = ggml_nbytes(orig);
if (offs + size < offs || offs + size > gguf_file.size()) {
throw std::runtime_error(format("LoRA tensor '%s' data is not within the file bounds, file is corrupted or incomplete", orig->name));
}
read_buf.resize(size);
gguf_file.seek(offs, SEEK_SET);
gguf_file.read_raw(read_buf.data(), size);
+9 -42
View File
@@ -180,10 +180,11 @@ llama_model_deepseek32::graph::graph(const llama_model & model, const llm_graph_
const int64_t n_indexer_head = hparams.indexer_n_head;
const int64_t n_embd_indexer_head = hparams.indexer_head_size;
const int64_t n_embd_indexer_head_rope = hparams.n_rot();
const int64_t n_embd_indexer_head_nope = n_embd_indexer_head - n_embd_indexer_head_rope;
const uint32_t n_indexer_top_k = hparams.indexer_top_k;
// the indexer head layous is [rope | nope]
GGML_ASSERT(hparams.n_rot() <= n_embd_indexer_head);
const uint32_t kv_lora_rank = hparams.n_lora_kv;
// We have to pre-scale kq_scale and attn_factor to make the YaRN RoPE work correctly.
@@ -233,28 +234,11 @@ llama_model_deepseek32::graph::graph(const llama_model & model, const llm_graph_
ggml_tensor * indexer_q = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_q_b, qr);
cb(indexer_q, "indexer_q", il);
// split into {n_embd_indexer_head_rope, n_indexer_head, n_tokens}
ggml_tensor * indexer_q_pe =
ggml_view_3d(ctx0, indexer_q, n_embd_indexer_head_rope, n_indexer_head, n_tokens,
ggml_row_size(indexer_q->type, n_embd_indexer_head),
ggml_row_size(indexer_q->type, n_embd_indexer_head) * n_indexer_head, 0);
cb(indexer_q_pe, "indexer_q_pe", il);
// and {n_embd_indexer_head_nope, n_indexer_head, n_tokens}
ggml_tensor * indexer_q_nope =
ggml_view_3d(ctx0, indexer_q, n_embd_indexer_head_nope, n_indexer_head, n_tokens,
ggml_row_size(indexer_q->type, n_embd_indexer_head),
ggml_row_size(indexer_q->type, n_embd_indexer_head) * n_indexer_head,
ggml_row_size(indexer_q->type, n_embd_indexer_head_nope));
cb(indexer_q_nope, "indexer_q_nope", il);
indexer_q_pe = ggml_rope_ext(ctx0, indexer_q_pe, inp_pos, nullptr, n_rot,
// {n_embd_indexer_head, n_indexer_head, n_tokens}
indexer_q = ggml_reshape_3d(ctx0, indexer_q, n_embd_indexer_head, n_indexer_head, n_tokens);
indexer_q = ggml_rope_ext(ctx0, indexer_q, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NEOX, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_q_pe, "indexer_q_pe", il);
// {n_embd_indexer_head_rope + n_embd_indexer_head_nope, n_head, n_tokens}
indexer_q = ggml_concat(ctx0, indexer_q_pe, indexer_q_nope, 0);
cb(indexer_q, "indexer_q", il);
ggml_tensor * indexer_k = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_k, cur);
@@ -263,28 +247,11 @@ llama_model_deepseek32::graph::graph(const llama_model & model, const llm_graph_
indexer_k = build_norm(indexer_k, model.layers[il].indexer_k_norm, model.layers[il].indexer_k_norm_b, LLM_NORM, il);
cb(indexer_k, "indexer_k", il);
// split into {n_embd_indexer_head_rope, 1, n_tokens}
ggml_tensor * indexer_k_pe =
ggml_view_3d(ctx0, indexer_k, n_embd_indexer_head_rope, 1, n_tokens,
ggml_row_size(indexer_k->type, n_embd_indexer_head),
ggml_row_size(indexer_k->type, n_embd_indexer_head) * 1, 0);
cb(indexer_k_pe, "indexer_k_pe", il);
// and {n_embd_indexer_head_nope, 1, n_tokens}
ggml_tensor * indexer_k_nope =
ggml_view_3d(ctx0, indexer_k, n_embd_indexer_head_nope, 1, n_tokens,
ggml_row_size(indexer_k->type, n_embd_indexer_head),
ggml_row_size(indexer_k->type, n_embd_indexer_head) * 1,
ggml_row_size(indexer_k->type, n_embd_indexer_head_nope));
cb(indexer_k_nope, "indexer_k_nope", il);
indexer_k_pe = ggml_rope_ext(ctx0, indexer_k_pe, inp_pos, nullptr, n_rot,
// {n_embd_indexer_head, 1, n_tokens}
indexer_k = ggml_reshape_3d(ctx0, indexer_k, n_embd_indexer_head, 1, n_tokens);
indexer_k = ggml_rope_ext(ctx0, indexer_k, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NEOX, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_k_pe, "indexer_k_pe", il);
// {n_embd_indexer_head_rope + n_embd_indexer_head_nope, 1, n_tokens}
indexer_k = ggml_concat(ctx0, indexer_k_pe, indexer_k_nope, 0);
cb(indexer_k, "indexer_k", il);
// perform Hadamard transform on indexer q and k
+9 -42
View File
@@ -216,10 +216,11 @@ llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_par
const int64_t n_indexer_head = hparams.indexer_n_head;
const int64_t n_embd_indexer_head = hparams.indexer_head_size;
const int64_t n_embd_indexer_head_rope = hparams.n_rot();
const int64_t n_embd_indexer_head_nope = n_embd_indexer_head - n_embd_indexer_head_rope;
const uint32_t n_indexer_top_k = hparams.indexer_top_k;
// the indexer head layout is [rope | nope]
GGML_ASSERT(hparams.n_rot() <= n_embd_indexer_head);
const uint32_t kv_lora_rank = hparams.n_lora_kv;
// We have to pre-scale kq_scale and attn_factor to make the YaRN RoPE work correctly.
@@ -273,28 +274,11 @@ llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_par
ggml_tensor * indexer_q = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_q_b, qr);
cb(indexer_q, "indexer_q", il);
// split into {n_embd_indexer_head_rope, n_indexer_head, n_tokens}
ggml_tensor * indexer_q_pe =
ggml_view_3d(ctx0, indexer_q, n_embd_indexer_head_rope, n_indexer_head, n_tokens,
ggml_row_size(indexer_q->type, n_embd_indexer_head),
ggml_row_size(indexer_q->type, n_embd_indexer_head) * n_indexer_head, 0);
cb(indexer_q_pe, "indexer_q_pe", il);
// and {n_embd_indexer_head_nope, n_indexer_head, n_tokens}
ggml_tensor * indexer_q_nope =
ggml_view_3d(ctx0, indexer_q, n_embd_indexer_head_nope, n_indexer_head, n_tokens,
ggml_row_size(indexer_q->type, n_embd_indexer_head),
ggml_row_size(indexer_q->type, n_embd_indexer_head) * n_indexer_head,
ggml_row_size(indexer_q->type, n_embd_indexer_head_nope));
cb(indexer_q_nope, "indexer_q_nope", il);
indexer_q_pe = ggml_rope_ext(ctx0, indexer_q_pe, inp_pos, nullptr, n_rot,
// {n_embd_indexer_head, n_indexer_head, n_tokens}
indexer_q = ggml_reshape_3d(ctx0, indexer_q, n_embd_indexer_head, n_indexer_head, n_tokens);
indexer_q = ggml_rope_ext(ctx0, indexer_q, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NORM, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_q_pe, "indexer_q_pe", il);
// {n_embd_indexer_head_rope + n_embd_indexer_head_nope, n_head, n_tokens}
indexer_q = ggml_concat(ctx0, indexer_q_pe, indexer_q_nope, 0);
cb(indexer_q, "indexer_q", il);
ggml_tensor * indexer_k = ggml_mul_mat(ctx0, model.layers[il].indexer_attn_k, cur);
@@ -303,28 +287,11 @@ llama_model_glm_dsa::graph::graph(const llama_model & model, const llm_graph_par
indexer_k = build_norm(indexer_k, model.layers[il].indexer_k_norm, model.layers[il].indexer_k_norm_b, LLM_NORM, il);
cb(indexer_k, "indexer_k", il);
// split into {n_embd_indexer_head_rope, 1, n_tokens}
ggml_tensor * indexer_k_pe =
ggml_view_3d(ctx0, indexer_k, n_embd_indexer_head_rope, 1, n_tokens,
ggml_row_size(indexer_k->type, n_embd_indexer_head),
ggml_row_size(indexer_k->type, n_embd_indexer_head) * 1, 0);
cb(indexer_k_pe, "indexer_k_pe", il);
// and {n_embd_indexer_head_nope, 1, n_tokens}
ggml_tensor * indexer_k_nope =
ggml_view_3d(ctx0, indexer_k, n_embd_indexer_head_nope, 1, n_tokens,
ggml_row_size(indexer_k->type, n_embd_indexer_head),
ggml_row_size(indexer_k->type, n_embd_indexer_head) * 1,
ggml_row_size(indexer_k->type, n_embd_indexer_head_nope));
cb(indexer_k_nope, "indexer_k_nope", il);
indexer_k_pe = ggml_rope_ext(ctx0, indexer_k_pe, inp_pos, nullptr, n_rot,
// {n_embd_indexer_head, 1, n_tokens}
indexer_k = ggml_reshape_3d(ctx0, indexer_k, n_embd_indexer_head, 1, n_tokens);
indexer_k = ggml_rope_ext(ctx0, indexer_k, inp_pos, nullptr, n_rot,
LLAMA_ROPE_TYPE_NORM, n_ctx_orig, freq_base, freq_scale,
ext_factor, attn_factor, beta_fast, beta_slow);
cb(indexer_k_pe, "indexer_k_pe", il);
// {n_embd_indexer_head_rope + n_embd_indexer_head_nope, 1, n_tokens}
indexer_k = ggml_concat(ctx0, indexer_k_pe, indexer_k_nope, 0);
cb(indexer_k, "indexer_k", il);
// perform Hadamard transform on indexer q and k
+49 -2
View File
@@ -10,6 +10,7 @@
#include "jinja/parser.h"
#include "jinja/lexer.h"
#include "jinja/utils.h"
#include "jinja/caps.h"
#include "testing.h"
@@ -33,6 +34,7 @@ static void test_array_methods(testing & t);
static void test_object_methods(testing & t);
static void test_hasher(testing & t);
static void test_stats(testing & t);
static void test_caps(testing & t);
static void test_string_parts(testing & t);
static void test_fuzzing(testing & t);
@@ -73,6 +75,7 @@ int main(int argc, char *argv[]) {
if (!g_python_mode) {
t.test("hasher", test_hasher);
t.test("stats", test_stats);
t.test("caps", test_caps);
t.test("string parts", test_string_parts);
t.test("fuzzing", test_fuzzing);
}
@@ -2059,6 +2062,51 @@ static void test_stats(testing & t) {
});
}
static void test_caps(testing & t) {
static auto get_caps = [](const std::string & tmpl) -> jinja::caps {
jinja::lexer lexer;
auto lexer_res = lexer.tokenize(tmpl);
jinja::program prog = jinja::parse_from_tokens(lexer_res);
return jinja::caps_get(prog);
};
t.test("string content", [](testing & t) {
auto caps = get_caps(
"{% for message in messages %}"
"{{ message['role'] + ': ' + message['content'] }}"
"{% endfor %}"
);
t.assert_true("supports string content", caps.supports_string_content);
t.assert_true("does not support typed content", !caps.supports_typed_content);
});
t.test("typed content, raises on string", [](testing & t) {
// 'selectattr' is not a String filter, so it throws
auto caps = get_caps(
"{% for message in messages %}"
"{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}"
"{{ content['text'] }}"
"{% endfor %}"
"{% endfor %}"
);
t.assert_true("does not support string content", !caps.supports_string_content);
t.assert_true("supports typed content", caps.supports_typed_content);
});
t.test("typed content, silently drops string", [](testing & t) {
// no throw here, but content[0]['text'] is undefined for a string (MiniMax-M1 case)
auto caps = get_caps(
"{% for message in messages %}"
"{{ message['content'][0]['text'] }}"
"{% endfor %}"
);
t.assert_true("does not support string content", !caps.supports_string_content);
t.assert_true("supports typed content", caps.supports_typed_content);
});
}
static void test_string_parts(testing & t) {
static auto render = [](const std::string & tmpl, const json & vars) -> jinja::string {
jinja::lexer lexer;
@@ -2116,8 +2164,7 @@ static void test_template_cpp(testing & t, const std::string & name, const std::
t.log("Actual : " + json(rendered).dump());
}
} catch (const jinja::not_implemented_exception & e) {
// TODO @ngxson : remove this when the test framework supports skipping tests
t.log("Skipped: " + std::string(e.what()));
t.skip(e.what());
}
});
}
+28 -3
View File
@@ -21,6 +21,11 @@ struct testing {
int failures = 0;
int unnamed = 0;
int exceptions = 0;
int skipped = 0;
// set by skip(), read by the innermost test()
bool skip_current = false;
std::string skip_reason;
static constexpr std::size_t status_column = 80;
@@ -78,7 +83,12 @@ struct testing {
}
}
void print_result(const std::string &label, int new_failures, int new_assertions, const std::string &extra = "") const {
void skip(const std::string &reason = "") {
skip_current = true;
skip_reason = reason;
}
void print_result(const std::string &label, int new_failures, int new_assertions, const std::string &extra = "", bool was_skipped = false) const {
std::string line = indent() + label;
std::string details;
@@ -101,7 +111,7 @@ struct testing {
line += " (" + details + ")";
}
std::string status = (new_failures == 0) ? "[PASS]" : "[FAIL]";
std::string status = new_failures != 0 ? "[FAIL]" : (was_skipped ? "[SKIP]" : "[PASS]");
if (line.size() + 1 < status_column) {
line.append(status_column - line.size(), ' ');
@@ -126,12 +136,26 @@ struct testing {
int before_failures = failures;
int before_assertions = assertions;
// do not let a skipped subtest also mark its parent as skipped
bool outer_skip = skip_current;
std::string outer_skip_reason = skip_reason;
skip_current = false;
skip_reason.clear();
run_with_exceptions([&] { f(*this); }, "test");
int new_failures = failures - before_failures;
int new_assertions = assertions - before_assertions;
print_result(name, new_failures, new_assertions);
bool was_skipped = skip_current && new_failures == 0;
if (was_skipped) {
++skipped;
}
print_result(name, new_failures, new_assertions, was_skipped ? skip_reason : "", was_skipped);
skip_current = outer_skip;
skip_reason = outer_skip_reason;
stack.pop_back();
}
@@ -238,6 +262,7 @@ struct testing {
out << "assertions : " << assertions << "\n";
out << "failures : " << failures << "\n";
out << "exceptions : " << exceptions << "\n";
out << "skipped : " << skipped << "\n";
return failures == 0 ? 0 : 1;
}
};
+2 -2
View File
@@ -846,7 +846,7 @@ static cmd_params parse_cmd_params(int argc, char ** argv) {
invalid_param = true;
break;
}
LOG_WRN("DEPRECATED: -mmp and --mmap are deprecated in favour of --load-mode. Please use --load-mode mmap instead.");
LOG_WRN("DEPRECATED: -mmp and --mmap are deprecated in favour of --load-mode. Please use --load-mode mmap instead.\n");
auto p = string_split<bool>(argv[i], split_delim);
std::vector<llama_load_mode> modes;
@@ -865,7 +865,7 @@ static cmd_params parse_cmd_params(int argc, char ** argv) {
invalid_param = true;
break;
}
LOG_WRN("DEPRECATED: -dio and --direct-io are deprecated in favour of --load-mode. Please use --load-mode dio instead.");
LOG_WRN("DEPRECATED: -dio and --direct-io are deprecated in favour of --load-mode. Please use --load-mode dio instead.\n");
auto p = string_split<bool>(argv[i], split_delim);
std::vector<llama_load_mode> modes;
@@ -8,8 +8,7 @@
SettingsChatImportExportTab,
SettingsChatMobileHeader,
SettingsChatToolsTab,
SettingsFooter,
SettingsRemoteAccess
SettingsFooter
} from '$lib/components/app/settings';
import { Button } from '$lib/components/ui/button';
import {
@@ -153,8 +152,6 @@
<SettingsChatToolsTab />
{:else if currentSection.title === SETTINGS_SECTION_TITLES.IMPORT_EXPORT}
<SettingsChatImportExportTab />
{:else if currentSection.title === SETTINGS_SECTION_TITLES.REMOTE_ACCESS}
<SettingsRemoteAccess />
{:else if currentSection.fields}
<div class="space-y-6">
<SettingsChatFields
@@ -1,291 +0,0 @@
<script lang="ts">
import {
AlertCircle,
Check,
Copy,
Loader2,
RefreshCw,
Users,
Wifi,
WifiOff
} from '@lucide/svelte';
import { SettingsGroup } from '$lib/components/app/settings';
import * as AlertDialog from '$lib/components/ui/alert-dialog';
import { Badge } from '$lib/components/ui/badge';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { webrtcStore } from '$lib/stores/webrtc.svelte';
import { fade } from 'svelte/transition';
// -- host state
let codeCopied = $state(false);
let showRegenerateDialog = $state(false);
let regenerating = $state(false);
// -- client state
let joinInput = $state('');
let joinError = $state('');
let joining = $state(false);
async function handleStartHost() {
await webrtcStore.startHost();
}
function handleStopHost() {
webrtcStore.stopHost();
}
function copyCode() {
navigator.clipboard.writeText(webrtcStore.shareCode).then(() => {
codeCopied = true;
setTimeout(() => (codeCopied = false), 2000);
});
}
async function handleRegenerateConfirm() {
showRegenerateDialog = false;
regenerating = true;
try {
await webrtcStore.regenerateCodes();
} finally {
regenerating = false;
}
}
async function handleJoin() {
joinError = '';
const code = joinInput.trim().replace(/\s/g, '');
if (code.length < 40) {
joinError = 'Code must be 40 characters';
return;
}
joining = true;
try {
await webrtcStore.joinAsClient(code);
} catch (e) {
joinError = e instanceof Error ? e.message : String(e);
} finally {
joining = false;
}
}
function handleLeave() {
webrtcStore.leaveAsClient();
joinInput = '';
joinError = '';
}
// Display the share code broken into 8-char blocks for readability
function formatCode(code: string): string {
return code.match(/.{1,8}/g)?.join(' ') ?? code;
}
</script>
<div class="space-y-12" in:fade={{ duration: 150 }}>
<!-- ------------------------------------------------------------------ -->
<!-- HOST -->
<!-- ------------------------------------------------------------------ -->
<SettingsGroup title="Host">
<div class="space-y-4">
<p class="text-sm text-muted-foreground">
Generate a code and share it so remote devices can connect to this instance.
</p>
<!-- Share code block: shown whenever codes exist, even when host is inactive -->
{#if webrtcStore.hasHostCodes}
<div class="rounded-lg border border-border bg-muted/40 p-4">
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Share code
</p>
<p class="break-all font-mono text-sm tracking-widest select-all">
{formatCode(webrtcStore.shareCode)}
</p>
<p class="mt-2 text-xs text-muted-foreground">
Share this 40-character code with remote devices. It includes both the room ID and the
passcode.
</p>
</div>
<div class="flex flex-wrap gap-2">
<Button variant="outline" size="sm" onclick={copyCode} class="gap-1.5">
{#if codeCopied}
<Check class="h-3.5 w-3.5" />
Copied
{:else}
<Copy class="h-3.5 w-3.5" />
Copy code
{/if}
</Button>
<Button
variant="outline"
size="sm"
onclick={() => (showRegenerateDialog = true)}
disabled={regenerating}
class="gap-1.5"
>
{#if regenerating}
<Loader2 class="h-3.5 w-3.5 animate-spin" />
Regenerating...
{:else}
<RefreshCw class="h-3.5 w-3.5" />
Regenerate code
{/if}
</Button>
</div>
{/if}
<!-- Status row (only when host is active) -->
{#if webrtcStore.mode === 'host'}
<div class="flex items-center gap-3">
{#if webrtcStore.status === 'connecting'}
<Badge variant="secondary" class="gap-1.5">
<Loader2 class="h-3 w-3 animate-spin" />
Connecting to trackers...
</Badge>
{:else if webrtcStore.status === 'connected'}
<Badge variant="default" class="gap-1.5">
<Wifi class="h-3 w-3" />
Active
</Badge>
<span class="flex items-center gap-1 text-sm text-muted-foreground">
<Users class="h-3.5 w-3.5" />
{webrtcStore.peerCount}
{webrtcStore.peerCount === 1 ? 'client' : 'clients'} connected
</span>
{:else if webrtcStore.status === 'error'}
<Badge variant="destructive" class="gap-1.5">
<AlertCircle class="h-3 w-3" />
Error
</Badge>
<span class="text-sm text-destructive">{webrtcStore.errorMessage}</span>
{/if}
</div>
{/if}
<!-- Enable / Disable button -->
{#if webrtcStore.mode === 'off' || webrtcStore.mode === 'client'}
<Button
variant="outline"
onclick={handleStartHost}
disabled={webrtcStore.mode === 'client'}
>
<Wifi class="h-4 w-4" />
Enable remote access
</Button>
{:else}
<Button variant="outline" onclick={handleStopHost}>
<WifiOff class="h-4 w-4" />
Disable remote access
</Button>
{/if}
{#if webrtcStore.mode === 'client'}
<p class="text-xs text-muted-foreground">
Disable join mode first before enabling host mode.
</p>
{/if}
</div>
</SettingsGroup>
<!-- ------------------------------------------------------------------ -->
<!-- CLIENT / JOIN -->
<!-- ------------------------------------------------------------------ -->
<SettingsGroup title="Join">
<div class="space-y-4">
<p class="text-sm text-muted-foreground">
Connect to a remote llama.cpp instance. All requests will be routed through the peer-to-peer
tunnel.
</p>
{#if webrtcStore.mode === 'client'}
<div class="space-y-3">
<div class="flex items-center gap-3">
{#if webrtcStore.status === 'connecting'}
<Badge variant="secondary" class="gap-1.5">
<Loader2 class="h-3 w-3 animate-spin" />
Connecting...
</Badge>
{:else if webrtcStore.status === 'connected'}
<Badge variant="default" class="gap-1.5">
<Wifi class="h-3 w-3" />
Connected to host
</Badge>
{:else if webrtcStore.status === 'error'}
<Badge variant="destructive" class="gap-1.5">
<AlertCircle class="h-3 w-3" />
Disconnected
</Badge>
<span class="text-sm text-destructive">{webrtcStore.errorMessage}</span>
{/if}
</div>
<Button variant="outline" onclick={handleLeave}>
<WifiOff class="h-4 w-4" />
Leave
</Button>
</div>
{:else}
<div class="space-y-3">
<div class="space-y-1.5">
<label for="join-code" class="text-sm font-medium">Access code</label>
<Input
id="join-code"
placeholder="Paste the 40-character code from the host"
bind:value={joinInput}
disabled={joining || webrtcStore.mode === 'host'}
class="font-mono"
/>
{#if joinError}
<p class="text-sm text-destructive">{joinError}</p>
{/if}
</div>
<Button
onclick={handleJoin}
disabled={joining || joinInput.trim().length < 40 || webrtcStore.mode === 'host'}
>
{#if joining}
<Loader2 class="h-4 w-4 animate-spin" />
Connecting...
{:else}
<Wifi class="h-4 w-4" />
Connect
{/if}
</Button>
{#if webrtcStore.mode === 'host'}
<p class="text-xs text-muted-foreground">
Disable host mode first before joining a remote server.
</p>
{/if}
</div>
{/if}
</div>
</SettingsGroup>
<p class="text-xs text-muted-foreground">
Uses WebRTC with Google STUN servers for NAT traversal. Signaling via public WebTorrent
trackers. No data is routed through any relay server.
</p>
</div>
<!-- Regenerate code confirmation dialog -->
<AlertDialog.Root bind:open={showRegenerateDialog}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Regenerate access code?</AlertDialog.Title>
<AlertDialog.Description>
This will create a new room and passcode. Any devices using the current code will be
disconnected and will need to be updated with the new code.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Action onclick={handleRegenerateConfirm}>Regenerate</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
@@ -74,10 +74,3 @@ export { default as SettingsChatFields } from './SettingsChat/SettingsChatFields
* server favicons and permission management controls.
*/
export { default as SettingsChatToolsTab } from './SettingsChat/SettingsChatToolsTab.svelte';
/**
* Remote Access configuration panel.
* Host mode: generates a share code for remote clients.
* Client mode: accepts a share code and routes all same-origin requests through a WebRTC tunnel.
*/
export { default as SettingsRemoteAccess } from './SettingsRemoteAccess.svelte';
@@ -18,7 +18,6 @@ export const SETTINGS_SECTION_SLUGS = {
GENERAL: 'general',
IMPORT_EXPORT: 'import-export',
PENALTIES: 'penalties',
REMOTE_ACCESS: 'remote-access',
SAMPLING: 'sampling',
TOOLS: 'tools'
} as const;
@@ -13,7 +13,6 @@ import {
Monitor,
Moon,
PencilRuler,
Radio,
Sliders,
Sun
} from '@lucide/svelte';
@@ -37,7 +36,6 @@ export const SETTINGS_SECTION_TITLES = {
GENERAL: 'General',
IMPORT_EXPORT: 'Import/Export',
PENALTIES: 'Penalties',
REMOTE_ACCESS: 'Remote Access',
SAMPLING: 'Sampling',
TOOLS: 'Tools'
} as const;
@@ -48,11 +46,6 @@ const STANDALONE_SECTIONS: { title: SettingsSectionTitle; slug: string; icon: Co
icon: Database,
slug: SETTINGS_SECTION_SLUGS.IMPORT_EXPORT,
title: SETTINGS_SECTION_TITLES.IMPORT_EXPORT
},
{
icon: Radio,
slug: SETTINGS_SECTION_SLUGS.REMOTE_ACCESS,
title: SETTINGS_SECTION_TITLES.REMOTE_ACCESS
}
];
const COLOR_MODE_OPTIONS: Array<{ value: string; label: string; icon: Component }> = [
-304
View File
@@ -1,304 +0,0 @@
import { browser } from '$app/environment';
import {
ClientTunnel,
generatePassCode,
generateRoomCode,
HostTunnel
} from '$lib/utils/webrtc-tunnel';
// Stores the generated host codes; persists until explicitly regenerated.
const HOST_CODES_KEY = 'llama_webrtc_host_codes';
// Stores the active session (mode + codes) for auto-reconnect on reload.
const SESSION_KEY = 'llama_webrtc_session';
type HostCodes = { roomCode: string; passCode: string };
type SessionData = { mode: 'host' | 'client'; roomCode: string; passCode: string };
type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'error';
class WebRTCStore {
mode = $state<'off' | 'host' | 'client'>('off');
status = $state<ConnectionStatus>('idle');
peerCount = $state(0);
errorMessage = $state('');
// Reflect the saved host codes; populated on init even when mode is 'off'.
private _roomCode = $state('');
private _passCode = $state('');
private hostTunnel: HostTunnel | null = null;
private clientTunnel: ClientTunnel | null = null;
// Requests that arrive while mode='client' but tunnel not yet open are held here.
private connectionWaiters: Array<{ resolve: () => void; reject: (e: Error) => void }> = [];
// The original window.fetch saved before the interceptor is installed.
private originalFetch: typeof window.fetch | null = null;
constructor() {
if (browser) {
// Load persisted host codes so the UI can show them before host is enabled.
const saved = this.readHostCodes();
if (saved) {
this._roomCode = saved.roomCode;
this._passCode = saved.passCode;
}
this.restoreSession();
}
}
get roomCode(): string {
return this._roomCode;
}
get passCode(): string {
return this._passCode;
}
// Full 40-char code shared with remote clients.
get shareCode(): string {
return this._roomCode + this._passCode;
}
get isConnected(): boolean {
return this.status === 'connected';
}
get hasHostCodes(): boolean {
return this._roomCode !== '' && this._passCode !== '';
}
// -------------------------------------------------------------------------
// Host
// -------------------------------------------------------------------------
async startHost(): Promise<void> {
if (this.mode !== 'off') return;
// Reuse the persisted codes; generate once if none exist yet.
let roomCode = this._roomCode;
let passCode = this._passCode;
if (!roomCode || !passCode) {
roomCode = generateRoomCode();
passCode = generatePassCode();
this._roomCode = roomCode;
this._passCode = passCode;
this.writeHostCodes({ passCode, roomCode });
}
await this.activateHost(roomCode, passCode);
}
/** Generate a fresh room + pass code. Restarts the tunnel if currently active. */
async regenerateCodes(): Promise<void> {
const roomCode = generateRoomCode();
const passCode = generatePassCode();
this._roomCode = roomCode;
this._passCode = passCode;
this.writeHostCodes({ passCode, roomCode });
if (this.mode === 'host') {
this.hostTunnel?.stop();
this.hostTunnel = null;
await this.activateHost(roomCode, passCode);
}
}
private async activateHost(roomCode: string, passCode: string): Promise<void> {
this.mode = 'host';
this.status = 'connecting';
this.errorMessage = '';
this.peerCount = 0;
const tunnel = new HostTunnel(roomCode, passCode, {
onPeerCountChange: (count) => {
this.peerCount = count;
}
});
try {
await tunnel.start();
this.hostTunnel = tunnel;
this.status = 'connected';
this.writeSession({ mode: 'host', passCode, roomCode });
} catch (e) {
this.hostTunnel = null;
this.status = 'error';
this.errorMessage = e instanceof Error ? e.message : String(e);
}
}
stopHost(): void {
this.hostTunnel?.stop();
this.hostTunnel = null;
this.mode = 'off';
this.status = 'idle';
this.peerCount = 0;
// Codes are intentionally kept: _roomCode/_passCode and HOST_CODES_KEY
// remain so the user can re-enable without a new code.
this.clearSession();
}
// -------------------------------------------------------------------------
// Client
// -------------------------------------------------------------------------
async joinAsClient(shareCode: string): Promise<void> {
if (shareCode.length < 40) throw new Error('Invalid code: must be 40 characters');
const roomCode = shareCode.slice(0, 8);
const passCode = shareCode.slice(8);
await this.activateClient(roomCode, passCode);
}
private async activateClient(roomCode: string, passCode: string): Promise<void> {
this.mode = 'client';
this.status = 'connecting';
this.errorMessage = '';
// Install the fetch interceptor synchronously (before any await) so that
// requests fired by layout effects on the same tick are already captured.
this.installInterceptor();
const tunnel = new ClientTunnel(roomCode, passCode, {
onConnected: () => {
this.status = 'connected';
},
onDisconnected: () => {
this.status = 'error';
this.errorMessage = 'Disconnected from host';
}
});
try {
await tunnel.connect();
this.clientTunnel = tunnel;
this.writeSession({ mode: 'client', passCode, roomCode });
// Release any requests that were queued while connecting.
const waiters = this.connectionWaiters.splice(0);
for (const w of waiters) w.resolve();
} catch (e) {
this.clientTunnel = null;
this.mode = 'off';
this.status = 'error';
this.errorMessage = e instanceof Error ? e.message : String(e);
this.uninstallInterceptor();
// Reject queued requests.
const waiters = this.connectionWaiters.splice(0);
const err = e instanceof Error ? e : new Error(String(e));
for (const w of waiters) w.reject(err);
throw e;
}
}
leaveAsClient(): void {
this.uninstallInterceptor();
this.clientTunnel?.disconnect();
this.clientTunnel = null;
this.mode = 'off';
this.status = 'idle';
this.clearSession();
}
// -------------------------------------------------------------------------
// Fetch interceptor (installed synchronously when client mode activates)
// -------------------------------------------------------------------------
private installInterceptor(): void {
if (this.originalFetch) return; // already installed
this.originalFetch = window.fetch.bind(window);
window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
try {
const url =
input instanceof Request ? input.url : input instanceof URL ? input.href : String(input);
const parsed = new URL(url, window.location.href);
if (parsed.origin === window.location.origin) {
return this.tunnelFetch(input, init);
}
} catch {
// not a parseable URL — fall through
}
return this.originalFetch!(input, init);
};
}
private uninstallInterceptor(): void {
if (!this.originalFetch) return;
window.fetch = this.originalFetch;
this.originalFetch = null;
}
// -------------------------------------------------------------------------
// Fetch proxy
// -------------------------------------------------------------------------
tunnelFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
// If the tunnel is open, forward immediately.
if (this.clientTunnel?.isConnected) {
return this.clientTunnel.fetch(input, init);
}
// If we are still connecting, queue the request until the tunnel opens.
if (this.mode === 'client' && this.status === 'connecting') {
return new Promise<void>((resolve, reject) => {
this.connectionWaiters.push({ reject, resolve });
}).then(() => this.clientTunnel!.fetch(input, init));
}
throw new Error('tunnel not connected');
}
// -------------------------------------------------------------------------
// Persistence helpers
// -------------------------------------------------------------------------
private readHostCodes(): HostCodes | null {
try {
const raw = localStorage.getItem(HOST_CODES_KEY);
return raw ? (JSON.parse(raw) as HostCodes) : null;
} catch {
return null;
}
}
private writeHostCodes(codes: HostCodes): void {
localStorage.setItem(HOST_CODES_KEY, JSON.stringify(codes));
}
private restoreSession(): void {
try {
const raw = localStorage.getItem(SESSION_KEY);
if (!raw) return;
const session = JSON.parse(raw) as SessionData;
if (session.mode === 'host') {
void this.activateHost(session.roomCode, session.passCode);
} else if (session.mode === 'client') {
void this.activateClient(session.roomCode, session.passCode);
}
} catch {
// ignore corrupt storage
}
}
private writeSession(data: SessionData): void {
localStorage.setItem(SESSION_KEY, JSON.stringify(data));
}
private clearSession(): void {
localStorage.removeItem(SESSION_KEY);
}
}
export const webrtcStore = new WebRTCStore();
-729
View File
@@ -1,729 +0,0 @@
/**
* WebRTC tunnel for remote llama.cpp access.
*
* Signaling uses the WebTorrent tracker WebSocket protocol (no external deps).
* The room code is used as the info_hash rendezvous key; the pass code
* authenticates the client on the data channel after the WebRTC handshake.
*
* Host: announces periodically, accepts incoming offers, relays HTTP requests
* made by connected clients back to its own local server.
* Client: sends an offer, authenticates via pass code, then all same-origin
* fetch calls are transparently forwarded through the data channel.
*/
const STUN_CONFIG: RTCConfiguration = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }]
};
const TRACKER_URLS = ['wss://tracker.openwebtorrent.com', 'wss://tracker.btorrent.xyz'];
const ICE_GATHER_TIMEOUT_MS = 10_000;
const ANNOUNCE_INTERVAL_MS = 30_000;
const CONNECT_TIMEOUT_MS = 30_000;
const TRACKER_CONNECT_TIMEOUT_MS = 10_000;
// Characters that are unambiguous to read aloud or type
const CODE_CHARS = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
function randomStr(len: number): string {
const bytes = new Uint8Array(len);
crypto.getRandomValues(bytes);
let result = '';
for (const b of bytes) result += CODE_CHARS[b % CODE_CHARS.length];
return result;
}
export function generateRoomCode(): string {
return randomStr(8);
}
export function generatePassCode(): string {
return randomStr(32);
}
// info_hash must be exactly 20 chars for WebTorrent trackers
function roomToInfoHash(roomCode: string): string {
return roomCode.padEnd(20, '0').slice(0, 20);
}
function waitForIceComplete(pc: RTCPeerConnection): Promise<void> {
return new Promise((resolve, reject) => {
if (pc.iceGatheringState === 'complete') {
resolve();
return;
}
const timer = setTimeout(
() => reject(new Error('ICE gathering timed out')),
ICE_GATHER_TIMEOUT_MS
);
pc.addEventListener('icegatheringstatechange', () => {
if (pc.iceGatheringState === 'complete') {
clearTimeout(timer);
resolve();
}
});
});
}
// ---------------------------------------------------------------------------
// Tracker signaling (WebTorrent WS tracker protocol)
// ---------------------------------------------------------------------------
type TrackerMsg = Record<string, unknown>;
class Tracker {
private ws: WebSocket | null = null;
private readonly infoHash: string;
private readonly peerId: string;
onOffer?: (fromPeerId: string, offerId: string, offer: RTCSessionDescriptionInit) => void;
onAnswer?: (offerId: string, answer: RTCSessionDescriptionInit) => void;
onClose?: () => void;
constructor(infoHash: string, peerId: string) {
this.infoHash = infoHash;
this.peerId = peerId;
}
connect(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
this.ws = ws;
const timer = setTimeout(
() => reject(new Error('tracker connect timeout')),
TRACKER_CONNECT_TIMEOUT_MS
);
ws.onopen = () => {
clearTimeout(timer);
resolve();
};
ws.onerror = () => {
clearTimeout(timer);
reject(new Error('tracker WebSocket error'));
};
ws.onclose = () => {
this.onClose?.();
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data as string) as TrackerMsg;
if (msg.offer && msg.peer_id && msg.offer_id) {
this.onOffer?.(
msg.peer_id as string,
msg.offer_id as string,
msg.offer as RTCSessionDescriptionInit
);
} else if (msg.answer && msg.offer_id) {
this.onAnswer?.(msg.offer_id as string, msg.answer as RTCSessionDescriptionInit);
}
} catch {
// ignore malformed tracker messages
}
};
});
}
private send(msg: TrackerMsg): void {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(msg));
}
}
announce(
opts: {
numwant?: number;
offers?: Array<{ offer_id: string; offer: RTCSessionDescriptionInit }>;
} = {}
): void {
const msg: TrackerMsg = {
action: 'announce',
info_hash: this.infoHash,
numwant: opts.numwant ?? 0,
peer_id: this.peerId
};
if (opts.offers) msg.offers = opts.offers;
this.send(msg);
}
sendAnswer(toPeerId: string, offerId: string, answer: RTCSessionDescriptionInit): void {
this.send({
action: 'announce',
answer,
info_hash: this.infoHash,
offer_id: offerId,
peer_id: this.peerId,
to_peer_id: toPeerId
});
}
close(): void {
this.ws?.close();
this.ws = null;
}
}
// ---------------------------------------------------------------------------
// Tunnel message types (JSON, sent over RTCDataChannel)
// ---------------------------------------------------------------------------
interface ReqMsg {
type: 'req';
id: string;
method: string;
path: string;
headers: Record<string, string>;
body: string | null; // base64 or null
}
interface ResStartMsg {
type: 'res_start';
id: string;
status: number;
headers: Record<string, string>;
}
interface ResChunkMsg {
type: 'res_chunk';
id: string;
data: string; // base64
}
interface ResEndMsg {
type: 'res_end';
id: string;
}
interface ResErrMsg {
type: 'res_err';
id: string;
message: string;
}
interface CancelMsg {
type: 'cancel';
id: string;
}
interface AuthMsg {
type: 'auth';
pass: string;
}
interface AuthOkMsg {
type: 'auth_ok';
}
interface AuthFailMsg {
type: 'auth_fail';
}
type TunnelMsg =
| ReqMsg
| ResStartMsg
| ResChunkMsg
| ResEndMsg
| ResErrMsg
| CancelMsg
| AuthMsg
| AuthOkMsg
| AuthFailMsg;
// Max bytes per res_chunk message (keeps data channel messages well below limits)
const CHUNK_BYTES = 8192;
function uint8ToBase64(bytes: Uint8Array): string {
let binary = '';
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return btoa(binary);
}
function base64ToUint8(b64: string): Uint8Array {
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
// ---------------------------------------------------------------------------
// HostTunnel
// ---------------------------------------------------------------------------
export type HostCallbacks = {
onPeerCountChange?: (count: number) => void;
};
export class HostTunnel {
private readonly passCode: string;
private readonly infoHash: string;
private readonly peerId: string;
private readonly callbacks: HostCallbacks;
private trackers: Tracker[] = [];
private peers = new Map<string, { pc: RTCPeerConnection; channel: RTCDataChannel }>();
// AbortControllers for in-flight host-side fetch() calls, keyed by request id.
private activeRequests = new Map<string, AbortController>();
private announceTimer: ReturnType<typeof setInterval> | null = null;
private stopped = false;
constructor(roomCode: string, passCode: string, callbacks: HostCallbacks = {}) {
this.passCode = passCode;
this.infoHash = roomToInfoHash(roomCode);
this.peerId = randomStr(20);
this.callbacks = callbacks;
}
get peerCount(): number {
return this.peers.size;
}
async start(): Promise<void> {
await this.connectTrackers();
this.announceTimer = setInterval(() => {
for (const t of this.trackers) t.announce({ numwant: 0 });
}, ANNOUNCE_INTERVAL_MS);
}
private async connectTrackers(): Promise<void> {
for (const url of TRACKER_URLS) {
try {
await this.connectOneTracker(url);
} catch {
// try next
}
}
}
private async connectOneTracker(url: string): Promise<void> {
const tracker = new Tracker(this.infoHash, this.peerId);
tracker.onOffer = (fromPeerId, offerId, offer) => {
void this.handleOffer(tracker, fromPeerId, offerId, offer);
};
tracker.onClose = () => {
this.trackers = this.trackers.filter((t) => t !== tracker);
if (!this.stopped) {
setTimeout(() => void this.connectOneTracker(url), 5000);
}
};
await tracker.connect(url);
tracker.announce({ numwant: 0 });
this.trackers.push(tracker);
}
private async handleOffer(
tracker: Tracker,
fromPeerId: string,
offerId: string,
offer: RTCSessionDescriptionInit
): Promise<void> {
try {
const pc = new RTCPeerConnection(STUN_CONFIG);
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
await waitForIceComplete(pc);
tracker.sendAnswer(fromPeerId, offerId, pc.localDescription!);
pc.ondatachannel = (event) => this.setupChannel(pc, fromPeerId, event.channel);
} catch {
// ignore failed handshakes
}
}
private setupChannel(pc: RTCPeerConnection, peerId: string, channel: RTCDataChannel): void {
let authenticated = false;
channel.onclose = () => {
if (this.peers.has(peerId)) {
this.peers.delete(peerId);
this.callbacks.onPeerCountChange?.(this.peers.size);
}
pc.close();
};
channel.onmessage = (event) => {
try {
const msg = JSON.parse(event.data as string) as TunnelMsg;
if (!authenticated) {
if (msg.type === 'auth') {
if (msg.pass === this.passCode) {
authenticated = true;
channel.send(JSON.stringify({ type: 'auth_ok' } satisfies AuthOkMsg));
this.peers.set(peerId, { channel, pc });
this.callbacks.onPeerCountChange?.(this.peers.size);
} else {
channel.send(JSON.stringify({ type: 'auth_fail' } satisfies AuthFailMsg));
channel.close();
}
}
return;
}
if (msg.type === 'req') {
void this.handleRequest(channel, msg);
} else if (msg.type === 'cancel') {
this.activeRequests.get(msg.id)?.abort();
}
} catch {
// ignore malformed messages
}
};
}
private async handleRequest(channel: RTCDataChannel, msg: ReqMsg): Promise<void> {
const { body, headers, id, method, path } = msg;
const t0 = performance.now();
const ac = new AbortController();
this.activeRequests.set(id, ac);
try {
const init: RequestInit = { headers, method, signal: ac.signal };
if (body !== null) init.body = base64ToUint8(body).buffer as ArrayBuffer;
const response = await fetch(path, init);
const resHeaders: Record<string, string> = {};
response.headers.forEach((v, k) => {
resHeaders[k] = v;
});
console.log(
`[rtc] ${method} ${path} -> ${response.status} (${Math.round(performance.now() - t0)}ms)`
);
channel.send(
JSON.stringify({
headers: resHeaders,
id,
status: response.status,
type: 'res_start'
} satisfies ResStartMsg)
);
const reader = response.body?.getReader();
if (reader) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (let i = 0; i < value.length; i += CHUNK_BYTES) {
const slice = value.subarray(i, i + CHUNK_BYTES);
channel.send(
JSON.stringify({
data: uint8ToBase64(slice),
id,
type: 'res_chunk'
} satisfies ResChunkMsg)
);
}
}
}
channel.send(JSON.stringify({ id, type: 'res_end' } satisfies ResEndMsg));
} catch (e) {
// AbortError means the client cancelled — no need to send an error back.
if (!(e instanceof DOMException && e.name === 'AbortError')) {
const errMsg = e instanceof Error ? e.message : String(e);
console.error(`[rtc] ${method} ${path} -> error: ${errMsg}`);
channel.send(JSON.stringify({ id, message: errMsg, type: 'res_err' } satisfies ResErrMsg));
}
} finally {
this.activeRequests.delete(id);
}
}
stop(): void {
this.stopped = true;
if (this.announceTimer) clearInterval(this.announceTimer);
for (const t of this.trackers) t.close();
for (const { channel, pc } of this.peers.values()) {
channel.close();
pc.close();
}
for (const ac of this.activeRequests.values()) ac.abort();
this.trackers = [];
this.peers.clear();
this.activeRequests.clear();
}
}
// ---------------------------------------------------------------------------
// ClientTunnel
// ---------------------------------------------------------------------------
export type ClientCallbacks = {
onConnected?: () => void;
onDisconnected?: () => void;
};
type PendingReq = {
onStart: (status: number, headers: Record<string, string>) => void;
onChunk: (data: string) => void;
onEnd: () => void;
onError: (message: string) => void;
};
export class ClientTunnel {
private readonly passCode: string;
private readonly infoHash: string;
private readonly peerId: string;
private readonly callbacks: ClientCallbacks;
private pc: RTCPeerConnection | null = null;
private channel: RTCDataChannel | null = null;
private tracker: Tracker | null = null;
private pending = new Map<string, PendingReq>();
constructor(roomCode: string, passCode: string, callbacks: ClientCallbacks = {}) {
this.passCode = passCode;
this.infoHash = roomToInfoHash(roomCode);
this.peerId = randomStr(20);
this.callbacks = callbacks;
}
get isConnected(): boolean {
return this.channel?.readyState === 'open';
}
async connect(): Promise<void> {
let lastError: Error = new Error('no trackers available');
for (const url of TRACKER_URLS) {
try {
await this.connectViaTracker(url);
return;
} catch (e) {
lastError = e instanceof Error ? e : new Error(String(e));
this.cleanupConnection();
}
}
throw lastError;
}
private async connectViaTracker(trackerUrl: string): Promise<void> {
const offerId = randomStr(20);
const pc = new RTCPeerConnection(STUN_CONFIG);
this.pc = pc;
const channel = pc.createDataChannel('tunnel', { ordered: true });
this.channel = channel;
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
await waitForIceComplete(pc);
const tracker = new Tracker(this.infoHash, this.peerId);
this.tracker = tracker;
await tracker.connect(trackerUrl);
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('connection timed out waiting for host'));
}, CONNECT_TIMEOUT_MS);
tracker.onAnswer = async (_offerId, answer) => {
if (_offerId !== offerId) return;
try {
await pc.setRemoteDescription(new RTCSessionDescription(answer));
} catch (e) {
clearTimeout(timer);
reject(e);
}
};
channel.onopen = () => {
channel.send(JSON.stringify({ pass: this.passCode, type: 'auth' } satisfies AuthMsg));
};
channel.onmessage = (event) => {
try {
const msg = JSON.parse(event.data as string) as TunnelMsg;
if (msg.type === 'auth_ok') {
clearTimeout(timer);
this.callbacks.onConnected?.();
resolve();
} else if (msg.type === 'auth_fail') {
clearTimeout(timer);
reject(new Error('authentication failed: invalid passcode'));
} else {
this.routeResponseMsg(msg);
}
} catch {
// ignore
}
};
channel.onclose = () => {
this.callbacks.onDisconnected?.();
this.rejectAllPending('connection closed');
};
channel.onerror = () => {
clearTimeout(timer);
reject(new Error('data channel error'));
};
tracker.announce({
numwant: 1,
offers: [{ offer: pc.localDescription!, offer_id: offerId }]
});
});
}
private routeResponseMsg(msg: TunnelMsg): void {
if (
msg.type !== 'res_start' &&
msg.type !== 'res_chunk' &&
msg.type !== 'res_end' &&
msg.type !== 'res_err'
)
return;
const req = this.pending.get(msg.id);
if (!req) return;
if (msg.type === 'res_start') {
req.onStart(msg.status, msg.headers);
} else if (msg.type === 'res_chunk') {
req.onChunk(msg.data);
} else if (msg.type === 'res_end') {
req.onEnd();
this.pending.delete(msg.id);
} else if (msg.type === 'res_err') {
req.onError(msg.message);
this.pending.delete(msg.id);
}
}
private rejectAllPending(reason: string): void {
for (const req of this.pending.values()) req.onError(reason);
this.pending.clear();
}
private cleanupConnection(): void {
this.channel?.close();
this.pc?.close();
this.tracker?.close();
this.channel = null;
this.pc = null;
this.tracker = null;
}
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
if (!this.channel || this.channel.readyState !== 'open') {
throw new Error('tunnel not connected');
}
const request = new Request(input, init);
const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
const id = randomStr(16);
if (signal?.aborted) {
return Promise.reject(new DOMException('Aborted', 'AbortError'));
}
// Extract path+query so the host fetches relative to its own origin
const reqUrl = new URL(request.url);
const path = reqUrl.pathname + reqUrl.search;
const headers: Record<string, string> = {};
request.headers.forEach((v, k) => {
headers[k] = v;
});
let bodyB64: string | null = null;
const bodyBytes = await request.arrayBuffer();
if (bodyBytes.byteLength > 0) {
bodyB64 = uint8ToBase64(new Uint8Array(bodyBytes));
}
return new Promise((resolve, reject) => {
let streamController!: ReadableStreamDefaultController<Uint8Array>;
const stream = new ReadableStream<Uint8Array>({
start(ctrl) {
streamController = ctrl;
}
});
const abortHandler = () => {
this.pending.delete(id);
try {
streamController.error(new DOMException('Aborted', 'AbortError'));
} catch {
// stream may already be closed
}
reject(new DOMException('Aborted', 'AbortError'));
// Tell the host to stop the in-flight fetch
if (this.channel?.readyState === 'open') {
this.channel.send(JSON.stringify({ id, type: 'cancel' } satisfies CancelMsg));
}
};
signal?.addEventListener('abort', abortHandler, { once: true });
this.pending.set(id, {
onChunk: (data) => {
streamController.enqueue(base64ToUint8(data));
},
onEnd: () => {
signal?.removeEventListener('abort', abortHandler);
streamController.close();
},
onError: (message) => {
signal?.removeEventListener('abort', abortHandler);
try {
streamController.error(new Error(message));
} catch {
// stream may already be closed
}
reject(new Error(message));
this.pending.delete(id);
},
onStart: (status, resHeaders) => {
resolve(new Response(stream, { headers: resHeaders, status }));
}
});
this.channel!.send(
JSON.stringify({
body: bodyB64,
headers,
id,
method: request.method,
path,
type: 'req'
} satisfies ReqMsg)
);
});
}
disconnect(): void {
this.rejectAllPending('disconnected');
this.cleanupConnection();
}
}