Compare commits

...
6 Commits
Author SHA1 Message Date
Georgi GerganovandGitHub 4a89937354 tests : reduce FA test sizes (#28842) 2026-09-13 13:05:28 +03:00
Sigbjørn SkjæretandGitHub 37b3a9e0cc ci : remove leftover command (#28839) 2026-09-13 10:41:27 +03:00
Georgi GerganovandGitHub 002a12ad25 ci : cap test-backend-ops parallel jobs at 2 and add a 3600s timeout (#28833)
- Clamp the -j parallelism to min(nproc, 2) so a single-core runner
  uses -j 1 and multi-core runners use at most -j 2, instead of
  unconditionally using $(nproc).
- Add a 3600s timeout to both test-backend-ops runs (the high-perf CPU
  path and the default path) so a hung test cannot stall CI indefinitely.
- Note a TODO to reduce the timeout to 1800s in the future.

Assisted-by: pi:llama.cpp/Qwen3.8-27B
2026-09-13 09:18:28 +03:00
Jeff BolzandGitHub f1e44dcc11 vulkan: workaround NV queuesubmit driver bug (#28830)
There is a driver bug where two queues on the same VkDevice simultaneously
submitting can break some internal synchronization. Until it's fixed, add a
mutex around queuesubmit.
2026-09-13 09:18:19 +03:00
Hongqiang WangandGitHub 56b9eb280a opencl: apply the noshuffle row-alignment rule to q4_K, q5_K and q8_0, not just q6_K (#28575) 2026-09-12 21:33:23 -07:00
Aldehir RojasandGitHub 790cf51aab chat : improve parsing of complex types in qwen3-coder (#28742)
* chat : improve schema support in qwen3 parser

* cont : clean up grammar a bit
2026-09-12 19:08:52 -05:00
7 changed files with 129 additions and 15 deletions
-2
View File
@@ -116,7 +116,6 @@ jobs:
run: |
source .venv/bin/activate
cd tools/server/tests
export ${{ matrix.extra_args }}
PYTEST_WORKERS=1 ./tests.sh
- name: Slow tests
@@ -125,5 +124,4 @@ jobs:
run: |
source .venv/bin/activate
cd tools/server/tests
export ${{ matrix.extra_args }}
PYTEST_WORKERS=1 SLOW_TESTS=1 ./tests.sh
+8 -3
View File
@@ -775,7 +775,11 @@ function gg_run_test_backend_ops {
set -e
local args_extra="-j $(nproc)"
local n_jobs=$(nproc)
if [ "${n_jobs}" -gt 2 ]; then
n_jobs=2
fi
local args_extra="-j ${n_jobs}"
# TODO: fix multi-threaded for ROCm
# https://github.com/ggml-org/llama.cpp/actions/runs/34576278519/job/103297889044?pr=28740#step:3:4865
@@ -789,10 +793,11 @@ function gg_run_test_backend_ops {
args_extra=""
fi
# TODO: reduce the test-backend-ops timeout to 1800s
if [ ! -z ${GG_BUILD_HIGH_PERF} ]; then
(time ./bin/test-backend-ops ${args_extra} -b CPU) 2>&1 | tee -a $OUT/${ci}-test-backend-ops.log
(time timeout 3600 ./bin/test-backend-ops ${args_extra} -b CPU) 2>&1 | tee -a $OUT/${ci}-test-backend-ops.log
else
(time ./bin/test-backend-ops ${args_extra} ) 2>&1 | tee -a $OUT/${ci}-test-backend-ops.log
(time timeout 3600 ./bin/test-backend-ops ${args_extra} ) 2>&1 | tee -a $OUT/${ci}-test-backend-ops.log
fi
set +e
+28 -3
View File
@@ -104,9 +104,34 @@ common_chat_params common_chat_params_init_qwen3_coder(const common_chat_templat
auto arg_open = p.tool_arg_open("<parameter=" + p.tool_arg_name(p.literal(param.name)) + ">\n");
auto arg_value = param.schema->may_be_string() ?
arg_string :
p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
auto types = param.schema->value_types();
auto arg_value = p.eps();
if (!types.has(common_chat_schema::TYPE_STRING)) {
arg_value = p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
} else if (types.is_only(common_chat_schema::TYPE_STRING)) {
arg_value = arg_string;
} else {
// The string alternative accepts any text, so the grammar only keeps the raw string
// rule. The parser still tries the JSON alternatives first to type the value.
auto json_value = p.choice();
if (types.has(common_chat_schema::TYPE_OBJECT)) {
json_value |= p.json_object();
}
if (types.has(common_chat_schema::TYPE_ARRAY)) {
json_value |= p.json_array();
}
if (types.has(common_chat_schema::TYPE_NUMBER) || types.has(common_chat_schema::TYPE_INTEGER)) {
json_value |= p.json_number();
}
if (types.has(common_chat_schema::TYPE_BOOLEAN)) {
json_value |= p.json_bool();
}
if (types.has(common_chat_schema::TYPE_NULL)) {
json_value |= p.json_null();
}
arg_value = p.gbnf(p.atomic(p.tool_arg_json_value(json_value) + arg_close) | arg_string, "xml-arg-string");
}
auto arg_rule = p.rule(rule_name, p.tool_arg(arg_open + arg_value));
+14 -3
View File
@@ -8302,9 +8302,20 @@ inline bool use_adreno_kernels(const ggml_backend_opencl_context *backend_ctx, c
bool threashold_ok = tensor->ne[0] >= threshold_ne0 && tensor->ne[1] >= threshold_ne1 &&
tensor->ne[2] == 1 && tensor->ne[3] == 1;
// q6_K adreno kernels requires ne1 is multiple of 128
if (tensor->type == GGML_TYPE_Q6_K) {
return threashold_ok && tensor->ne[1] % 128 == 0;
// The noshuffle layout packs 2 rows per 32-bit texel and the GEMV reads it at an
// ne1/2 texel stride with an exact-cover dispatch, so it is only addressable when
// ne1 is a multiple of 64; an unaligned ne1 truncates the stride and the weight is
// read misaligned. That is a property of the layout, not of one quant -- q4_K, q5_K
// and q8_0 read the same packing as q6_K. The bound is 64, not 128: a q8_0 attention
// weight of ne1 = 2880 is a multiple of 64 but not 128 and is correct.
switch (tensor->type) {
case GGML_TYPE_Q4_K:
case GGML_TYPE_Q5_K:
case GGML_TYPE_Q6_K:
case GGML_TYPE_Q8_0:
return threashold_ok && tensor->ne[1] % 64 == 0;
default:
break;
}
return threashold_ok;
}
+17 -1
View File
@@ -333,6 +333,7 @@ static void ggml_vk_print_device_lost_info(const vk_device& device);
struct vk_queue_handle {
vk::Queue queue;
vk_device_ref device;
std::mutex * device_submit_mutex = nullptr;
virtual void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) = 0;
virtual void lock() {} // no-op by default (internally synchronized case)
virtual void unlock() {}
@@ -342,6 +343,11 @@ struct vk_queue_handle {
struct vk_queue_handle_synchronized : vk_queue_handle {
std::mutex mutex;
void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) override {
// Workaround for NVIDIA driver bug
std::unique_lock<std::mutex> device_guard;
if (device_submit_mutex) {
device_guard = std::unique_lock<std::mutex>(*device_submit_mutex);
}
std::lock_guard<std::mutex> guard(mutex);
try {
queue.submit(submits, fence);
@@ -356,9 +362,14 @@ struct vk_queue_handle_synchronized : vk_queue_handle {
void unlock() override { mutex.unlock(); }
};
// Driver guarantees internal synchronization via VK_KHR_internally_synchronized_queues
struct vk_queue_handle_unsynchronized : vk_queue_handle {
void submit(vk::ArrayProxy<const vk::SubmitInfo> submits, vk::Fence fence) override {
// Driver guarantees internal synchronization via VK_KHR_internally_synchronized_queues
// Workaround for NVIDIA driver bug
std::unique_lock<std::mutex> device_guard;
if (device_submit_mutex) {
device_guard = std::unique_lock<std::mutex>(*device_submit_mutex);
}
try {
queue.submit(submits, fence);
} catch (vk::DeviceLostError &) {
@@ -835,6 +846,7 @@ static bool ggml_vk_lightning_indexer_k_type_supported(ggml_type type) {
struct vk_device_struct {
std::recursive_mutex mutex;
std::mutex queue_submit_mutex;
mutable std::shared_mutex pinned_memory_mutex;
// Guards compile_pending, all_pipelines, and the dynamic pipeline maps
@@ -3520,6 +3532,10 @@ static std::unique_ptr<vk_queue> ggml_vk_create_queue(vk_device& device, uint32_
h->queue = device->device.getQueue2(queue_info2);
h->device = device;
// Avoid concurrent submissions on NVIDIA due to driver bug.
if (device->vendor_id == VK_VENDOR_ID_NVIDIA) {
h->device_submit_mutex = &device->queue_submit_mutex;
}
q->handle = h;
q->cmd_pool.init(device, q.get());
+3 -3
View File
@@ -10678,9 +10678,9 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 16384, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
// MLA shape: the V cache is a sub-view of the K cache, with quantized KV
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 113, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 1024, 64, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {8, 1}, 113, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {8, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {8, 1}, 1024, 64, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, true, true));
// Sparse mask hint: supported decode/prefill layouts and dense fallbacks.
test_cases.emplace_back(new test_flash_attn_ext(512, 512, 1, { 8, 1}, 4096, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16, {0, 1, 2, 3}, true, false, 512));
+59
View File
@@ -846,6 +846,25 @@ static common_chat_tool nullable_int_tool{
})",
};
static common_chat_tool string_union_tool{
/* .name = */ "set_union",
/* .description = */ "Set values whose types are unions with string",
/* .parameters = */ R"({
"type": "object",
"properties": {
"value": {
"type": ["string", "object"],
"description": "A string or object value"
},
"amount": {
"type": ["string", "integer"],
"description": "A string or integer value"
}
},
"required": ["value", "amount"]
})",
};
static common_chat_tool enum_no_type_tool{
/* .name = */ "set_unit",
/* .description = */ "Set a temperature unit",
@@ -3805,6 +3824,46 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
})
.run();
// nullable string given null - parses as JSON null, not the string "null"
tst.test(
"<tool_call>\n"
"<function=set_nullable_str>\n"
"<parameter=name>\nnull\n</parameter>\n"
"</function>\n"
"</tool_call>")
.tools({ nullable_string_tool })
.expect_tool_calls({
{ "set_nullable_str", R"({"name": null})", {} },
})
.run();
// unions with string - JSON values of the other types are typed, everything else is a string
tst.test(
"<tool_call>\n"
"<function=set_union>\n"
"<parameter=value>\n{\"a\": 1}\n</parameter>\n"
"<parameter=amount>\n2 dollars\n</parameter>\n"
"</function>\n"
"</tool_call>")
.tools({ string_union_tool })
.expect_tool_calls({
{ "set_union", R"({"value": {"a": 1}, "amount": "2 dollars"})", {} },
})
.run();
tst.test(
"<tool_call>\n"
"<function=set_union>\n"
"<parameter=value>\n{not valid json\n</parameter>\n"
"<parameter=amount>\n42\n</parameter>\n"
"</function>\n"
"</tool_call>")
.tools({ string_union_tool })
.expect_tool_calls({
{ "set_union", R"({"value": "{not valid json", "amount": 42})", {} },
})
.run();
// enum without explicit type key - should infer string from enum values
tst.test(
"<tool_call>\n"