diff --git a/common/arg.cpp b/common/arg.cpp index 3c8505b13..1efbeb821 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -2,12 +2,12 @@ #include "chat.h" #include "common.h" +#include "download.h" #include "json-schema-to-grammar.h" #include "log.h" #include "sampling.h" #include "chat.h" #include "build-info.h" -#include "download.h" #include "preset.h" // fix problem with std::min and std::max @@ -50,6 +50,8 @@ #define LLAMA_MAX_URL_LENGTH 2084 // Maximum URL Length in Chrome: 2083 +extern const char * LICENSES[]; + using json = nlohmann::ordered_json; using namespace common_arg_utils; @@ -281,12 +283,20 @@ static std::string clean_file_name(const std::string & fname) { static bool common_params_handle_remote_preset(common_params & params, llama_example ex) { GGML_ASSERT(!params.model.hf_repo.empty()); + // the returned hf_repo is without tag + auto [hf_repo, hf_tag] = common_download_split_repo_tag(params.model.hf_repo); + + // "latest" tag (default if not specified) is translated to "default" preset + if (hf_tag == "latest") { + hf_tag = "default"; + } + const bool offline = params.offline; std::string model_endpoint = get_model_endpoint(); - auto preset_url = model_endpoint + params.model.hf_repo + "/resolve/main/preset.ini"; + auto preset_url = model_endpoint + hf_repo + "/resolve/main/preset.ini"; // prepare local path for caching - auto preset_fname = clean_file_name(params.model.hf_repo + "_preset.ini"); + auto preset_fname = clean_file_name(hf_repo + "_preset.ini"); auto preset_path = fs_get_cache_file(preset_fname); const int status = common_download_file_single(preset_url, preset_path, params.hf_token, offline); const bool has_preset = status >= 200 && status < 400; @@ -295,14 +305,15 @@ static bool common_params_handle_remote_preset(common_params & params, llama_exa if (has_preset) { LOG_INF("applying remote preset from %s\n", preset_url.c_str()); common_preset_context ctx(ex, /* only_remote_allowed */ true); - common_preset global; // unused for now + common_preset global; auto remote_presets = ctx.load_from_ini(preset_path, global); - if (remote_presets.find(COMMON_PRESET_DEFAULT_NAME) != remote_presets.end()) { - common_preset & preset = remote_presets.at(COMMON_PRESET_DEFAULT_NAME); + remote_presets = ctx.cascade(global, remote_presets); + if (remote_presets.find(hf_tag) != remote_presets.end()) { + common_preset preset = remote_presets.at(hf_tag); LOG_INF("\n%s", preset.to_ini().c_str()); // to_ini already added trailing newline preset.apply_to_params(params); } else { - throw std::runtime_error("Remote preset.ini does not contain [" + std::string(COMMON_PRESET_DEFAULT_NAME) + "] section"); + throw std::runtime_error("Remote preset.ini does not contain [" + std::string(hf_tag) + "] section"); } } else { LOG_INF("%s", "no remote preset found, skipping\n"); @@ -1032,6 +1043,16 @@ common_params_context common_params_parser_init(common_params & params, llama_ex exit(0); } )); + add_opt(common_arg( + {"--license"}, + "show source code license and dependencies", + [](common_params &) { + for (int i = 0; LICENSES[i]; ++i) { + printf("%s\n", LICENSES[i]); + } + exit(0); + } + )); add_opt(common_arg( {"-cl", "--cache-list"}, "show list of models in cache", @@ -1276,7 +1297,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex [](common_params & params) { params.kv_unified = true; } - ).set_env("LLAMA_ARG_KV_UNIFIED").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_PERPLEXITY})); + ).set_env("LLAMA_ARG_KV_UNIFIED").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_PERPLEXITY, LLAMA_EXAMPLE_BATCHED})); add_opt(common_arg( {"--context-shift"}, {"--no-context-shift"}, @@ -2858,10 +2879,18 @@ common_params_context common_params_parser_init(common_params & params, llama_ex params.n_threads_http = value; } ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_THREADS_HTTP")); + add_opt(common_arg( + {"--cache-prompt"}, + {"--no-cache-prompt"}, + string_format("whether to enable prompt caching (default: %s)", params.cache_prompt ? "enabled" : "disabled"), + [](common_params & params, bool value) { + params.cache_prompt = value; + } + ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_CACHE_PROMPT")); add_opt(common_arg( {"--cache-reuse"}, "N", string_format( - "min chunk size to attempt reusing from the cache via KV shifting (default: %d)\n" + "min chunk size to attempt reusing from the cache via KV shifting, requires prompt caching to be enabled (default: %d)\n" "[(card)](https://ggml.ai/f0.png)", params.n_cache_reuse ), [](common_params & params, int value) { diff --git a/common/common.h b/common/common.h index 16f9025a3..d716e1578 100644 --- a/common/common.h +++ b/common/common.h @@ -76,6 +76,7 @@ int32_t cpu_get_num_math(); // enum llama_example { + LLAMA_EXAMPLE_BATCHED, LLAMA_EXAMPLE_DEBUG, LLAMA_EXAMPLE_COMMON, LLAMA_EXAMPLE_SPECULATIVE, @@ -471,6 +472,7 @@ struct common_params { int32_t timeout_write = timeout_read; // http write timeout in seconds int32_t n_threads_http = -1; // number of threads to process HTTP requests (TODO: support threadpool) int32_t n_cache_reuse = 0; // min chunk size to reuse from the cache via KV shifting + bool cache_prompt = true; // whether to enable prompt caching int32_t n_ctx_checkpoints = 8; // max number of context checkpoints per slot int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc. diff --git a/common/download.cpp b/common/download.cpp index a1e0e518e..dc7d5c847 100644 --- a/common/download.cpp +++ b/common/download.cpp @@ -161,6 +161,16 @@ static bool is_http_status_ok(int status) { return status >= 200 && status < 400; } +std::pair common_download_split_repo_tag(const std::string & hf_repo_with_tag) { + auto parts = string_split(hf_repo_with_tag, ':'); + std::string tag = parts.size() > 1 ? parts.back() : "latest"; + std::string hf_repo = parts[0]; + if (string_split(hf_repo, '/').size() != 2) { + throw std::invalid_argument("error: invalid HF repo format, expected /[:quant]\n"); + } + return {hf_repo, tag}; +} + #ifdef LLAMA_USE_CURL // @@ -922,12 +932,8 @@ common_hf_file_res common_get_hf_file(const std::string & hf_repo_with_tag, const std::string & bearer_token, bool offline, const common_header_list & custom_headers) { - auto parts = string_split(hf_repo_with_tag, ':'); - std::string tag = parts.size() > 1 ? parts.back() : "latest"; - std::string hf_repo = parts[0]; - if (string_split(hf_repo, '/').size() != 2) { - throw std::invalid_argument("error: invalid HF repo format, expected /[:quant]\n"); - } + // the returned hf_repo is without tag + auto [hf_repo, tag] = common_download_split_repo_tag(hf_repo_with_tag); std::string url = get_model_endpoint() + "v2/" + hf_repo + "/manifests/" + tag; diff --git a/common/download.h b/common/download.h index c79be2f90..1c1d8e6db 100644 --- a/common/download.h +++ b/common/download.h @@ -17,6 +17,12 @@ struct common_remote_params { // get remote file content, returns std::pair> common_remote_get_content(const std::string & url, const common_remote_params & params); +// split HF repo with tag into +// for example: "user/model:tag" -> <"user/model", "tag"> +// if tag is not present, default to "latest" +// example: "user/model" -> <"user/model", "latest"> +std::pair common_download_split_repo_tag(const std::string & hf_repo_with_tag); + struct common_cached_model_info { std::string manifest_path; std::string user; diff --git a/common/preset.cpp b/common/preset.cpp index aec14e076..57ccd000b 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -32,8 +32,10 @@ static std::set get_remote_preset_whitelist(const std::map allowed_keys; @@ -318,6 +320,11 @@ common_presets common_preset_context::load_from_ini(const std::string & path, co } LOG_DBG("loading preset: %s\n", preset.name.c_str()); for (const auto & [key, value] : section.second) { + if (key == "version") { + // skip version key (reserved for future use) + continue; + } + LOG_DBG("option: %s = %s\n", key.c_str(), value.c_str()); if (filter_allowed_keys && allowed_keys.find(key) == allowed_keys.end()) { throw std::runtime_error(string_format( @@ -334,7 +341,10 @@ common_presets common_preset_context::load_from_ini(const std::string & path, co } LOG_DBG("accepted option: %s = %s\n", key.c_str(), preset.options[opt].c_str()); } else { - // TODO: maybe warn about unknown key? + throw std::runtime_error(string_format( + "option '%s' not recognized in preset '%s'", + key.c_str(), preset.name.c_str() + )); } } diff --git a/convert_hf_to_gguf.py b/convert_hf_to_gguf.py index ead180523..cc5e3691c 100755 --- a/convert_hf_to_gguf.py +++ b/convert_hf_to_gguf.py @@ -4367,7 +4367,37 @@ class Qwen3NextModel(Qwen2MoeModel): elif name.endswith("norm.weight") and not name.endswith("linear_attn.norm.weight"): data_torch = data_torch + 1 - yield from super().modify_tensors(data_torch, name, bid) + if "in_proj_qkvz.weight" in name: + # original order: [q, k, v, z] * head_count + # corrected order: [q * head_count, k * head_count, v * head_count, z * head_count] + head_k_dim = self.hparams["linear_key_head_dim"] + head_v_dim = self.hparams["linear_value_head_dim"] + num_v_heads = self.hparams["linear_num_value_heads"] + num_k_heads = self.hparams["linear_num_key_heads"] + hidden_size = self.hparams["hidden_size"] + split_arg_list_qkvz = [ + head_k_dim, # q partition + head_k_dim, # k partition + (num_v_heads // num_k_heads * head_v_dim), # v partition + (num_v_heads // num_k_heads * head_v_dim), # z partition + ] + # view as (n_embd, head_count, [q+k+v+z]) + data_torch = data_torch.permute(1, 0).contiguous() + data_torch = data_torch.view(-1, num_k_heads, sum(split_arg_list_qkvz)) + # split into q, k, v, z + q, k, v, z = torch.split(data_torch, split_arg_list_qkvz, dim=-1) + # flatten dim + head_count + q = q.contiguous().view(hidden_size, -1) + k = k.contiguous().view(hidden_size, -1) + v = v.contiguous().view(hidden_size, -1) + z = z.contiguous().view(hidden_size, -1) + # stack back + qkv = torch.cat([q, k, v], dim=-1).permute(1, 0).contiguous() + z = z.permute(1, 0).contiguous() + yield (self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_QKV, bid, ".weight"), qkv) + yield (self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_GATE, bid, ".weight"), z) + else: + yield from super().modify_tensors(data_torch, name, bid) @ModelBase.register("RND1") diff --git a/ggml/src/ggml-blas/ggml-blas.cpp b/ggml/src/ggml-blas/ggml-blas.cpp index 5b888cdd8..84956cbb9 100644 --- a/ggml/src/ggml-blas/ggml-blas.cpp +++ b/ggml/src/ggml-blas/ggml-blas.cpp @@ -115,15 +115,11 @@ static void ggml_backend_blas_mul_mat(ggml_backend_blas_context * ctx, struct gg #endif } -#if defined(OPENBLAS_VERSION) +#if defined(GGML_BLAS_USE_OPENBLAS) openblas_set_num_threads(ctx->n_threads); -#endif - -#if defined(GGML_BLAS_USE_BLIS) +#elif defined(GGML_BLAS_USE_BLIS) bli_thread_set_num_threads(ctx->n_threads); -#endif - -#if defined(GGML_BLAS_USE_NVPL) +#elif defined(GGML_BLAS_USE_NVPL) nvpl_blas_set_num_threads(ctx->n_threads); #endif @@ -288,7 +284,7 @@ ggml_backend_t ggml_backend_blas_init(void) { /* .context = */ ctx, }; -#if defined(OPENBLAS_VERSION) && defined(GGML_USE_OPENMP) +#if defined(GGML_BLAS_USE_OPENBLAS) && defined(GGML_USE_OPENMP) if (openblas_get_parallel() != OPENBLAS_OPENMP) { GGML_LOG_DEBUG("%s: warning: ggml is using OpenMP, but OpenBLAS was compiled without OpenMP support\n", __func__); } @@ -329,7 +325,7 @@ static const char * ggml_backend_blas_device_get_description(ggml_backend_dev_t return "BLIS"; #elif defined(GGML_BLAS_USE_NVPL) return "NVPL"; - #elif defined(OPENBLAS_VERSION) + #elif defined(GGML_BLAS_USE_OPENBLAS) return "OpenBLAS"; #else return "BLAS"; diff --git a/ggml/src/ggml-cuda/ggml-cuda.cu b/ggml/src/ggml-cuda/ggml-cuda.cu index 408b81c4f..7d0514235 100644 --- a/ggml/src/ggml-cuda/ggml-cuda.cu +++ b/ggml/src/ggml-cuda/ggml-cuda.cu @@ -3749,6 +3749,7 @@ static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx) { return cuda_ctx->cuda_graph->is_enabled(); #else + GGML_UNUSED(cuda_ctx); return false; #endif // USE_CUDA_GRAPH } diff --git a/ggml/src/ggml-cuda/mmq.cu b/ggml/src/ggml-cuda/mmq.cu index a3f07fd2c..f42650c2d 100644 --- a/ggml/src/ggml-cuda/mmq.cu +++ b/ggml/src/ggml-cuda/mmq.cu @@ -190,7 +190,7 @@ void ggml_cuda_mul_mat_q( { const int64_t s11 = src1->nb[1] / ts_src1; const int64_t s12 = src1->nb[2] / ts_src1; - const int64_t s13 = src1->nb[2] / ts_src1; + const int64_t s13 = src1->nb[3] / ts_src1; if (use_native_mxfp4) { quantize_mmq_mxfp4_cuda(src1_d, ids_src1.get(), src1_q8_1.get(), src0->type, ne10, s11, s12, s13, @@ -335,28 +335,31 @@ bool ggml_cuda_should_use_mmq(enum ggml_type type, int cc, int64_t ne11, int64_t } if (amd_wmma_available(cc)) { - // RDNA 4 is consistently worse on rocblas - // https://github.com/ggml-org/llama.cpp/pull/18537#issuecomment-3706422301 if (GGML_CUDA_CC_IS_RDNA3(cc)) { - // High expert counts almost always better on MMQ - // due to a large amount of graph splits + // High expert counts are almost always better on MMQ due to + // the synchronization overhead in the cuBLAS/hipBLAS path: // https://github.com/ggml-org/llama.cpp/pull/18202 if (n_experts >= 64) { return true; } + // For some quantization types MMQ can have lower peak TOPS than hipBLAS + // so it's only faster for sufficiently small batch sizes: switch (type) { - // These quants are really bad on MMQ case GGML_TYPE_Q2_K: + return ne11 <= 128; case GGML_TYPE_Q6_K: - // These quants are usually worse but not always + return ne11 <= (GGML_CUDA_CC_IS_RDNA3_0(cc) ? 128 : 256); case GGML_TYPE_IQ2_XS: case GGML_TYPE_IQ2_S: - return ne11 <= 128; + return GGML_CUDA_CC_IS_RDNA3_5(cc) || ne11 <= 128; default: return true; } } + + // For RDNA4 MMQ is consistently faster than dequantization + hipBLAS: + // https://github.com/ggml-org/llama.cpp/pull/18537#issuecomment-3706422301 return true; } diff --git a/ggml/src/ggml-vulkan/ggml-vulkan.cpp b/ggml/src/ggml-vulkan/ggml-vulkan.cpp index 22d33865e..c548a972e 100644 --- a/ggml/src/ggml-vulkan/ggml-vulkan.cpp +++ b/ggml/src/ggml-vulkan/ggml-vulkan.cpp @@ -135,6 +135,8 @@ struct ggml_backend_vk_context; // Max number of adds that can be fused without exceeding MAX_PARAMETER_COUNT. #define MAX_FUSED_ADDS (MAX_PARAMETER_COUNT - 3) +typedef std::shared_ptr vk_pipeline; + struct vk_pipeline_struct { std::string name; vk::ShaderModule shader_module; @@ -152,9 +154,15 @@ struct vk_pipeline_struct { std::atomic compiled {}; // number of registers used, extracted from pipeline executable properties uint32_t register_count {}; + +#if defined(VK_EXT_shader_64bit_indexing) + bool is_64b_indexing {}; +#endif + // linked list of pipelines for multiple compilation variants. + // currently only used to compile a 64-bit indexing variant. + vk_pipeline next; }; -typedef std::shared_ptr vk_pipeline; typedef std::weak_ptr vk_pipeline_ref; static void ggml_vk_destroy_pipeline(vk::Device& device, vk_pipeline& pipeline); @@ -246,9 +254,7 @@ static ggml_backend_buffer_type_i ggml_backend_vk_buffer_type_interface = { /* .is_host = */ NULL, }; -#ifdef GGML_VULKAN_MEMORY_DEBUG class vk_memory_logger; -#endif class vk_perf_logger; static void ggml_vk_destroy_buffer(vk_buffer& buf); static void ggml_vk_synchronize(ggml_backend_vk_context * ctx); @@ -600,6 +606,8 @@ struct vk_device_struct { bool add_rms_fusion; uint32_t partials_binding_alignment; + bool shader_64b_indexing; + bool integer_dot_product; // 0: default, 1: force mmvq, -1: disable mmvq int32_t mmvq_mode; @@ -831,9 +839,7 @@ struct vk_device_struct { bool allow_sysmem_fallback; bool disable_graph_optimize; -#ifdef GGML_VULKAN_MEMORY_DEBUG std::unique_ptr memory_logger; -#endif ~vk_device_struct() { VK_LOG_DEBUG("destroy device " << name); @@ -1569,8 +1575,9 @@ static void ggml_vk_preallocate_buffers(ggml_backend_vk_context * ctx, vk_contex static void ggml_vk_load_shaders(vk_device& device); static void ggml_pipeline_allocate_descriptor_sets(ggml_backend_vk_context * ctx); -#if defined(GGML_VULKAN_MEMORY_DEBUG) || defined(GGML_VULKAN_DEBUG) -#define VK_LOG_MEMORY(msg) std::cerr << "ggml_vulkan memory: " << msg << std::endl +static bool vk_memory_logger_enabled = false; + +#define VK_LOG_MEMORY(msg) if (vk_memory_logger_enabled) { std::cerr << "ggml_vulkan memory: " << msg << std::endl; } static std::string format_size(size_t size) { const size_t kib = 1024; @@ -1603,10 +1610,10 @@ private: std::map allocations; // Track allocations size_t total_device; size_t total_host; + static std::mutex log_mutex; }; -#else -#define VK_LOG_MEMORY(msg) ((void) 0) -#endif // GGML_VULKAN_MEMORY_DEBUG + +std::mutex vk_memory_logger::log_mutex; static bool vk_perf_logger_enabled = false; static bool vk_perf_logger_concurrent = false; @@ -1913,10 +1920,10 @@ struct ggml_backend_vk_buffer_context { } }; -#ifdef GGML_VULKAN_MEMORY_DEBUG -static std::mutex log_mutex; - void vk_memory_logger::log_allocation(vk_buffer_ref buf_ref, size_t size) { + if (!vk_memory_logger_enabled) { + return; + } std::lock_guard guard(log_mutex); vk_buffer buf = buf_ref.lock(); const bool device = bool(buf->memory_property_flags & vk::MemoryPropertyFlagBits::eDeviceLocal); @@ -1928,7 +1935,7 @@ void vk_memory_logger::log_allocation(vk_buffer_ref buf_ref, size_t size) { } void vk_memory_logger::log_deallocation(vk_buffer_ref buf_ref) { - if (buf_ref.expired() || buf_ref.lock()->size == 0) { + if (buf_ref.expired() || buf_ref.lock()->size == 0 || !vk_memory_logger_enabled) { return; } @@ -1946,7 +1953,6 @@ void vk_memory_logger::log_deallocation(vk_buffer_ref buf_ref) { VK_LOG_MEMORY("ERROR " << buf->device->name << ": Attempted to deallocate unknown " << type << " memory at " << buf->buffer); } } -#endif // GGML_VULKAN_MEMORY_DEBUG struct vk_instance_t { vk::Instance instance; @@ -2096,6 +2102,19 @@ static void ggml_vk_create_pipeline_func(vk_device& device, vk_pipeline& pipelin compute_pipeline_create_info.setPNext(&rci); } +#if defined(VK_EXT_shader_64bit_indexing) + vk::PipelineCreateFlags2CreateInfo pipelineFlags2CreateInfo; + if (pipeline->is_64b_indexing) + { + pipelineFlags2CreateInfo.flags = vk::PipelineCreateFlagBits2::e64BitIndexingEXT; + if (device->pipeline_executable_properties_support) { + pipelineFlags2CreateInfo.flags |= vk::PipelineCreateFlagBits2::eCaptureStatisticsKHR; + } + pipelineFlags2CreateInfo.setPNext(compute_pipeline_create_info.pNext); + compute_pipeline_create_info.setPNext(&pipelineFlags2CreateInfo); + } +#endif + try { pipeline->pipeline = device->device.createComputePipeline(VK_NULL_HANDLE, compute_pipeline_create_info).value; } catch (const vk::SystemError& e) { @@ -2586,9 +2605,7 @@ static vk_buffer ggml_vk_create_buffer(vk_device& device, size_t size, const std buf->bda_addr = device->device.getBufferAddress(addressInfo); } -#ifdef GGML_VULKAN_MEMORY_DEBUG device->memory_logger->log_allocation(buf, size); -#endif return buf; } @@ -2645,11 +2662,9 @@ static void ggml_vk_destroy_buffer(vk_buffer& buf) { return; } -#ifdef GGML_VULKAN_MEMORY_DEBUG if (buf->device != nullptr) { buf->device->memory_logger->log_deallocation(buf); } -#endif buf.reset(); } @@ -3018,6 +3033,11 @@ static void ggml_vk_load_shaders(vk_device& device) { if ((device->architecture == AMD_GCN) && (device->driver_id != vk::DriverId::eAmdProprietary)) { m_warptile_mmq = m_warptile_mmq_int = { 256, 64, 64, 32, 16, 16, 2, 2, 2, 1, 16 }; m_warptile_mmqid = m_warptile_mmqid_int = { 256, 64, 64, 32, 16, 16, 2, 2, 2, 1, 16 }; + } else if (device->vendor_id == VK_VENDOR_ID_AMD && device->coopmat_support && device->driver_id != vk::DriverId::eAmdProprietary) { + // This is intentionally using tx_m values, slight performance increase + l_warptile = { 256, 128, 128, 16, subgroup_size_8, 64, 2, tm_m, tn_m, tk_m, subgroup_size_8 }; + l_warptile_mmq = l_warptile_mmq_int = { 256, 128, 128, 32, subgroup_size_8, 64, 2, tm_m, tn_m, tk_m, subgroup_size_8 }; + l_warptile_mmq_int_k = { 256, 128, 128, 32, subgroup_size_16, 64, 1, 4, 2, 1, subgroup_size_16 }; } else if (device->vendor_id == VK_VENDOR_ID_INTEL && device->coopmat_support && device->architecture == INTEL_XE2) { // Xe2/Xe3 with coopmat enabled - warptile performance tuning l_warptile = { 512, 128, 128, 16, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 }; @@ -3077,7 +3097,7 @@ static void ggml_vk_load_shaders(vk_device& device) { } std::vector> compiles; - auto const &ggml_vk_create_pipeline = [&](vk_device& device, vk_pipeline& pipeline, const char *name, size_t spv_size, const void* spv_data, const char *entrypoint, + auto const &ggml_vk_create_pipeline = [&](vk_device& device, vk_pipeline& base_pipeline, const char *name, size_t spv_size, const void* spv_data, const char *entrypoint, uint32_t parameter_count, uint32_t push_constant_size, std::array wg_denoms, const std::vector& specialization_constants, uint32_t align, bool disable_robustness = false, bool require_full_subgroups = false, uint32_t required_subgroup_size = 0) { @@ -3085,35 +3105,49 @@ static void ggml_vk_load_shaders(vk_device& device) { required_subgroup_size = get_subgroup_size(name, device->architecture); } - if (!pipeline) { - pipeline = std::make_shared(); - } - if (!pipeline->initialized) { - pipeline->name = name; - pipeline->parameter_count = parameter_count; - pipeline->push_constant_size = push_constant_size; - pipeline->wg_denoms = wg_denoms; - pipeline->align = align; - pipeline->initialized = true; - } + vk_pipeline *ptr = &base_pipeline; - if (!pipeline->needed || pipeline->compiled) { - return; + int num_pipelines = 1; +#if defined(VK_EXT_shader_64bit_indexing) + if (device->shader_64b_indexing) { + num_pipelines = 2; } - // TODO: We're no longer benefitting from the async compiles (shaders are - // compiled individually, as needed) and this complexity can be removed. - { - // wait until fewer than N compiles are in progress - uint32_t N = std::max(1u, std::thread::hardware_concurrency()); - std::unique_lock guard(compile_count_mutex); - while (compile_count >= N) { - compile_count_cond.wait(guard); +#endif + for (int i = 0; i < num_pipelines; ++i, ptr = &(*ptr)->next) { + vk_pipeline &pipeline = *ptr; + if (!pipeline) { + pipeline = std::make_shared(); + } + if (!pipeline->initialized) { + pipeline->name = name; + pipeline->parameter_count = parameter_count; + pipeline->push_constant_size = push_constant_size; + pipeline->wg_denoms = wg_denoms; + pipeline->align = align; + pipeline->initialized = true; +#if defined(VK_EXT_shader_64bit_indexing) + pipeline->is_64b_indexing = (i == 1); +#endif } - compile_count++; - } - compiles.push_back(std::async(ggml_vk_create_pipeline_func, std::ref(device), std::ref(pipeline), spv_size, spv_data, entrypoint, - parameter_count, wg_denoms, specialization_constants, disable_robustness, require_full_subgroups, required_subgroup_size)); + if (!pipeline->needed || pipeline->compiled) { + continue; + } + // TODO: We're no longer benefitting from the async compiles (shaders are + // compiled individually, as needed) and this complexity can be removed. + { + // wait until fewer than N compiles are in progress + uint32_t N = std::max(1u, std::thread::hardware_concurrency()); + std::unique_lock guard(compile_count_mutex); + while (compile_count >= N) { + compile_count_cond.wait(guard); + } + compile_count++; + } + + compiles.push_back(std::async(ggml_vk_create_pipeline_func, std::ref(device), std::ref(pipeline), spv_size, spv_data, entrypoint, + parameter_count, wg_denoms, specialization_constants, disable_robustness, require_full_subgroups, required_subgroup_size)); + } }; auto const &ggml_vk_create_pipeline2 = [&](vk_device& device, vk_pipeline& pipeline, const std::string &name, size_t spv_size, const void* spv_data, const char *entrypoint, @@ -4451,9 +4485,7 @@ static vk_device ggml_vk_get_device(size_t idx) { vk_device device = std::make_shared(); vk_instance.devices[idx] = device; -#ifdef GGML_VULKAN_MEMORY_DEBUG device->memory_logger = std::unique_ptr(new vk_memory_logger()); -#endif size_t dev_num = vk_instance.device_indices[idx]; @@ -4497,6 +4529,7 @@ static vk_device ggml_vk_get_device(size_t idx) { bool pipeline_executable_properties_support = false; device->coopmat_support = false; device->integer_dot_product = false; + device->shader_64b_indexing = false; bool bfloat16_support = false; for (const auto& properties : ext_props) { @@ -4544,6 +4577,10 @@ static vk_device ggml_vk_get_device(size_t idx) { device->memory_priority = true; } else if (strcmp("VK_EXT_external_memory_host", properties.extensionName) == 0) { device->external_memory_host = true; +#if defined(VK_EXT_shader_64bit_indexing) + } else if (strcmp("VK_EXT_shader_64bit_indexing", properties.extensionName) == 0) { + device->shader_64b_indexing = true; +#endif } } @@ -4842,6 +4879,16 @@ static vk_device ggml_vk_get_device(size_t idx) { device_extensions.push_back("VK_EXT_external_memory_host"); } +#if defined(VK_EXT_shader_64bit_indexing) + VkPhysicalDeviceShader64BitIndexingFeaturesEXT shader_64bit_indexing_features {}; + shader_64bit_indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_64_BIT_INDEXING_FEATURES_EXT; + if (device->shader_64b_indexing) { + last_struct->pNext = (VkBaseOutStructure *)&shader_64bit_indexing_features; + last_struct = (VkBaseOutStructure *)&shader_64bit_indexing_features; + device_extensions.push_back("VK_EXT_shader_64bit_indexing"); + } +#endif + vkGetPhysicalDeviceFeatures2(device->physical_device, &device_features2); device->pipeline_executable_properties_support = pipeline_executable_properties_support; @@ -5108,7 +5155,7 @@ static vk_device ggml_vk_get_device(size_t idx) { switch (device->vendor_id) { #ifndef GGML_VULKAN_RUN_TESTS case VK_VENDOR_ID_AMD: - device->mul_mat_l[i] = false; + device->mul_mat_l[i] = device->coopmat_support && device->driver_id != vk::DriverId::eAmdProprietary; device->mul_mat_m[i] = true; device->mul_mat_s[i] = true; device->mul_mat_id_l[i] = false; @@ -5449,6 +5496,7 @@ static void ggml_vk_instance_init() { vk_perf_logger_enabled = getenv("GGML_VK_PERF_LOGGER") != nullptr; vk_perf_logger_concurrent = getenv("GGML_VK_PERF_LOGGER_CONCURRENT") != nullptr; vk_enable_sync_logger = getenv("GGML_VK_SYNC_LOGGER") != nullptr; + vk_memory_logger_enabled = getenv("GGML_VK_MEMORY_LOGGER") != nullptr; const char* GGML_VK_PERF_LOGGER_FREQUENCY = getenv("GGML_VK_PERF_LOGGER_FREQUENCY"); if (GGML_VK_PERF_LOGGER_FREQUENCY != nullptr) { @@ -6935,6 +6983,20 @@ static void ggml_vk_quantize_q8_1(ggml_backend_vk_context * ctx, vk_context& sub ggml_vk_sync_buffers(ctx, subctx); } +static vk_pipeline ggml_vk_get_64b_indexing_pipeline(ggml_backend_vk_context * ctx, vk_pipeline &pipeline) { + GGML_UNUSED(ctx); +#if defined(VK_EXT_shader_64bit_indexing) + vk_pipeline *ptr = &pipeline; + while (*ptr) { + if ((*ptr)->is_64b_indexing) { + return *ptr; + } + ptr = &(*ptr)->next; + } +#endif + return pipeline; +} + static void ggml_vk_mul_mat_q_f16(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, bool disable_split_k) { VK_LOG_DEBUG("ggml_vk_mul_mat_q_f16((" << src0 << ", name=" << src0->name << ", type=" << ggml_type_name(src0->type) << ", ne0=" << src0->ne[0] << ", ne1=" << src0->ne[1] << ", ne2=" << src0->ne[2] << ", ne3=" << src0->ne[3] << ", nb0=" << src0->nb[0] << ", nb1=" << src0->nb[1] << ", nb2=" << src0->nb[2] << ", nb3=" << src0->nb[3]; std::cerr << "), (" << src1 << ", name=" << src1->name << ", type=" << ggml_type_name(src1->type) << ", ne0=" << src1->ne[0] << ", ne1=" << src1->ne[1] << ", ne2=" << src1->ne[2] << ", ne3=" << src1->ne[3] << ", nb0=" << src1->nb[0] << ", nb1=" << src1->nb[1] << ", nb2=" << src1->nb[2] << ", nb3=" << src1->nb[3]; @@ -7018,6 +7080,10 @@ static void ggml_vk_mul_mat_q_f16(ggml_backend_vk_context * ctx, vk_context& sub vk_pipeline pipeline = ggml_vk_guess_matmul_pipeline(ctx, mmp, ne01, ne11, aligned, qx_needs_dequant ? f16_type : src0->type, quantize_y ? GGML_TYPE_Q8_1 : (y_f32_kernel ? GGML_TYPE_F32 : src1->type)); + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + pipeline = ggml_vk_get_64b_indexing_pipeline(ctx, pipeline); + } + // Reserve extra storage in the N dimension for the Y matrix, so we can avoid bounds-checking uint32_t padded_n = qy_needs_dequant ? ROUNDUP_POW2(ne11, pipeline->wg_denoms[1]) : ne11; const uint64_t x_ne = ggml_nelements(src0); @@ -7327,6 +7393,10 @@ static void ggml_vk_mul_mat_vec_q_f16(ggml_backend_vk_context * ctx, vk_context& to_q8_1 = ggml_vk_get_quantize_pipeline(ctx, GGML_TYPE_Q8_1); } + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + dmmv = ggml_vk_get_64b_indexing_pipeline(ctx, dmmv); + } + const bool qx_needs_dequant = x_non_contig; const bool qy_needs_dequant = !quantize_y && ((src1->type != GGML_TYPE_F16 && !f16_f32_kernel) || y_non_contig); @@ -7522,9 +7592,15 @@ static void ggml_vk_mul_mat_vec_p021_f16_f32(ggml_backend_vk_context * ctx, vk_c gqa_ratio = 1; } + vk_pipeline pipeline = ctx->device->pipeline_mul_mat_vec_p021_f16_f32[gqa_ratio - 1]; + + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + pipeline = ggml_vk_get_64b_indexing_pipeline(ctx, pipeline); + } + { // Request descriptor sets - ggml_pipeline_request_descriptor_sets(ctx, ctx->device->pipeline_mul_mat_vec_p021_f16_f32[gqa_ratio - 1], 1); + ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1); } vk_subbuffer d_D = ggml_vk_tensor_subbuffer(ctx, cgraph->nodes[node_idx + ctx->num_additional_fused_ops], true); @@ -7566,7 +7642,7 @@ static void ggml_vk_mul_mat_vec_p021_f16_f32(ggml_backend_vk_context * ctx, vk_c workgroups_z /= gqa_ratio; } - ggml_vk_dispatch_pipeline(ctx, subctx, ctx->device->pipeline_mul_mat_vec_p021_f16_f32[gqa_ratio - 1], + ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { d_Qx, d_Qy, @@ -7616,9 +7692,14 @@ static void ggml_vk_mul_mat_vec_nc_f16_f32(ggml_backend_vk_context * ctx, vk_con const uint32_t channel_stride_x = nb02 / sizeof(ggml_fp16_t); const uint32_t channel_stride_y = nb12 / sizeof(float); + vk_pipeline pipeline = ctx->device->pipeline_mul_mat_vec_nc_f16_f32; + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + pipeline = ggml_vk_get_64b_indexing_pipeline(ctx, pipeline); + } + { // Request descriptor sets - ggml_pipeline_request_descriptor_sets(ctx, ctx->device->pipeline_mul_mat_vec_nc_f16_f32, 1); + ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1); } vk_subbuffer d_D = ggml_vk_tensor_subbuffer(ctx, cgraph->nodes[node_idx + ctx->num_additional_fused_ops], true); @@ -7655,7 +7736,7 @@ static void ggml_vk_mul_mat_vec_nc_f16_f32(ggml_backend_vk_context * ctx, vk_con init_pushconst_tensor_offsets(ctx, pc, src0, src1, nullptr, nullptr, cgraph->nodes[node_idx + ctx->num_additional_fused_ops]); - ggml_vk_dispatch_pipeline(ctx, subctx, ctx->device->pipeline_mul_mat_vec_nc_f16_f32, + ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { d_Qx, d_Qy, @@ -7674,8 +7755,9 @@ static void ggml_vk_mul_mat(ggml_backend_vk_context * ctx, vk_context& subctx, c // Handle huge A matrix by splitting the M dimensions. This works well for convolution use cases // where the M dimension is very large. // Split_k doesn't work with M splitting. + // This only supports batchsize == 1. const size_t nbytes = ggml_nbytes(src0); - const bool needs_split = nbytes > ctx->device->properties.limits.maxStorageBufferRange; + const bool needs_split = dst->ne[2] == 1 && dst->ne[3] == 1 && nbytes > ctx->device->properties.limits.maxStorageBufferRange; if (needs_split) { // Choose the number of rows that can fit (and divide by two, to allow for any additional offsets) const uint32_t M_split = ctx->device->properties.limits.maxStorageBufferRange / (2 * src0->nb[1]); @@ -7817,6 +7899,9 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context& vk_pipeline pipeline = ggml_vk_guess_matmul_id_pipeline(ctx, mmp, ne01, nei1, aligned, qx_needs_dequant ? f16_type : src0->type); + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + pipeline = ggml_vk_get_64b_indexing_pipeline(ctx, pipeline); + } // Reserve extra storage in the N dimension for the Y matrix, so we can avoid bounds-checking uint32_t padded_n = qy_needs_dequant ? ROUNDUP_POW2(ne11, pipeline->wg_denoms[1]) :ne11; const uint64_t x_ne = ggml_nelements(src0); @@ -8078,6 +8163,10 @@ static void ggml_vk_mul_mat_vec_id_q_f16(ggml_backend_vk_context * ctx, vk_conte const bool qx_needs_dequant = x_non_contig; const bool qy_needs_dequant = !quantize_y && ((src1->type != GGML_TYPE_F16 && !f16_f32_kernel) || y_non_contig); + if (ggml_nbytes(src0) > ctx->device->properties.limits.maxStorageBufferRange) { + dmmv = ggml_vk_get_64b_indexing_pipeline(ctx, dmmv); + } + // Not implemented GGML_ASSERT(y_non_contig || !qy_needs_dequant); // NOLINT GGML_ASSERT(!qx_needs_dequant || to_fp16_vk_0 != nullptr); // NOLINT diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec.comp index b3c96576d..2271be402 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec.comp @@ -87,7 +87,6 @@ void compute_outputs(const uint32_t first_row, const uint32_t num_rows) { const uint tid = gl_LocalInvocationID.x; get_offsets(a_offset, b_offset, d_offset); - a_offset /= QUANT_K; y_offset = QUANT_R == 1 ? 1 : QUANT_K/2; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_base.glsl b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_base.glsl index cfc8b0c7f..dfb786593 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_base.glsl +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_base.glsl @@ -65,9 +65,9 @@ void get_offsets(out uint a_offset, out uint b_offset, out uint d_offset) { a_offset = #ifdef MUL_MAT_ID - expert_id * p.batch_stride_a; + expert_id * (p.batch_stride_a / QUANT_K); #else - batch_idx_a * p.batch_stride_a; + batch_idx_a * (p.batch_stride_a / QUANT_K); #endif b_offset = #ifdef MUL_MAT_ID diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_m.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_m.comp index e5cc7ff86..3ea24a76c 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_m.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_m.comp @@ -11,7 +11,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint ib32, const uint num_blocks_per_row, const uint first_row, const uint num_rows) { // Compute starting index in matrix B for this superblock const uint y_idx = i * QUANT_K + 32 * ib32; - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; // Precompute indices for quantization lookup tables const uint qh_base = 2 * ib32; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_s.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_s.comp index c5f5e9cbb..fd953c8fa 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_s.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq1_s.comp @@ -17,7 +17,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint ib32, const vec4 b_val_1 = vec4(data_b_v4[base_b_idx + 2 * l + 1]); // index for data_a - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { const float d = float(data_a[ibi].d); diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_s.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_s.comp index e424af12c..b4f6d1d6b 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_s.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_s.comp @@ -12,7 +12,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint nibble_shift = 4 * (itid & 1); const uint ib32 = itid / 2; // 0..7 - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { const float d = float(data_a[ibi].d); const uint scale = (data_a[ibi].scales[ib32] >> nibble_shift) & 0xF; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xs.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xs.comp index 7ec2e04f5..d8dafe5f7 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xs.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xs.comp @@ -11,7 +11,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint y_idx = i * QUANT_K + 16 * itid; const uint nibble_shift = 4 * (itid & 1); const uint ib32 = itid / 2; // 0..7 - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; // Precompute db multiplication factors float db_vals[NUM_ROWS]; [[unroll]] for (uint n = 0; n < num_rows; ++n) { @@ -22,7 +22,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, db_vals[n] = d * (0.125f + float(scale) * 0.25f); ibi += num_blocks_per_row; } - ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { // Preload grid and sign data for all l values vec4 grid0_vals[2], grid1_vals[2]; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xxs.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xxs.comp index 71bd72d17..f75dcf833 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xxs.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq2_xxs.comp @@ -11,7 +11,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint y_idx = i * QUANT_K + 16 * itid; const uint ib32 = itid / 2; // 0..7 - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { const float d = float(data_a[ibi].d); const uint signscale = pack32(u16vec2( diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_s.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_s.comp index a4b9ab1f9..5cdf2a89d 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_s.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_s.comp @@ -10,7 +10,7 @@ FLOAT_TYPE temp[NUM_COLS][NUM_ROWS]; void calc_superblock(const uint a_offset, const uint b_offset, const uint ib32, const uint i, const uint num_blocks_per_row, const uint first_row, const uint num_rows) { const uint y_idx = i * QUANT_K + 32 * ib32; - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { const float d = float(data_a[ibi].d); const uint scale = (data_a[ibi].scales[ib32/2] >> (4 * (ib32 & 1))) & 0xF; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_xxs.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_xxs.comp index 40849c691..a88898109 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_xxs.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_iq3_xxs.comp @@ -11,7 +11,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint y_idx = i * QUANT_K + 16 * itid; const uint ib32 = itid / 2; // 0..7 - uint ibi = a_offset / QUANT_K + first_row * num_blocks_per_row + i; + uint ibi = a_offset + first_row * num_blocks_per_row + i; [[unroll]] for (uint n = 0; n < num_rows; ++n) { const float d = float(data_a[ibi].d); const uint signscale = pack32(u16vec2( diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q2_k.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q2_k.comp index 14093c0de..619de054c 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q2_k.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q2_k.comp @@ -15,7 +15,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint y_idx = i * QUANT_K + y_offset; [[unroll]] for (uint n = 0; n < num_rows; ++n) { - const uint ib0 = a_offset / QUANT_K + (first_row+n)*num_blocks_per_row; + const uint ib0 = a_offset + (first_row+n)*num_blocks_per_row; csel ^= 1; if (!all_threads) { // when we don't have enough blocks to use all threads diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q3_k.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q3_k.comp index 528f224d8..93e48b790 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q3_k.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q3_k.comp @@ -14,7 +14,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint ix, co const uint y_idx = i * QUANT_K + y_offset; [[unroll]] for (uint n = 0; n < num_rows; ++n) { - const uint ib0 = a_offset / QUANT_K + (first_row+n)*num_blocks_per_row; + const uint ib0 = a_offset + (first_row+n)*num_blocks_per_row; csel ^= 1; if (!all_threads) { // when we don't have enough blocks to use all threads diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q4_k.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q4_k.comp index 49d91ad59..6af5a8158 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q4_k.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q4_k.comp @@ -13,7 +13,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint v_im, const uint y2_idx = y1_idx + 128; [[unroll]] for (uint n = 0; n < num_rows; ++n) { - const uint ib0 = a_offset / QUANT_K + (first_row+n)*num_blocks_per_row; + const uint ib0 = a_offset + (first_row+n)*num_blocks_per_row; const FLOAT_TYPE_VEC2 dm = FLOAT_TYPE_VEC2(data_a[ib0 + i].dm); const uint32_t scale0_u32 = data_a_packed16[ib0 + i].scales[v_im ]; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q5_k.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q5_k.comp index 0d61b4966..3695b47b9 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q5_k.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q5_k.comp @@ -13,7 +13,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint v_im, const uint y2_idx = y1_idx + 128; [[unroll]] for (uint n = 0; n < num_rows; ++n) { - const uint ib0 = a_offset / QUANT_K + (first_row+n)*num_blocks_per_row; + const uint ib0 = a_offset + (first_row+n)*num_blocks_per_row; const FLOAT_TYPE_VEC2 dm = FLOAT_TYPE_VEC2(data_a[ib0 + i].dm); const uint32_t scale0_u32 = data_a_packed16[ib0 + i].scales[v_im ]; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q6_k.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q6_k.comp index d7a7f6426..3e89d91cb 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q6_k.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vec_q6_k.comp @@ -15,7 +15,7 @@ void calc_superblock(const uint a_offset, const uint b_offset, const uint itid, const uint y_idx = i * QUANT_K + y_offset; [[unroll]] for (uint n = 0; n < num_rows; ++n) { - const uint ib0 = a_offset / QUANT_K + (first_row+n)*num_blocks_per_row; + const uint ib0 = a_offset + (first_row+n)*num_blocks_per_row; csel ^= 1; if (!all_threads) { // when we don't have enough blocks to use all threads diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vecq.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vecq.comp index ff5f43979..6fe3e2dc0 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vecq.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vecq.comp @@ -79,7 +79,7 @@ void compute_outputs(const uint32_t first_row, const uint32_t num_rows) { const uint tid = gl_LocalInvocationID.x; get_offsets(a_offset, b_offset, d_offset); - a_offset /= QUANT_K_Q8_1; + a_offset *= QUANT_K / QUANT_K_Q8_1; b_offset /= QUANT_K_Q8_1; FLOAT_TYPE temp[NUM_COLS][NUM_ROWS]; diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm.comp index c0c00d28f..775e9a70f 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm.comp @@ -234,13 +234,13 @@ void main() { const uint end_k = min(p.K, (ik + 1) * p.k_split); #endif - uint pos_a = ( + uint pos_a = #ifdef MUL_MAT_ID - expert_idx * p.batch_stride_a + + expert_idx * (p.batch_stride_a / LOAD_VEC_A) + #else - batch_idx_a * p.batch_stride_a + + batch_idx_a * (p.batch_stride_a / LOAD_VEC_A) + #endif - ir * BM * p.stride_a + start_k) / LOAD_VEC_A; + (ir * BM * p.stride_a + start_k) / LOAD_VEC_A; #ifdef MUL_MAT_ID uint pos_b = 0; #else diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm_cm2.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm_cm2.comp index d0d1d8ef7..b6614d2fc 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm_cm2.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mm_cm2.comp @@ -250,10 +250,10 @@ void main() { #endif #ifdef MUL_MAT_ID - uint pos_a = (expert_idx * p.batch_stride_a) / QUANT_K; + uint pos_a = expert_idx * (p.batch_stride_a / QUANT_K); uint pos_b = 0; #else - uint pos_a = (batch_idx_a * p.batch_stride_a) / QUANT_K; + uint pos_a = batch_idx_a * (p.batch_stride_a / QUANT_K); uint pos_b = batch_idx * p.batch_stride_b; uint pos_d = batch_idx * p.batch_stride_d + ik * p.batch_stride_d * gl_NumWorkGroups.z; #endif diff --git a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mmq.comp b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mmq.comp index cd36e270a..335d7f6a6 100644 --- a/ggml/src/ggml-vulkan/vulkan-shaders/mul_mmq.comp +++ b/ggml/src/ggml-vulkan/vulkan-shaders/mul_mmq.comp @@ -189,13 +189,13 @@ void main() { const uint end_k = min(p.K, (ik + 1) * p.k_split); #endif - uint pos_a_ib = ( + uint pos_a_ib = #ifdef MUL_MAT_ID - expert_idx * p.batch_stride_a + + expert_idx * (p.batch_stride_a / BK) + #else - batch_idx_a * p.batch_stride_a + + batch_idx_a * (p.batch_stride_a / BK) + #endif - ir * BM * p.stride_a + start_k) / BK; + (ir * BM * p.stride_a + start_k) / BK; #ifdef MUL_MAT_ID uint pos_b_ib = 0; #else diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py index b240e8e4a..404af1ef0 100644 --- a/gguf-py/gguf/constants.py +++ b/gguf-py/gguf/constants.py @@ -1738,6 +1738,7 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = { MODEL_TENSOR.ATTN_OUT, MODEL_TENSOR.ATTN_POST_NORM, MODEL_TENSOR.ATTN_GATE, + MODEL_TENSOR.ATTN_QKV, MODEL_TENSOR.FFN_GATE_INP, MODEL_TENSOR.FFN_GATE_INP_SHEXP, MODEL_TENSOR.FFN_UP_SHEXP, diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp index 2ead96546..f736ee670 100644 --- a/src/llama-arch.cpp +++ b/src/llama-arch.cpp @@ -950,6 +950,8 @@ static std::set llm_get_tensor_names(llm_arch arch) { LLM_TENSOR_ATTN_K_NORM, LLM_TENSOR_ATTN_V, LLM_TENSOR_ATTN_OUT, + LLM_TENSOR_ATTN_QKV, + LLM_TENSOR_ATTN_GATE, LLM_TENSOR_FFN_NORM, LLM_TENSOR_FFN_GATE_INP, LLM_TENSOR_FFN_GATE_EXPS, diff --git a/src/llama-graph.cpp b/src/llama-graph.cpp index 374ff1ebf..944c7e53b 100644 --- a/src/llama-graph.cpp +++ b/src/llama-graph.cpp @@ -96,11 +96,9 @@ void llm_graph_input_pos_bucket::set_input(const llama_ubatch * ubatch) { int32_t * data = (int32_t *) pos_bucket->data; - for (int h = 0; h < 1; ++h) { - for (int j = 0; j < n_tokens; ++j) { - for (int i = 0; i < n_tokens; ++i) { - data[h*(n_tokens*n_tokens) + j*n_tokens + i] = llama_relative_position_bucket(ubatch->pos[i], ubatch->pos[j], hparams.n_rel_attn_bkts, true); - } + for (int j = 0; j < n_tokens; ++j) { + for (int i = 0; i < n_tokens; ++i) { + data[j*n_tokens + i] = llama_relative_position_bucket(ubatch->pos[i], ubatch->pos[j], hparams.n_rel_attn_bkts, true); } } } @@ -323,34 +321,32 @@ void llm_graph_input_attn_no_cache::set_input(const llama_ubatch * ubatch) { const int64_t n_tokens = ubatch->n_tokens; const auto fill_mask = [&](float * data, int n_swa, llama_swa_type swa_type) { - for (int h = 0; h < 1; ++h) { - for (int i1 = 0; i1 < n_tokens; ++i1) { - const llama_seq_id s1 = ubatch->seq_id[i1][0]; - const llama_pos p1 = ubatch->pos[i1]; + for (int i1 = 0; i1 < n_tokens; ++i1) { + const llama_seq_id s1 = ubatch->seq_id[i1][0]; + const llama_pos p1 = ubatch->pos[i1]; - const uint64_t idst = h*(n_kv*n_tokens) + i1*n_kv; + const uint64_t idst = i1*n_kv; - for (int i0 = 0; i0 < n_tokens; ++i0) { - const llama_seq_id s0 = ubatch->seq_id[i0][0]; - const llama_pos p0 = ubatch->pos[i0]; + for (int i0 = 0; i0 < n_tokens; ++i0) { + const llama_seq_id s0 = ubatch->seq_id[i0][0]; + const llama_pos p0 = ubatch->pos[i0]; - // mask different sequences - if (s0 != s1) { - continue; - } - - // mask future tokens - if (cparams.causal_attn && p0 > p1) { - continue; - } - - // apply SWA if any - if (llama_hparams::is_masked_swa(n_swa, swa_type, p0, p1)) { - continue; - } - - data[idst + i0] = hparams.use_alibi ? -std::abs(p0 - p1) : 0.0f; + // mask different sequences + if (s0 != s1) { + continue; } + + // mask future tokens + if (cparams.causal_attn && p0 > p1) { + continue; + } + + // apply SWA if any + if (llama_hparams::is_masked_swa(n_swa, swa_type, p0, p1)) { + continue; + } + + data[idst + i0] = hparams.use_alibi ? -std::abs(p0 - p1) : 0.0f; } } }; @@ -454,27 +450,19 @@ void llm_graph_input_attn_cross::set_input(const llama_ubatch * ubatch) { float * data = (float *) cross_kq_mask->data; - for (int h = 0; h < 1; ++h) { - for (int i = 0; i < n_tokens; ++i) { - for (int j = 0; j < n_enc; ++j) { - float f = -INFINITY; + for (int i = 0; i < n_tokens; ++i) { + for (int j = 0; j < n_enc; ++j) { + float f = -INFINITY; - for (int s = 0; s < ubatch->n_seq_id[i]; ++s) { - const llama_seq_id seq_id = ubatch->seq_id[i][s]; + for (int s = 0; s < ubatch->n_seq_id[i]; ++s) { + const llama_seq_id seq_id = ubatch->seq_id[i][s]; - if (cross->seq_ids_enc[j].find(seq_id) != cross->seq_ids_enc[j].end()) { - f = 0.0f; - } + if (cross->seq_ids_enc[j].find(seq_id) != cross->seq_ids_enc[j].end()) { + f = 0.0f; } - - data[h*(n_enc*n_tokens) + i*n_enc + j] = f; } - } - for (int i = n_tokens; i < n_tokens; ++i) { - for (int j = 0; j < n_enc; ++j) { - data[h*(n_enc*n_tokens) + i*n_enc + j] = -INFINITY; - } + data[i*n_enc + j] = f; } } } diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 11e860141..135e92b96 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -6925,7 +6925,10 @@ bool llama_model::load_tensors(llama_model_loader & ml) { } else { // Linear attention (gated delta net) specific tensors // Create tensors with calculated dimensions - layer.ssm_in = create_tensor(tn(LLM_TENSOR_SSM_IN, "weight", i), { n_embd, qkvz_dim }, 0); + // note: ssm_in is used by legacy GGUF + layer.ssm_in = create_tensor(tn(LLM_TENSOR_SSM_IN, "weight", i), { n_embd, qkvz_dim }, TENSOR_NOT_REQUIRED); + layer.wqkv = create_tensor(tn(LLM_TENSOR_ATTN_QKV, "weight", i), { n_embd, key_dim * 2 + value_dim }, TENSOR_NOT_REQUIRED); + layer.wqkv_gate = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "weight", i), { n_embd, value_dim }, TENSOR_NOT_REQUIRED); layer.ssm_conv1d = create_tensor(tn(LLM_TENSOR_SSM_CONV1D, "weight", i), { hparams.ssm_d_conv, conv_dim }, 0); layer.ssm_dt = create_tensor(tn(LLM_TENSOR_SSM_DT, "bias", i), { hparams.ssm_dt_rank }, 0); layer.ssm_a = create_tensor(tn(LLM_TENSOR_SSM_A_NOSCAN, i), { hparams.ssm_dt_rank }, 0); diff --git a/src/models/gemma3n-iswa.cpp b/src/models/gemma3n-iswa.cpp index 93defbeef..51acab149 100644 --- a/src/models/gemma3n-iswa.cpp +++ b/src/models/gemma3n-iswa.cpp @@ -258,12 +258,12 @@ ggml_tensor * llm_build_gemma3n_iswa::get_per_layer_inputs() { res->add_input(std::move(inp)); } else { // Vision embedding path: use padding token (ID=0) embedding + // TODO: verify if this is the correct behavior in transformers implementation const int64_t embd_size = model.tok_embd_per_layer->ne[0]; // n_embd_altup * n_layer - // Extract and dequantize padding token embedding (column 0) - ggml_tensor * padding_q = ggml_view_1d(ctx0, model.tok_embd_per_layer, embd_size, 0); - ggml_tensor * padding_f32 = ggml_new_tensor_1d(ctx0, GGML_TYPE_F32, embd_size); - inp_per_layer = ggml_cpy(ctx0, padding_q, padding_f32); + // Extract and dequantize padding token embedding (row 0) + ggml_tensor * padding = ggml_view_1d(ctx0, model.tok_embd_per_layer, embd_size, 0); + inp_per_layer = ggml_cast(ctx0, padding, GGML_TYPE_F32); // Reshape to [n_embd_altup, n_layer, 1] inp_per_layer = ggml_reshape_3d(ctx0, inp_per_layer, n_embd_altup, n_layer, 1); diff --git a/src/models/models.h b/src/models/models.h index 72b2b760c..6c40f4804 100644 --- a/src/models/models.h +++ b/src/models/models.h @@ -466,7 +466,8 @@ private: ggml_tensor * cur, int il); - ggml_tensor * build_delta_net_chunking( + // returns pair of output and new state + std::pair build_delta_net_chunking( ggml_tensor * q, ggml_tensor * k, ggml_tensor * v, @@ -478,7 +479,8 @@ private: ggml_tensor * diag_mask, int il); - ggml_tensor * build_delta_net_autoregressive( + // returns pair of output and new state + std::pair build_delta_net_autoregressive( ggml_tensor * q, ggml_tensor * k, ggml_tensor * v, @@ -493,6 +495,11 @@ private: ggml_tensor * gate, int layer); + // returns pair of qkv, z + std::pair build_qkvz( + ggml_tensor * input, + int il); + const llama_model & model; }; diff --git a/src/models/qwen3next.cpp b/src/models/qwen3next.cpp index 775b3135d..57b6659ba 100644 --- a/src/models/qwen3next.cpp +++ b/src/models/qwen3next.cpp @@ -86,7 +86,15 @@ llm_build_qwen3next::llm_build_qwen3next(const llama_model & model, const llm_gr ggml_build_forward_expand(gf, cur); } -ggml_tensor * llm_build_qwen3next::build_delta_net_chunking( +// utility to get one slice from the third dimension +// input dim: [x, y, c, b] +// output dim: [x, y, 1, b] +static ggml_tensor * get_slice_2d(ggml_context * ctx0, ggml_tensor * t, int64_t c) { + return ggml_view_4d(ctx0, t, t->ne[0], t->ne[1], 1, t->ne[3], + t->nb[1], t->nb[2], t->nb[3], t->nb[2] * c); +} + +std::pair llm_build_qwen3next::build_delta_net_chunking( ggml_tensor * q, ggml_tensor * k, ggml_tensor * v, @@ -187,18 +195,16 @@ ggml_tensor * llm_build_qwen3next::build_delta_net_chunking( beta = ggml_reshape_4d(ctx0, beta, 1, chunk_size, n_chunks, H_k * n_seqs); ggml_tensor * g_cumsum = ggml_cumsum(ctx0, g); + cb(g_cumsum, "g_cumsum", il); // shape: (chunk_size, 1, n_chunks, H_v * n_seqs) - cb(g_cumsum, "g_cumsum", il); - - ggml_tensor * gcs_i = ggml_reshape_4d(ctx0, g_cumsum, chunk_size, 1, n_chunks, H_v * n_seqs); + ggml_tensor * gcs_i = g_cumsum; // ggml_reshape_4d(ctx0, g_cumsum, chunk_size, 1, n_chunks, H_v * n_seqs); ggml_tensor * gcs_j = ggml_reshape_4d(ctx0, g_cumsum, 1, chunk_size, n_chunks, H_v * n_seqs); ggml_tensor * gcs_j_broadcast = ggml_repeat_4d(ctx0, gcs_j, chunk_size, chunk_size, n_chunks, H_v * n_seqs); ggml_tensor * decay_mask = ggml_sub(ctx0, gcs_j_broadcast, gcs_i); - - cb(decay_mask, "decay_mask", il); + cb(decay_mask, "decay_mask", il); // shape: (chunk_size, chunk_size, n_chunks, H_v * n_seqs) decay_mask = ggml_mul(ctx0, decay_mask, diag_mask); decay_mask = ggml_exp(ctx0, decay_mask); @@ -208,8 +214,7 @@ ggml_tensor * llm_build_qwen3next::build_delta_net_chunking( ggml_tensor * k_decay = ggml_mul(ctx0, kmulkbeta, decay_mask); ggml_tensor * attn = ggml_neg(ctx0, ggml_mul(ctx0, k_decay, causal_mask)); - - cb(attn, "attn_pre_solve", il); + cb(attn, "attn_pre_solve", il); // shape: (chunk_size, chunk_size, n_chunks, H_v * n_seqs) ggml_tensor * attn_lower = ggml_mul(ctx0, attn, causal_mask); ggml_tensor * lhs = ggml_sub(ctx0, ggml_repeat(ctx0, identity, attn_lower), attn_lower); @@ -217,8 +222,7 @@ ggml_tensor * llm_build_qwen3next::build_delta_net_chunking( ggml_tensor * lin_solve = ggml_solve_tri(ctx0, lhs, attn, true, true, false); attn = ggml_mul(ctx0, lin_solve, causal_mask); attn = ggml_add(ctx0, attn, identity); - - cb(attn, "attn_solved", il); + cb(attn, "attn_solved", il); // shape: (chunk_size, chunk_size, n_chunks, H_v * n_seqs) v = ggml_mul_mat(ctx0, ggml_cont(ctx0, ggml_transpose(ctx0, v_beta)), attn); @@ -226,116 +230,126 @@ ggml_tensor * llm_build_qwen3next::build_delta_net_chunking( ggml_tensor * gexp = ggml_exp(ctx0, g_cumsum_t); ggml_tensor * kbeta_gexp = ggml_mul(ctx0, k_beta, gexp); - - cb(kbeta_gexp, "kbeta_gexp", il); + cb(kbeta_gexp, "kbeta_gexp", il); // shape: (S_k, chunk_size, n_chunks, H_v * n_seqs) ggml_tensor * k_cumdecay = ggml_cont(ctx0, ggml_transpose(ctx0, ggml_mul_mat(ctx0, attn, ggml_cont(ctx0, ggml_transpose(ctx0, kbeta_gexp))))); + cb(k_cumdecay, "k_cumdecay", il); // shape: (chunk_size, chunk_size, n_chunks, H_v * n_seqs) - cb(k_cumdecay, "k_cumdecay", il); + ggml_tensor * attn_kq = ggml_mul_mat(ctx0, k, q); + attn_kq = ggml_mul(ctx0, attn_kq, decay_mask); + attn_kq = ggml_mul(ctx0, attn_kq, diag_mask); + cb(attn_kq, "attn_kq", il); // shape: (chunk_size, chunk_size, n_chunks, H_v * n_seqs) + + // vectorized calculation of key_gdiff + // improved from the chunked version: + // g_last = torch.clamp(g_cum[:, :, -1], max=50.0).exp().unsqueeze(-1).unsqueeze(-1) + // g_diff = torch.clamp(g_cum[:, :, -1:] - g_cum, max=50.0).exp() + // key_gdiff = key * g_diff.unsqueeze(-1) + // kgdmulvnew = (key_gdiff).transpose(-1, -2) @ v_new + // last_recurrent_state = last_recurrent_state * g_last + kgdmulvnew + + // get last element in g_cumsum along chunk_size dimension (ne0) + // example: [[x, y, z, ..., last], ...] -> [[last], ...] + ggml_tensor * g_last = ggml_view_4d(ctx0, g_cumsum, 1, 1, g_cumsum->ne[2], g_cumsum->ne[3], + g_cumsum->nb[1], g_cumsum->nb[2], g_cumsum->nb[3], + (g_cumsum->ne[0] - 1) * ggml_element_size(g_cumsum)); + g_last = ggml_cont(ctx0, g_last); + cb(g_last, "g_last", il); // shape: (1, 1, n_chunks, H_v * n_seqs) + + ggml_tensor * g_last_exp = ggml_exp(ctx0, g_last); + cb(g_last_exp, "g_last_exp", il); // shape: (1, 1, n_chunks, H_v * n_seqs) + + ggml_tensor * g_diff = ggml_neg(ctx0, ggml_sub(ctx0, g_cumsum, g_last)); + cb(g_diff, "g_diff", il); // shape: (chunk_size, 1, n_chunks, H_v * n_seqs) + + ggml_tensor * g_diff_exp = ggml_exp(ctx0, g_diff); + ggml_tensor * key_gdiff = ggml_mul(ctx0, k, g_diff_exp); + cb(key_gdiff, "key_gdiff", il); // shape: (S_k, chunk_size, n_chunks, H_v * n_seqs) + + + // state to be updated per chunk + ggml_tensor * new_state = state; // ggml_dup(ctx0, state); + cb(new_state, "new_state", il); // shape: (S_v, S_v, H_v, n_seqs) + + // shape after loop of chunks: (S_v, chunk_size, n_chunks, H_v * n_seqs) ggml_tensor * core_attn_out = nullptr; - ggml_tensor * new_state = ggml_dup(ctx0, state); - - cb(new_state, "new_state", il); for (int64_t chunk = 0; chunk < n_chunks; chunk++) { - auto chunkify = [=](ggml_tensor * t) { - return ggml_cont(ctx0, ggml_view_4d(ctx0, t, t->ne[0], chunk_size, 1, t->ne[3], - t->nb[1], t->nb[2], t->nb[3], t->nb[2] * chunk)); - }; + // shape: (S_k, chunk_size, 1, H_k * n_seqs) + ggml_tensor * q_chunk = get_slice_2d(ctx0, q, chunk); // (no cont), next op: ggml_mul - auto chunkify_g = [=](ggml_tensor * t) { - return ggml_cont(ctx0, ggml_view_4d(ctx0, t, chunk_size, t->ne[1], 1, t->ne[3], - t->nb[1], t->nb[2], t->nb[3], t->nb[2] * chunk)); - }; + // shape: (S_v, chunk_size, 1, H_v * n_seqs) + ggml_tensor * v_chunk = get_slice_2d(ctx0, v, chunk); // (no cont), next op: ggml_repeat - ggml_tensor * k_chunk = chunkify(k); - ggml_tensor * q_chunk = chunkify(q); - ggml_tensor * v_chunk = chunkify(v); + // shape: (chunk_size, 1, n_chunks, H_v * n_seqs) + ggml_tensor * gexp_chunk = get_slice_2d(ctx0, gexp, chunk); // (no cont), next op: ggml_mul - ggml_tensor * g_cs_chunk = chunkify_g(g_cumsum); - ggml_tensor * g_cs_chunk_t = ggml_cont(ctx0, ggml_transpose(ctx0, g_cs_chunk)); - - ggml_tensor * decay_mask_chunk = chunkify(decay_mask); - ggml_tensor * k_cumdecay_chunk = chunkify(k_cumdecay); - - ggml_tensor * gexp_chunk = ggml_exp(ctx0, g_cs_chunk_t); + // shape: (chunk_size, 1, H_v * n_seqs) + ggml_tensor * k_cumdecay_chunk = get_slice_2d(ctx0, k_cumdecay, chunk); // (no cont), next op: ggml_mul_mat // attn = (q_i @ k_i.transpose(-1, -2) * decay_mask[:, :, i]).masked_fill_(mask, 0) - attn = ggml_mul_mat(ctx0, k_chunk, q_chunk); - attn = ggml_mul(ctx0, attn, decay_mask_chunk); - attn = ggml_mul(ctx0, attn, diag_mask); + // replaced by precomputed attn_kq + ggml_tensor * attn_chunk = get_slice_2d(ctx0, attn_kq, chunk); + cb(attn_chunk, "attn_chunk", il); ggml_tensor * state_t = ggml_cont_4d(ctx0, ggml_permute(ctx0, new_state, 1, 0, 2, 3), S_v, S_v, 1, H_v * n_seqs); // v_prime = (k_cumdecay[:, :, i]) @ last_recurrent_state ggml_tensor * v_prime = ggml_mul_mat(ctx0, state_t, k_cumdecay_chunk); + cb(v_prime, "v_prime_chunk", il); // shape: (S_v, 1, H_v * n_seqs) // v_new = v_i - v_prime ggml_tensor * v_new = ggml_sub(ctx0, ggml_repeat(ctx0, v_chunk, v_prime), v_prime); ggml_tensor * v_new_t = ggml_cont(ctx0, ggml_transpose(ctx0, v_new)); + cb(v_new, "v_new_chunk", il); // attn_inter = (q_i * g[:, :, i, :, None].exp()) @ last_recurrent_state ggml_tensor * q_g_exp = ggml_mul(ctx0, q_chunk, gexp_chunk); ggml_tensor * attn_inter = ggml_mul_mat(ctx0, state_t, q_g_exp); + cb(attn_inter, "attn_inter_chunk", il); // core_attn_out[:, :, i] = attn_inter + attn @ v_new - ggml_tensor * v_attn = ggml_mul_mat(ctx0, v_new_t, attn); + ggml_tensor * v_attn = ggml_mul_mat(ctx0, v_new_t, attn_chunk); + cb(v_attn, "v_attn_chunk", il); ggml_tensor * core_attn_out_chunk = ggml_add(ctx0, attn_inter, v_attn); + cb(core_attn_out_chunk, "core_attn_out_chunk", il); // shape: (S_v, chunk_size, 1, H_v * n_seqs) - core_attn_out = core_attn_out == nullptr ? core_attn_out_chunk : ggml_concat(ctx0, core_attn_out, core_attn_out_chunk, 1); + core_attn_out = core_attn_out == nullptr + ? core_attn_out_chunk + : ggml_concat(ctx0, core_attn_out, core_attn_out_chunk, 2); - // g_last = torch.clamp(g_cum[:, :, -1], max=50.0).exp().unsqueeze(-1).unsqueeze(-1) - // g_diff = torch.clamp(g_cum[:, :, -1:] - g_cum, max=50.0).exp() - // key_gdiff = key * g_diff.unsqueeze(-1) // kgdmulvnew = (key_gdiff).transpose(-1, -2) @ v_new + ggml_tensor * k_gdiff = ggml_cont(ctx0, get_slice_2d(ctx0, key_gdiff, chunk)); + //ggml_tensor * kgdmulvnew = ggml_mul_mat(ctx0, k_gdiff, v_new); // this is slower on metal, why? + ggml_tensor * kgdmulvnew = ggml_mul_mat(ctx0, v_new_t, ggml_cont(ctx0, ggml_transpose(ctx0, k_gdiff))); + // last_recurrent_state = last_recurrent_state * g_last + kgdmulvnew - - ggml_tensor * g_cum_last = - ggml_cont(ctx0, ggml_view_4d(ctx0, g_cs_chunk_t, g_cs_chunk_t->ne[0], 1, g_cs_chunk_t->ne[2], g_cs_chunk_t->ne[3], - g_cs_chunk_t->nb[1], g_cs_chunk_t->nb[2], g_cs_chunk_t->nb[3], - g_cs_chunk_t->nb[0] * (g_cs_chunk_t->ne[1] - 1))); - - ggml_tensor * gexp_last = - ggml_reshape_4d(ctx0, ggml_exp(ctx0, g_cum_last), 1, 1, g_cum_last->ne[0] * g_cum_last->ne[2], g_cum_last->ne[3]); - - ggml_tensor * g_cum_last_3d = - ggml_reshape_3d(ctx0, g_cum_last, g_cum_last->ne[0], g_cum_last->ne[2], g_cum_last->ne[3]); - - ggml_tensor * g_cumsum_3d = ggml_reshape_3d(ctx0, g_cs_chunk, g_cs_chunk->ne[0], g_cs_chunk->ne[2], g_cs_chunk->ne[3]); - - ggml_tensor * g_diff = ggml_neg(ctx0, ggml_sub(ctx0, g_cumsum_3d, g_cum_last_3d)); - - ggml_tensor * g_diff_exp = ggml_exp(ctx0, g_diff); - - ggml_tensor * key_gdiff = ggml_mul(ctx0, k_chunk, - ggml_reshape_4d(ctx0, g_diff_exp, 1, g_diff_exp->ne[0], g_diff_exp->ne[1], - g_diff_exp->ne[2] * g_diff_exp->ne[3])); - - ggml_tensor * kgdmulvnew = ggml_mul_mat(ctx0, v_new_t, ggml_cont(ctx0, ggml_transpose(ctx0, key_gdiff))); - + ggml_tensor * gexp_last_chunk = ggml_cont(ctx0, get_slice_2d(ctx0, g_last_exp, chunk)); new_state = ggml_add(ctx0, - ggml_mul(ctx0, new_state, ggml_reshape_4d(ctx0, gexp_last, gexp_last->ne[0], gexp_last->ne[1], H_v, n_seqs)), + ggml_mul(ctx0, new_state, ggml_reshape_4d(ctx0, gexp_last_chunk, gexp_last_chunk->ne[0], gexp_last_chunk->ne[1], H_v, n_seqs)), ggml_reshape_4d(ctx0, kgdmulvnew, kgdmulvnew->ne[0], kgdmulvnew->ne[1], H_v, n_seqs)); } - core_attn_out = ggml_cont_4d(ctx0, core_attn_out, S_v, chunk_size * n_chunks, H_v, n_seqs); - - ggml_tensor * output_tokens = ggml_view_4d(ctx0, core_attn_out, S_v, n_tokens, H_v, n_seqs, core_attn_out->nb[1], core_attn_out->nb[2], core_attn_out->nb[3], 0); + // truncate padded tokens + ggml_tensor * output_tokens = ggml_view_4d(ctx0, core_attn_out, + S_v, n_tokens, H_v, n_seqs, + ggml_row_size(core_attn_out->type, S_v), + ggml_row_size(core_attn_out->type, S_v * chunk_size * n_chunks), + ggml_row_size(core_attn_out->type, S_v * chunk_size * n_chunks * H_v), 0); + output_tokens = ggml_cont(ctx0, output_tokens); cb(output_tokens, "output_tokens", il); - // flatten output - ggml_tensor * flat_output = - ggml_cont_1d(ctx0, ggml_permute(ctx0, output_tokens, 0, 2, 1, 3), S_v * H_v * n_tokens * n_seqs); + // permute back to (S_v, H_v, n_tokens, n_seqs) + output_tokens = ggml_permute(ctx0, output_tokens, 0, 2, 1, 3); + output_tokens = ggml_cont(ctx0, output_tokens); - ggml_tensor * flat_state = ggml_cont_1d(ctx0, new_state, S_v * S_v * H_v * n_seqs); - - return ggml_concat(ctx0, flat_output, flat_state, 0); + return {output_tokens, new_state}; } -ggml_tensor * llm_build_qwen3next::build_delta_net_autoregressive( +std::pair llm_build_qwen3next::build_delta_net_autoregressive( ggml_tensor * q, ggml_tensor * k, ggml_tensor * v, @@ -419,11 +433,7 @@ ggml_tensor * llm_build_qwen3next::build_delta_net_autoregressive( cb(core_attn_out, "output_tokens", il); cb(state, "new_state", il); - // flatten output, no need to permute since n_tokens is 1 so [S_v, 1, H_v, n_seqs] and [S_v, H_v, 1, n_seqs] are equivalent memory-layout wise - ggml_tensor * flat_output = ggml_reshape_1d(ctx0, core_attn_out, S_v * H_v * n_tokens * n_seqs); - ggml_tensor * flat_state = ggml_reshape_1d(ctx0, state, S_v * S_v * H_v * n_seqs); - - return ggml_concat(ctx0, flat_output, flat_state, 0); + return {core_attn_out, state}; } ggml_tensor * llm_build_qwen3next::build_norm_gated( @@ -523,6 +533,88 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn( return cur; } +std::pair llm_build_qwen3next::build_qkvz( + ggml_tensor * input, + int il) { + const int64_t d_inner = hparams.ssm_d_inner; + const int64_t n_seqs = ubatch.n_seqs; + const int64_t head_k_dim = hparams.ssm_d_state; + const int64_t num_k_heads = hparams.ssm_n_group; + const int64_t num_v_heads = hparams.ssm_dt_rank; + const int64_t head_v_dim = d_inner / num_v_heads; + const int64_t n_seq_tokens = ubatch.n_seq_tokens; + + if (model.layers[il].wqkv) { + // optimized path + ggml_tensor * qkv_mixed = build_lora_mm(model.layers[il].wqkv, input); + qkv_mixed = ggml_reshape_3d(ctx0, qkv_mixed, qkv_mixed->ne[0], n_seq_tokens, n_seqs); + cb(qkv_mixed, "linear_attn_qkv_mixed", il); + + ggml_tensor * z = build_lora_mm(model.layers[il].wqkv_gate, input); + cb(z, "z", il); + + return { qkv_mixed, z }; + + } else { + // legacy (slower) path + ggml_tensor * mixed_qkvz = build_lora_mm(model.layers[il].ssm_in, input); + cb(mixed_qkvz, "linear_attn_mixed_qkvz", il); + + int64_t qkvz_new_dim = 2 * head_k_dim + 2 * head_v_dim * (num_v_heads / num_k_heads); + ggml_tensor * mixed_qkvz_reshaped = ggml_reshape_4d(ctx0, mixed_qkvz, qkvz_new_dim, num_k_heads, n_seq_tokens, n_seqs); + + // Split mixed_qkvz into query, key, value, z + int64_t split_sizes_qkvz[4] = { + head_k_dim, // query size + head_k_dim, // key size + head_v_dim * num_v_heads / num_k_heads, // value size + head_v_dim * num_v_heads / num_k_heads // z size + }; + + ggml_tensor * query = + ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[0], num_k_heads, n_seq_tokens, n_seqs, + mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], 0); + cb(query, "q", il); + + ggml_tensor * key = ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[1], num_k_heads, n_seq_tokens, n_seqs, + mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], + split_sizes_qkvz[0] * ggml_element_size(mixed_qkvz_reshaped)); + cb(key, "k", il); + + ggml_tensor * value = + ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[2], num_k_heads, n_seq_tokens, n_seqs, + mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], + (split_sizes_qkvz[0] + split_sizes_qkvz[1]) * ggml_element_size(mixed_qkvz_reshaped)); + cb(value, "v", il); + + ggml_tensor * z = ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[3], num_k_heads, n_seq_tokens, n_seqs, + mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], + (split_sizes_qkvz[0] + split_sizes_qkvz[1] + split_sizes_qkvz[2]) * ggml_element_size(mixed_qkvz_reshaped)); + z = ggml_cont(ctx0, z); + cb(z, "z", il); + + // After creating query, key, and value_reshaped, reshape each to flatten the head dimensions + // query: [head_k_dim, num_k_heads, n_tokens, n_seqs] -> [head_k_dim * num_k_heads, n_tokens, n_seqs] + ggml_tensor * query_flat = ggml_cont_3d(ctx0, query, head_k_dim * num_k_heads, n_seq_tokens, n_seqs); + cb(query_flat, "query_flat", il); + + // key: [head_k_dim, num_k_heads, n_tokens, n_seqs] -> [head_k_dim * num_k_heads, n_tokens, n_seqs] + ggml_tensor * key_flat = ggml_cont_3d(ctx0, key, head_k_dim * num_k_heads, n_seq_tokens, n_seqs); + cb(key_flat, "key_flat", il); + + // value_reshaped: [head_v_dim, num_v_heads, n_tokens, n_seqs] -> [head_v_dim * num_v_heads, n_tokens, n_seqs] + ggml_tensor * value_flat = ggml_cont_3d(ctx0, value, head_v_dim * num_v_heads, n_seq_tokens, n_seqs); + cb(value_flat, "value_flat", il); + + // Now concatenate along the feature dimension (dim 0) to get [conv_dim, n_tokens, n_seqs] + ggml_tensor * qkv_mixed = ggml_concat(ctx0, query_flat, key_flat, 0); + qkv_mixed = ggml_concat(ctx0, qkv_mixed, value_flat, 0); + cb(qkv_mixed, "qkv_mixed", il); + + return { qkv_mixed, z }; + } +} + ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( llm_graph_input_rs * inp, ggml_tensor * cur, @@ -547,15 +639,13 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( GGML_ASSERT(ubatch.n_tokens == n_seq_tokens * n_seqs); // Input projections - ggml_tensor * mixed_qkvz = build_lora_mm(model.layers[il].ssm_in, cur); - cb(mixed_qkvz, "linear_attn_mixed_qkvz", il); + auto qkvz = build_qkvz(cur, il); + ggml_tensor * qkv_mixed = qkvz.first; + ggml_tensor * z = qkvz.second; ggml_tensor * mixed_ba = build_lora_mm(model.layers[il].ssm_beta_alpha, cur); cb(mixed_ba, "linear_attn_mixed_ba", il); - int64_t qkvz_new_dim = 2 * head_k_dim + 2 * head_v_dim * (num_v_heads / num_k_heads); - ggml_tensor * mixed_qkvz_reshaped = ggml_reshape_4d(ctx0, mixed_qkvz, qkvz_new_dim, num_k_heads, n_seq_tokens, n_seqs); - // Reshape mixed_ba: [batch, seq_len, hidden_size] -> [batch, seq_len, num_k_heads, 2*num_v_heads/num_k_heads] int64_t ba_new_dim = 2 * num_v_heads / num_k_heads; ggml_tensor * mixed_ba_reshaped = ggml_reshape_4d(ctx0, mixed_ba, ba_new_dim, num_k_heads, n_seq_tokens, n_seqs); @@ -575,8 +665,9 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( split_sizes_ba[0] * ggml_element_size(mixed_ba_reshaped)); cb(a, "a", il); - // Reshape b and a to merge head dimensions: [batch, seq_len, num_k_heads, num_v_heads/num_k_heads] -> [batch, seq_len, num_v_heads] - ggml_tensor * beta = ggml_cont_3d(ctx0, b, num_v_heads, n_seq_tokens, n_seqs); + ggml_tensor * beta = ggml_cont_4d(ctx0, b, num_v_heads, 1, n_seq_tokens, n_seqs); + + // Reshape a to merge head dimensions: [batch, seq_len, num_k_heads, num_v_heads/num_k_heads] -> [batch, seq_len, num_v_heads] ggml_tensor * alpha = ggml_cont_3d(ctx0, a, num_v_heads, n_seq_tokens, n_seqs); ggml_tensor * alpha_biased = ggml_add(ctx0, alpha, model.layers[il].ssm_dt); @@ -585,48 +676,6 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( ggml_tensor * gate = ggml_mul(ctx0, alpha_softplus, model.layers[il].ssm_a); // -A_log.exp() * softplus cb(gate, "gate", il); - // Split mixed_qkvz into query, key, value, z - int64_t split_sizes_qkvz[4] = { - head_k_dim, // query size - head_k_dim, // key size - head_v_dim * num_v_heads / num_k_heads, // value size - head_v_dim * num_v_heads / num_k_heads // z size - }; - - ggml_tensor * query = - ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[0], num_k_heads, n_seq_tokens, n_seqs, - mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], 0); - cb(query, "q", il); - - ggml_tensor * key = ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[1], num_k_heads, n_seq_tokens, n_seqs, - mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], - split_sizes_qkvz[0] * sizeof(float)); - cb(key, "k", il); - - ggml_tensor * value = - ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[2], num_k_heads, n_seq_tokens, n_seqs, - mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], - (split_sizes_qkvz[0] + split_sizes_qkvz[1]) * sizeof(float)); - cb(value, "v", il); - - ggml_tensor * z = ggml_view_4d(ctx0, mixed_qkvz_reshaped, split_sizes_qkvz[3], num_k_heads, n_seq_tokens, n_seqs, - mixed_qkvz_reshaped->nb[1], mixed_qkvz_reshaped->nb[2], mixed_qkvz_reshaped->nb[3], - (split_sizes_qkvz[0] + split_sizes_qkvz[1] + split_sizes_qkvz[2]) * sizeof(float)); - cb(z, "z", il); - - // After creating query, key, and value_reshaped, reshape each to flatten the head dimensions - // query: [head_k_dim, num_k_heads, n_tokens, n_seqs] -> [head_k_dim * num_k_heads, n_tokens, n_seqs] - ggml_tensor * query_flat = ggml_cont_3d(ctx0, query, head_k_dim * num_k_heads, n_seq_tokens, n_seqs); - cb(query_flat, "query_flat", il); - - // key: [head_k_dim, num_k_heads, n_tokens, n_seqs] -> [head_k_dim * num_k_heads, n_tokens, n_seqs] - ggml_tensor * key_flat = ggml_cont_3d(ctx0, key, head_k_dim * num_k_heads, n_seq_tokens, n_seqs); - cb(key_flat, "key_flat", il); - - // value_reshaped: [head_v_dim, num_v_heads, n_tokens, n_seqs] -> [head_v_dim * num_v_heads, n_tokens, n_seqs] - ggml_tensor * value_flat = ggml_cont_3d(ctx0, value, head_v_dim * num_v_heads, n_seq_tokens, n_seqs); - cb(value_flat, "value_flat", il); - // Get convolution states from cache ggml_tensor * conv_states_all = mctx_cur->get_r_l(il); ggml_tensor * ssm_states_all = mctx_cur->get_s_l(il); @@ -637,17 +686,6 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( ggml_tensor * conv_states = build_rs(inp, conv_states_all, hparams.n_embd_r(), n_seqs); cb(conv_states, "conv_states", il); - // Now concatenate along the feature dimension (dim 0) to get [conv_dim, n_tokens, n_seqs] - ggml_tensor * qkv_mixed = ggml_concat(ctx0, query_flat, key_flat, 0); - qkv_mixed = ggml_concat(ctx0, qkv_mixed, value_flat, 0); - cb(qkv_mixed, "qkv_mixed", il); - - qkv_mixed = ggml_permute(ctx0, qkv_mixed, 1, 0, 2, 3); - cb(qkv_mixed, "qkv_mixed_permuted", il); - - // Calculate the total conv dimension - int64_t qkv_dim = head_k_dim * num_k_heads * 2 + head_v_dim * num_v_heads; - // Calculate convolution kernel size ggml_tensor * conv_kernel = model.layers[il].ssm_conv1d; const int64_t conv_kernel_size = conv_kernel->ne[0]; @@ -655,6 +693,9 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( conv_states = ggml_reshape_3d(ctx0, conv_states, conv_kernel_size - 1, conv_channels, n_seqs); cb(conv_states, "conv_states_reshaped", il); + qkv_mixed = ggml_permute(ctx0, qkv_mixed, 1, 0, 2, 3); + cb(qkv_mixed, "qkv_mixed_permuted", il); + ggml_tensor * conv_input = ggml_concat(ctx0, conv_states, qkv_mixed, 0); cb(conv_input, "conv_input", il); @@ -677,26 +718,25 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( ggml_tensor * conv_output_proper = ggml_ssm_conv(ctx0, conv_input, conv_kernel); cb(conv_output_proper, "conv_output_raw", il); - conv_output_proper = ggml_cont(ctx0, ggml_transpose(ctx0, conv_output_proper)); - cb(conv_output_proper, "conv_output_pre_silu", il); - ggml_tensor * conv_output_silu = ggml_silu(ctx0, conv_output_proper); cb(conv_output_silu, "conv_output_silu", il); - ggml_tensor * conv_qkv_mix = - ggml_cont_2d(ctx0, ggml_transpose(ctx0, conv_output_silu), qkv_dim, n_seq_tokens * n_seqs); - cb(conv_qkv_mix, "conv_qkv_mix", il); + ggml_tensor * conv_qkv_mix = conv_output_silu; + + // Calculate the total conv dimension + int64_t qkv_dim = head_k_dim * num_k_heads * 2 + head_v_dim * num_v_heads; + int64_t nb1_qkv = ggml_row_size(conv_qkv_mix->type, qkv_dim); // Extract the convolved Q, K, V from conv_output ggml_tensor * q_conv = - ggml_view_2d(ctx0, conv_qkv_mix, head_k_dim * num_k_heads, n_seq_tokens * n_seqs, conv_qkv_mix->nb[1], 0); + ggml_view_2d(ctx0, conv_qkv_mix, head_k_dim * num_k_heads, n_seq_tokens * n_seqs, nb1_qkv, 0); cb(q_conv, "q_conv", il); ggml_tensor * k_conv = - ggml_view_2d(ctx0, conv_qkv_mix, head_k_dim * num_k_heads, n_seq_tokens * n_seqs, conv_qkv_mix->nb[1], + ggml_view_2d(ctx0, conv_qkv_mix, head_k_dim * num_k_heads, n_seq_tokens * n_seqs, nb1_qkv, head_k_dim * num_k_heads * ggml_element_size(conv_qkv_mix)); cb(k_conv, "k_conv", il); ggml_tensor * v_conv = - ggml_view_2d(ctx0, conv_qkv_mix, head_v_dim * num_v_heads, n_seq_tokens * n_seqs, conv_qkv_mix->nb[1], + ggml_view_2d(ctx0, conv_qkv_mix, head_v_dim * num_v_heads, n_seq_tokens * n_seqs, nb1_qkv, 2 * head_k_dim * num_k_heads * ggml_element_size(conv_qkv_mix)); cb(v_conv, "v_conv", il); @@ -705,8 +745,6 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( k_conv = ggml_cont_4d(ctx0, k_conv, head_k_dim, num_k_heads, n_seq_tokens, n_seqs); v_conv = ggml_cont_4d(ctx0, v_conv, head_v_dim, num_v_heads, n_seq_tokens, n_seqs); - beta = ggml_cont_4d(ctx0, b, num_v_heads, 1, n_seq_tokens, n_seqs); - ggml_tensor * state = build_rs(inp, ssm_states_all, hparams.n_embd_s(), n_seqs); state = ggml_reshape_4d(ctx0, state, head_v_dim, head_v_dim * num_v_heads, 1, n_seqs); cb(state, "state_predelta", il); @@ -738,45 +776,29 @@ ggml_tensor * llm_build_qwen3next::build_layer_attn_linear( cb(v_conv, "v_conv_predelta", il); // Choose between build_delta_net_chunking, build_delta_net_recurrent, and build_delta_net_autoregressive based on n_tokens - ggml_tensor * attn_out; + std::pair attn_out; // pair of (output, new_state) if (n_seq_tokens == 1) { attn_out = build_delta_net_autoregressive(q_conv, k_conv, v_conv, gate, beta, state, il); } else { attn_out = build_delta_net_chunking(q_conv, k_conv, v_conv, gate, beta, state, causal_mask, identity, diag_mask, il); } - cb(attn_out, "attn_out", il); - - // The tensors were concatenated 1d, so we need to extract them 1d as well - const int64_t output_flat_size = head_v_dim * num_v_heads * n_seq_tokens * n_seqs; - ggml_tensor * attn_out_1d = ggml_view_1d(ctx0, attn_out, output_flat_size, 0); - cb(attn_out_1d, "attn_out_1d", il); - - ggml_tensor * attn_out_final = ggml_cont_4d(ctx0, attn_out_1d, head_v_dim, num_v_heads, n_seq_tokens, n_seqs); - cb(attn_out_final, "attn_out_reshaped", il); - - // Extract the state part (second part of the concatenated tensor) - // State starts after n_tokens elements along dimension 1 - const int64_t state_flat_size = head_v_dim * head_v_dim * num_v_heads * n_seqs; - - ggml_tensor * state_1d = - ggml_view_1d(ctx0, attn_out, state_flat_size, output_flat_size * ggml_element_size(attn_out)); - cb(state_1d, "state_1d", il); + ggml_tensor * output = attn_out.first; + ggml_tensor * new_state = attn_out.second; + cb(output, "attn_output", il); + cb(new_state, "new_state", il); // Update the recurrent states ggml_build_forward_expand(gf, - ggml_cpy(ctx0, state_1d, + ggml_cpy(ctx0, new_state, ggml_view_1d(ctx0, ssm_states_all, hparams.n_embd_s() * n_seqs, kv_head * hparams.n_embd_s() * ggml_element_size(ssm_states_all)))); - GGML_ASSERT(ggml_nelements(attn_out_1d) + ggml_nelements(state_1d) == ggml_nelements(attn_out)); - // Reshape both attn_out_final and z to 2D tensors for normalization // attn_out_final: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * n_tokens * n_seqs, head_dim] - ggml_tensor * attn_out_2d_final = - ggml_cont_2d(ctx0, attn_out_final, head_v_dim, num_v_heads * n_seq_tokens * n_seqs); + ggml_tensor * attn_out_2d_final = ggml_reshape_2d(ctx0, output, head_v_dim, num_v_heads * n_seq_tokens * n_seqs); // z: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * n_tokens * n_seqs, head_dim] - ggml_tensor * z_2d = ggml_cont_2d(ctx0, z, head_v_dim, num_v_heads * n_seq_tokens * n_seqs); + ggml_tensor * z_2d = ggml_reshape_2d(ctx0, z, head_v_dim, num_v_heads * n_seq_tokens * n_seqs); // Apply gated normalization: self.norm(core_attn_out, z) ggml_tensor * attn_out_norm = build_norm_gated(attn_out_2d_final, model.layers[il].ssm_norm, z_2d, il); @@ -828,12 +850,6 @@ ggml_tensor * llm_build_qwen3next::build_layer_ffn(ggml_tensor * cur, const int shared_gate = ggml_sigmoid(ctx0, shared_gate); cb(shared_gate, "shared_expert_gate_sigmoid", il); - // The gate needs to be broadcast to match the dimensions of ffn_shexp - // ffn_shexp is [n_embd, n_tokens, 1, 1] and shared_gate is [1, n_tokens, 1, 1] - // We need to repeat the gate along the feature dimension - shared_gate = ggml_repeat(ctx0, shared_gate, ffn_shexp); - cb(shared_gate, "shared_expert_gate_broadcast", il); - // Apply the gate to the shared expert output ffn_shexp = ggml_mul(ctx0, ffn_shexp, shared_gate); cb(ffn_shexp, "ffn_shexp_gated", il); diff --git a/tests/test-backend-sampler.cpp b/tests/test-backend-sampler.cpp index 24ece9d4b..c10bde91b 100644 --- a/tests/test-backend-sampler.cpp +++ b/tests/test-backend-sampler.cpp @@ -11,76 +11,78 @@ #include #include #include -#include #include #include #include #include #include -struct backend_cli_args { - const char * model = nullptr; - const char * test = nullptr; - const char * device = "cpu"; +struct test_args { + std::string model; + std::string test; + std::string device = "auto"; }; -struct test_model_context { - llama_model_ptr model; +struct test_params { + llama_model_ptr model; +}; + +static llama_model_ptr load_model(const test_args & args) { + auto mparams = llama_model_default_params(); + + ggml_backend_dev_t devs[2] = { nullptr, nullptr }; + + if (args.device != "auto") { + if (args.device == "gpu") { + devs[0] = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU); + + if (devs[0] == nullptr) { + fprintf(stderr, "Error: GPU requested but not available\n"); + return nullptr; + } + + mparams.n_gpu_layers = 999; + } else if (args.device == "cpu") { + devs[0] = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU); + + mparams.n_gpu_layers = 0; + } else { + fprintf(stderr, "Error: invalid device '%s'\n", args.device.c_str()); + return nullptr; + } + + mparams.devices = devs; + + fprintf(stderr, "Using device: %s\n", ggml_backend_dev_name(devs[0])); + } + + llama_model_ptr res; + + res.reset(llama_model_load_from_file(args.model.c_str(), mparams)); + + if (!res) { + fprintf(stderr, "Warning: failed to load model '%s', skipping test\n", args.model.c_str()); + return nullptr; + } + + return res; +} + +struct test_context { llama_context_ptr ctx; - int n_vocab = 0; + + int n_vocab = 0; + + const llama_vocab * vocab = nullptr; std::unordered_map seq_positions; std::unordered_map last_batch_info; - bool load_model(const backend_cli_args & args) { - if (model) { - return true; - } + test_context(const test_params & params, std::vector & configs, int32_t n_seq_max = -1) { + auto * model = params.model.get(); - llama_backend_init(); - - auto mparams = llama_model_default_params(); - - ggml_backend_dev_t devs[2]; - if (std::string_view(args.device) == "gpu") { - ggml_backend_dev_t gpu = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU); - if (gpu == nullptr) { - fprintf(stderr, "Error: GPU requested but not available\n"); - return false; - } - devs[0] = gpu; - devs[1] = nullptr; // null terminator - mparams.devices = devs; - mparams.n_gpu_layers = 999; - } else if (std::string_view(args.device) == "cpu") { - ggml_backend_dev_t cpu = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU); - devs[0] = cpu; - devs[1] = nullptr; // null terminator - mparams.devices = devs; - } - - fprintf(stderr, "Using device: %s\n", ggml_backend_dev_name(devs[0])); - - model.reset(llama_model_load_from_file(args.model, mparams)); - - if (!model) { - fprintf(stderr, "Warning: failed to load model '%s', skipping test\n", args.model); - return false; - } - n_vocab = llama_vocab_n_tokens(get_vocab()); - fprintf(stderr, "Vocabulary size: %d\n", n_vocab); - - return true; - } - - bool setup(const backend_cli_args & args, std::vector & configs, int32_t n_seq_max = -1) { - if (!model) { - load_model(args); - } - - if (ctx) { - return true; - } + GGML_ASSERT(model); + GGML_ASSERT(!ctx); llama_context_params cparams = llama_context_default_params(); cparams.n_ctx = 512; @@ -99,26 +101,23 @@ struct test_model_context { cparams.n_seq_max = n_seq_max; } - ctx.reset(llama_init_from_model(model.get(), cparams)); + ctx.reset(llama_init_from_model(model, cparams)); if (!ctx) { - fprintf(stderr, "Warning: failed to create context, skipping test\n"); - return false; + throw std::runtime_error("failed to create context"); } + llama_set_warmup(ctx.get(), false); - return true; + vocab = llama_model_get_vocab(model); + n_vocab = llama_vocab_n_tokens(vocab); } bool decode(const std::map & prompts) { - if (!ctx) { - fprintf(stderr, "Error: context not initialized, call setup() first\n"); - return false; - } + GGML_ASSERT(ctx); last_batch_info.clear(); llama_batch batch = llama_batch_init(512, 0, prompts.size()); - auto vocab = get_vocab(); for (const auto & [seq_id, prompt] : prompts) { std::vector tokens; tokens.push_back(llama_vocab_bos(vocab)); @@ -199,10 +198,7 @@ struct test_model_context { } bool decode_token(llama_token token, llama_seq_id seq_id = 0) { - if (ctx == nullptr) { - fprintf(stderr, "Error: context not initialized, call setup() first\n"); - return false; - } + GGML_ASSERT(ctx); llama_batch batch = llama_batch_init(1, 0, 1); int32_t pos = seq_positions[seq_id]; @@ -218,14 +214,12 @@ struct test_model_context { seq_positions[seq_id]++; llama_batch_free(batch); + return true; } bool decode_tokens(const std::map & seq_tokens) { - if (ctx == nullptr) { - fprintf(stderr, "Error: context not initialized, call setup() first\n"); - return false; - } + GGML_ASSERT(ctx); llama_batch batch = llama_batch_init(seq_tokens.size(), 0, seq_tokens.size()); @@ -247,40 +241,27 @@ struct test_model_context { update_batch_info(batch); llama_batch_free(batch); + return true; } - std::string token_to_piece(llama_token token, bool special) { + std::string token_to_piece(llama_token token, bool special) const { std::string piece; piece.resize(piece.capacity()); // using string internal cache, 15 bytes + '\n' - const int n_chars = llama_token_to_piece(get_vocab(), token, &piece[0], piece.size(), 0, special); + const int n_chars = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special); if (n_chars < 0) { piece.resize(-n_chars); - int check = llama_token_to_piece(get_vocab(), token, &piece[0], piece.size(), 0, special); + int check = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special); GGML_ASSERT(check == -n_chars); - } - else { + } else { piece.resize(n_chars); } return piece; } - - void reset() { - ctx.reset(); - seq_positions.clear(); - last_batch_info.clear(); - } - - const llama_vocab * get_vocab() const { - return model ? llama_model_get_vocab(model.get()) : nullptr; - } - }; -static void test_backend_greedy_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_greedy_sampling(const test_params & params) { const int seq_id = 0; struct llama_sampler_chain_params backend_sampler_params = llama_sampler_chain_default_params(); @@ -289,9 +270,7 @@ static void test_backend_greedy_sampling(const backend_cli_args & args) { llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_greedy()); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Some"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -317,9 +296,7 @@ static void test_backend_greedy_sampling(const backend_cli_args & args) { } } -static void test_backend_top_k_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_top_k_sampling(const test_params & params) { const int seq_id = 0; const int32_t k = 8; struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); @@ -327,9 +304,7 @@ static void test_backend_top_k_sampling(const backend_cli_args & args) { llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_top_k(k)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Hello"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -358,16 +333,12 @@ static void test_backend_top_k_sampling(const backend_cli_args & args) { llama_sampler_chain_add(chain.get(), llama_sampler_init_dist(18)); llama_token token = llama_sampler_sample(chain.get(), test_ctx.ctx.get(), batch_idx); - const std::string token_str = test_ctx.token_to_piece(token, false); GGML_ASSERT(token >= 0 && token < test_ctx.n_vocab); printf("backend top-k hybrid sampling test PASSED\n"); } -static void test_backend_temp_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - - +static void test_backend_temp_sampling(const test_params & params) { { const float temp_0 = 0.8f; struct llama_sampler_chain_params backend_chain_params_0 = llama_sampler_chain_default_params(); @@ -384,9 +355,7 @@ static void test_backend_temp_sampling(const backend_cli_args & args) { { 1, backend_sampler_chain_1.get() } }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{0, "Some where over the"}, {1, "Once upon a"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -430,8 +399,6 @@ static void test_backend_temp_sampling(const backend_cli_args & args) { auto test_argmax_temp = [&](float temp) { printf("\nTesting temperature = %.1f\n", temp); - test_ctx.reset(); - int seq_id = 0; struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); @@ -441,9 +408,7 @@ static void test_backend_temp_sampling(const backend_cli_args & args) { { seq_id, backend_sampler_chain.get() }, }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Once"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -459,12 +424,9 @@ static void test_backend_temp_sampling(const backend_cli_args & args) { test_argmax_temp(-1.0f); printf("backend temp sampling test PASSED\n"); - } -static void test_backend_temp_ext_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_temp_ext_sampling(const test_params & params) { { int seq_id = 0; const float temp = 0.8f; @@ -478,9 +440,7 @@ static void test_backend_temp_ext_sampling(const backend_cli_args & args) { { seq_id, backend_sampler_chain.get() }, }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Once upon a"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -494,14 +454,10 @@ static void test_backend_temp_ext_sampling(const backend_cli_args & args) { } } - test_ctx.reset(); - // lambda to testing non-positive temp/delta/exponent values. auto test_argmax_temp = [&](float temp, float delta, float exponent) { printf("\nTesting temperature = %.1f, delta = %1.f, exponent = %1.f\n", temp, delta, exponent); - test_ctx.reset(); - int seq_id = 0; struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); @@ -511,9 +467,7 @@ static void test_backend_temp_ext_sampling(const backend_cli_args & args) { { seq_id, backend_sampler_chain.get() }, }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Once"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -535,12 +489,9 @@ static void test_backend_temp_ext_sampling(const backend_cli_args & args) { test_argmax_temp(0.8f, 0.0f, 2.0f); // Temperature scaling printf("backend temp_ext sampling test PASSED\n"); - } -static void test_backend_min_p_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_min_p_sampling(const test_params & params) { const int seq_id = 0; const float p = 0.1; struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); @@ -548,9 +499,7 @@ static void test_backend_min_p_sampling(const backend_cli_args & args) { llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_min_p(p, 0)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Hello"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -594,9 +543,7 @@ static void test_backend_min_p_sampling(const backend_cli_args & args) { printf("min-p sampling test PASSED\n"); } -static void test_backend_top_p_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_top_p_sampling(const test_params & params) { const int seq_id = 0; const float p = 0.9; struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); @@ -604,9 +551,7 @@ static void test_backend_top_p_sampling(const backend_cli_args & args) { llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_top_p(p, 0)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Hello"}})) { return; @@ -648,9 +593,7 @@ static void test_backend_top_p_sampling(const backend_cli_args & args) { printf("top-p sampling test PASSED\n"); } -static void test_backend_multi_sequence_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_multi_sequence_sampling(const test_params & params) { struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params(); llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0)); llama_sampler_chain_add(sampler_chain_0.get(), llama_sampler_init_greedy()); @@ -665,9 +608,7 @@ static void test_backend_multi_sequence_sampling(const backend_cli_args & args) { 1, sampler_chain_1.get() } }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); std::map prompts = { {0, "Hello"}, @@ -718,19 +659,16 @@ static void test_backend_multi_sequence_sampling(const backend_cli_args & args) printf("backend multi-sequence sampling test PASSED\n"); } -static void test_backend_dist_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_dist_sampling(const test_params & params) { const int seq_id = 189; const int32_t seed = 88; + struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Some"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -749,19 +687,16 @@ static void test_backend_dist_sampling(const backend_cli_args & args) { printf("backend dist sampling test PASSED\n"); } -static void test_backend_dist_sampling_and_cpu(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_dist_sampling_and_cpu(const test_params & params) { const int seq_id = 0; const int32_t seed = 88; + struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Some"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -782,31 +717,31 @@ static void test_backend_dist_sampling_and_cpu(const backend_cli_args & args) { printf("backend dist & cpu sampling test PASSED\n"); } -static void test_backend_logit_bias_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - - // Calling load_model to ensure vocab is loaded and can be accessed - if (!test_ctx.load_model(args)) { - return; - } +static void test_backend_logit_bias_sampling(const test_params & params) { + const auto * model = params.model.get(); + const auto * vocab = llama_model_get_vocab(model); const int seq_id = 0; - // Create the logit biases vector. std::vector logit_bias; // Get the token for the piece "World". const std::string piece = "World"; std::vector tokens(16); - llama_tokenize(test_ctx.get_vocab(), piece.c_str(), piece.size(), tokens.data(), tokens.size(), false, false); + llama_tokenize(vocab, piece.c_str(), piece.size(), tokens.data(), tokens.size(), false, false); + llama_token bias_token = tokens[0]; - logit_bias.push_back({ bias_token, +100.0f }); + // TODO: biasing too much here makes the Vulkan sampling fail - should be investigated further + // https://github.com/ggml-org/llama.cpp/actions/runs/20894267644/job/60030252675?pr=18753#step:3:23350 + //logit_bias.push_back({ bias_token, +100.0f }); + logit_bias.push_back({ bias_token, +10.0f }); + printf("biasing token piece '%s' -> token id %d\n", piece.c_str(), bias_token); struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_logit_bias( - llama_vocab_n_tokens(test_ctx.get_vocab()), + llama_vocab_n_tokens(vocab), logit_bias.size(), logit_bias.data())); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(88)); @@ -815,17 +750,14 @@ static void test_backend_logit_bias_sampling(const backend_cli_args & args) { { seq_id, backend_sampler_chain.get() }, }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Hello"}})) { GGML_ASSERT(false && "Failed to decode token"); } llama_token backend_token = llama_get_sampled_token_ith(test_ctx.ctx.get(), test_ctx.idx_for_seq(seq_id)); - const std::string backend_token_str = test_ctx.token_to_piece(backend_token, false); - printf("logit bias sampled token = %d, string='%s'\n", backend_token, backend_token_str.c_str()); + printf("sampled token = %d, expected = %d\n", backend_token, bias_token); GGML_ASSERT(backend_token == bias_token); printf("backend logit bias sampling test PASSED\n"); @@ -833,9 +765,7 @@ static void test_backend_logit_bias_sampling(const backend_cli_args & args) { // This test verifies that it is possible to have two different backend sampler, // one that uses the backend dist sampler, and another that uses CPU dist sampler. -static void test_backend_mixed_sampling(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_mixed_sampling(const test_params & params) { struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params(); llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0)); llama_sampler_chain_add(sampler_chain_0.get(), llama_sampler_init_dist(88)); @@ -850,9 +780,7 @@ static void test_backend_mixed_sampling(const backend_cli_args & args) { { 1, sampler_chain_1.get() } }; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); std::map prompts = { {0, "Hello"}, @@ -887,19 +815,16 @@ static void test_backend_mixed_sampling(const backend_cli_args & args) { printf("backend mixed sampling test PASSED\n"); } -static void test_backend_set_sampler(const backend_cli_args & args) { - test_model_context test_ctx; - - const int32_t seed = 88; +static void test_backend_set_sampler(const test_params & params) { const int seq_id = 0; + const int32_t seed = 88; + struct llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); if (!test_ctx.decode({{seq_id, "Hello"}})) { GGML_ASSERT(false && "Failed to decode token"); @@ -955,9 +880,7 @@ static void test_backend_set_sampler(const backend_cli_args & args) { printf("backend set sampler test PASSED\n"); } -static void test_backend_cpu_mixed_batch(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_cpu_mixed_batch(const test_params & params) { // Sequence 0 uses backend sampling struct llama_sampler_chain_params chain_params_0 = llama_sampler_chain_default_params(); llama_sampler_ptr sampler_chain_0(llama_sampler_chain_init(chain_params_0)); @@ -968,12 +891,10 @@ static void test_backend_cpu_mixed_batch(const backend_cli_args & args) { }; // We need 2 sequences: seq 0 with backend sampling, seq 1 with CPU sampling - if (!test_ctx.setup(args, backend_sampler_configs, 2)) { - return; - } + test_context test_ctx(params, backend_sampler_configs, 2); std::map prompts = { - {0, "Hello"}, // Will use backend sampling + {0, "Hello"}, // Will use backend sampling {1, "Some"} // Will use CPU sampling }; @@ -1047,28 +968,25 @@ static void test_backend_cpu_mixed_batch(const backend_cli_args & args) { printf("backend-cpu mixed batch test PASSED\n"); } -static void test_backend_max_outputs(const backend_cli_args & args) { - test_model_context test_ctx; - +static void test_backend_max_outputs(const test_params & params) { const int seq_id = 0; const int32_t seed = 88; + llama_sampler_chain_params backend_chain_params = llama_sampler_chain_default_params(); llama_sampler_ptr backend_sampler_chain(llama_sampler_chain_init(backend_chain_params)); llama_sampler_chain_add(backend_sampler_chain.get(), llama_sampler_init_dist(seed)); std::vector backend_sampler_configs = {{ seq_id, backend_sampler_chain.get() }}; - if (!test_ctx.setup(args, backend_sampler_configs)) { - return; - } + test_context test_ctx(params, backend_sampler_configs); llama_batch batch = llama_batch_init(512, 0, 1); std::string prompt = "Hello"; std::vector tokens; - tokens.push_back(llama_vocab_bos(test_ctx.get_vocab())); + tokens.push_back(llama_vocab_bos(test_ctx.vocab)); std::vector prompt_tokens(32); - int n_tokens = llama_tokenize(test_ctx.get_vocab(), prompt.c_str(), prompt.length(), + int n_tokens = llama_tokenize(test_ctx.vocab, prompt.c_str(), prompt.length(), prompt_tokens.data(), prompt_tokens.size(), false, false); for (int i = 0; i < n_tokens; i++) { @@ -1090,8 +1008,8 @@ static void test_backend_max_outputs(const backend_cli_args & args) { } struct backend_test_case { - const char * name; - void (*fn)(const backend_cli_args &); + std::string name; + void (*fn)(const test_params &); bool enabled_by_default; }; @@ -1112,8 +1030,8 @@ static const backend_test_case BACKEND_TESTS[] = { { "top_p", test_backend_top_p_sampling, true }, }; -static backend_cli_args parse_backend_cli(int argc, char ** argv) { - backend_cli_args out; +static test_args parse_cli(int argc, char ** argv) { + test_args out; for (int i = 1; i < argc; ++i) { const char * arg = argv[i]; @@ -1154,7 +1072,7 @@ static backend_cli_args parse_backend_cli(int argc, char ** argv) { out.device = arg + 9; continue; } - if (!out.model) { + if (out.model.empty()) { out.model = arg; continue; } @@ -1163,28 +1081,28 @@ static backend_cli_args parse_backend_cli(int argc, char ** argv) { exit(EXIT_FAILURE); } - if (std::strcmp(out.device, "cpu") != 0 && std::strcmp(out.device, "gpu") != 0) { - fprintf(stderr, "Invalid device '%s'. Must be 'cpu' or 'gpu'\n", out.device); + if (out.device != "cpu" && out.device != "gpu" && out.device != "auto") { + fprintf(stderr, "Invalid device '%s'. Must be 'cpu', 'gpu' or 'auto'\n", out.device.c_str()); exit(EXIT_FAILURE); } return out; } -static std::vector collect_tests_to_run(const char * requested) { +static std::vector collect_tests_to_run(const std::string & requested) { std::vector selected; - if (requested != nullptr) { + if (!requested.empty()) { for (const auto & test : BACKEND_TESTS) { - if (std::strcmp(test.name, requested) == 0) { + if (test.name == requested) { selected.push_back(&test); break; } } if (selected.empty()) { - fprintf(stderr, "Unknown test '%s'. Available tests:\n", requested); + fprintf(stderr, "Unknown test '%s'. Available tests:\n", requested.c_str()); for (const auto & test : BACKEND_TESTS) { - fprintf(stderr, " %s\n", test.name); + fprintf(stderr, " %s\n", test.name.c_str()); } exit(EXIT_FAILURE); } @@ -1203,34 +1121,44 @@ static std::vector collect_tests_to_run(const char * return selected; } -static void run_tests(const std::vector & tests, const backend_cli_args & args) { - for (const auto * test : tests) { - fprintf(stderr, "\n=== %s ===\n", test->name); - test->fn(args); +static void run_tests(const std::vector & tests, const test_params & args) { + for (const auto & test : tests) { + fprintf(stderr, "\n=== %s ===\n", test->name.c_str()); + try { + test->fn(args); + } catch (const std::exception & e) { + fprintf(stderr, "Error running test '%s': %s\n", test->name.c_str(), e.what()); + exit(EXIT_FAILURE); + } } } - int main(int argc, char ** argv) { - backend_cli_args args = parse_backend_cli(argc, argv); + test_args args = parse_cli(argc, argv); - if (args.model == nullptr) { + if (args.model.empty()) { args.model = get_model_or_exit(1, argv); } - std::ifstream file(args.model); - if (!file.is_open()) { - fprintf(stderr, "no model '%s' found\n", args.model); - return EXIT_FAILURE; + { + std::ifstream file(args.model); + if (!file.is_open()) { + fprintf(stderr, "no model '%s' found\n", args.model.c_str()); + return EXIT_FAILURE; + } } - fprintf(stderr, "using '%s'\n", args.model); + fprintf(stderr, "using '%s'\n", args.model.c_str()); - ggml_time_init(); + llama_backend_init(); + + test_params params = { + /*.model =*/ load_model(args), + }; const std::vector tests = collect_tests_to_run(args.test); if (!tests.empty()) { - run_tests(tests, args); + run_tests(tests, params); } return 0; diff --git a/tools/mtmd/clip.cpp b/tools/mtmd/clip.cpp index f7d128212..3b53cd811 100644 --- a/tools/mtmd/clip.cpp +++ b/tools/mtmd/clip.cpp @@ -4205,7 +4205,7 @@ bool clip_is_glm(const struct clip_ctx * ctx) { return ctx->proj_type() == PROJECTOR_TYPE_GLM_EDGE; } -bool clip_is_mrope(const struct clip_ctx * ctx) { +bool clip_is_mrope(const struct clip_ctx * ctx) { //kcpp: this was removed in https://github.com/ggml-org/llama.cpp/pull/18793 and moved to mtmd_decode_use_mrope switch (ctx->proj_type()) { case PROJECTOR_TYPE_QWEN2VL: case PROJECTOR_TYPE_QWEN25VL: diff --git a/tools/mtmd/mtmd.cpp b/tools/mtmd/mtmd.cpp index b68de7429..f25706987 100644 --- a/tools/mtmd/mtmd.cpp +++ b/tools/mtmd/mtmd.cpp @@ -146,8 +146,6 @@ struct mtmd_context { bool tok_row_end_trail = false; bool ov_img_first = false; - bool use_mrope = false; // for Qwen2VL, we need to use M-RoPE - // string template for slice image delimiters with row/col (idefics3) std::string sli_img_start_tmpl; @@ -217,7 +215,6 @@ struct mtmd_context { void init_vision() { GGML_ASSERT(ctx_v != nullptr); - use_mrope = clip_is_mrope(ctx_v); projector_type proj = clip_get_projector_type(ctx_v); int minicpmv_version = clip_is_minicpmv(ctx_v); @@ -627,7 +624,7 @@ struct mtmd_tokenizer { } mtmd_image_tokens_ptr image_tokens(new mtmd_image_tokens); - if (ctx->use_mrope) { + if (mtmd_decode_use_mrope(ctx)) { // for Qwen2VL, we need this information for M-RoPE decoding positions image_tokens->nx = clip_n_output_tokens_x(ctx->ctx_v, batch_f32.entries[0].get()); image_tokens->ny = clip_n_output_tokens_y(ctx->ctx_v, batch_f32.entries[0].get()); @@ -863,10 +860,7 @@ float * mtmd_get_output_embd(mtmd_context * ctx) { bool mtmd_decode_use_non_causal(mtmd_context * ctx) { switch (ctx->proj_type_v()) { - case PROJECTOR_TYPE_QWEN2VL: - case PROJECTOR_TYPE_QWEN25VL: - case PROJECTOR_TYPE_QWEN3VL: - case PROJECTOR_TYPE_YOUTUVL: + case PROJECTOR_TYPE_GEMMA3: return true; default: return false; @@ -874,7 +868,15 @@ bool mtmd_decode_use_non_causal(mtmd_context * ctx) { } bool mtmd_decode_use_mrope(mtmd_context * ctx) { - return ctx->use_mrope; + switch (ctx->proj_type_v()) { + case PROJECTOR_TYPE_QWEN2VL: + case PROJECTOR_TYPE_QWEN25VL: + case PROJECTOR_TYPE_QWEN3VL: + case PROJECTOR_TYPE_GLM4V: + return true; + default: + return false; + } } bool mtmd_support_vision(mtmd_context * ctx) { diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp index 324c3af30..af6e05342 100644 --- a/tools/server/server-context.cpp +++ b/tools/server/server-context.cpp @@ -79,6 +79,8 @@ struct server_slot { common_speculative * spec = nullptr; + // TODO: move members that belong to the task (such as `generated_text`, `has_new_line`) to task_results_state + // see https://github.com/ggml-org/llama.cpp/pull/18283#issuecomment-3710175837 std::unique_ptr task; std::unique_ptr task_prev; // used for debugging @@ -153,7 +155,7 @@ struct server_slot { common_sampler_ptr smpl; - llama_token sampled; // in speculative mode, this is the last accepted token + llama_token sampled; // in speculative mode, this is the last accepted token llama_tokens drafted; // stats @@ -201,12 +203,46 @@ struct server_slot { alora_invocation_start = -1; } + // remove cached prompt + tokens + void clear(bool allow_processing) { + if (!allow_processing) { + GGML_ASSERT(!is_processing()); + } + + SLT_INF(*this, "clearing slot with %zu tokens\n", prompt.tokens.size()); + + llama_memory_seq_rm(llama_get_memory(ctx), id, -1, -1); + prompt.tokens.clear(); + } + + void init_sampler() const { + const int64_t t_start = ggml_time_us(); + + common_sampler_reset(smpl.get()); + + int n_text = 0; + + for (int i = 0; i < (int) prompt.tokens.size(); i++) { + const llama_token id = prompt.tokens[i]; + + if (id != LLAMA_TOKEN_NULL) { + common_sampler_accept(smpl.get(), id, false); + n_text++; + } + } + + SLT_INF(*this, "init sampler, took %0.2f ms, tokens: text = %d, total = %d\n", + (ggml_time_us() - t_start) / 1000.0, n_text, (int) prompt.tokens.size()); + } + + // TODO: move to server_task bool need_embd() const { GGML_ASSERT(task); return server_task_type_need_embd(task->type); } + // TODO: move to server_task bool need_logits() const { GGML_ASSERT(task); @@ -258,10 +294,13 @@ struct server_slot { SLT_WRN(*this, "%s", "slot is not processing\n"); return; } + generated_token_probs.push_back(token); } int get_n_draft_max() const { + GGML_ASSERT(task); + if (!can_speculate()) { return 0; } @@ -287,12 +326,14 @@ struct server_slot { } // note: a slot can also be either a parent or a child + // TODO: move to server_task bool is_parent() const { - return is_processing() && task->n_children > 0; + return task->n_children > 0; } + // TODO: move to server_task bool is_child() const { - return is_processing() && task->id_parent >= 0; + return task->id_parent >= 0; } void release() { @@ -301,10 +342,16 @@ struct server_slot { SLT_INF(*this, "stop processing: n_tokens = %d, truncated = %d\n", prompt.n_tokens(), truncated); - t_last_used = ggml_time_us(); + t_last_used = ggml_time_us(); t_token_generation = (ggml_time_us() - t_start_generation) / 1e3; + state = SLOT_STATE_IDLE; + // do not keep context of the child slots - the parent's context is enough + if (is_child()) { + clear(false); + } + task_prev = std::move(task); task.reset(); @@ -425,14 +472,22 @@ struct server_slot { } void copy_state_to(server_slot & other) const { - llama_memory_seq_rm(llama_get_memory(ctx), other.id, 0, -1); - llama_memory_seq_cp(llama_get_memory(ctx), id, other.id, 0, -1); + GGML_ASSERT(state == SLOT_STATE_DONE_PROMPT); + + llama_memory_seq_rm(llama_get_memory(ctx), other.id, -1, -1); + llama_memory_seq_cp(llama_get_memory(ctx), id, other.id, -1, -1); + other.n_decoded = n_decoded; other.n_remaining = n_remaining; other.i_batch = i_batch; + + other.t_start_process_prompt = t_start_process_prompt; + other.t_prompt_processing = t_prompt_processing; other.n_prompt_tokens_cache = n_prompt_tokens_cache; other.n_prompt_tokens_processed = n_prompt_tokens_processed; + other.prompt = prompt.clone(); + other.init_sampler(); } }; @@ -745,6 +800,7 @@ private: } slots.clear(); + for (int i = 0; i < params_base.n_parallel; i++) { server_slot slot; @@ -993,7 +1049,7 @@ private: ret->prompt_save(*prompt_cache); if (!ret->prompt_load(*prompt_cache, task.tokens)) { - clear_slot(*ret); + ret->clear(false); } prompt_cache->update(); @@ -1005,17 +1061,6 @@ private: return ret; } - void clear_slot(server_slot & slot, bool allow_processing = false) const { - if (!allow_processing) { - GGML_ASSERT(!slot.is_processing()); - } - - SLT_WRN(slot, "clearing slot with %zu tokens\n", slot.prompt.tokens.size()); - - llama_memory_seq_rm(llama_get_memory(ctx), slot.id, -1, -1); - slot.prompt.tokens.clear(); - } - // return true if at least one slot has been cleared // TODO: improve logic // - smarter decision which slot to clear (LRU or longest prompt?) @@ -1036,7 +1081,7 @@ private: if (slot.prompt.n_tokens() > 0) { SRV_WRN("purging slot %d with %zu tokens\n", slot.id, slot.prompt.tokens.size()); - clear_slot(slot); + slot.clear(false); res = true; @@ -1182,7 +1227,7 @@ private: ? SLOT_STATE_WAIT_OTHER // wait for the parent to process prompt : SLOT_STATE_STARTED; - SLT_INF(slot, "%s", "processing task\n"); + SLT_INF(slot, "processing task, is_child = %d\n", slot.is_child()); return true; } @@ -1819,7 +1864,7 @@ private: // Erase token cache const size_t n_erased = slot->prompt.tokens.size(); - clear_slot(*slot); + slot->clear(false); auto res = std::make_unique(); res->id = task.id; @@ -2053,8 +2098,29 @@ private: continue; } + // check if this is a child slot + if (slot.state == SLOT_STATE_WAIT_OTHER) { + SLT_DBG(slot, "%s", "waiting for parent slot to complete\n"); + continue; + } + // this slot still has a prompt to be processed if (slot.state == SLOT_STATE_PROCESSING_PROMPT || slot.state == SLOT_STATE_STARTED) { + // wait for all children to be launched + if (slot.is_parent()) { + int n_launched = 0; + for (auto & other : slots) { + if (other.is_processing() && other.is_child() && other.task->id_parent == slot.task->id) { + ++n_launched; + } + } + + if (n_launched < slot.task->n_children) { + SLT_DBG(slot, "waiting for children to be launched, n_children = %d, n_launched = %d\n", slot.task->n_children, n_launched); + continue; + } + } + const auto & input_tokens = slot.task->tokens; // TODO: maybe move branch to outside of this loop in the future @@ -2355,7 +2421,7 @@ private: if (!llama_memory_seq_rm(llama_get_memory(ctx), slot.id, p0, -1)) { SLT_WRN(slot, "failed to truncate tokens with position >= %d - clearing the memory\n", p0); - clear_slot(slot, /*allow_processing=*/true); + slot.clear(true); // there is no common part left slot.n_prompt_tokens_cache = 0; @@ -2455,16 +2521,6 @@ private: GGML_ASSERT(batch.n_tokens > 0); - common_sampler_reset(slot.smpl.get()); - - // Process all prompt tokens through sampler system - for (int i = 0; i < slot.task->n_tokens(); ++i) { - llama_token id = input_tokens[i]; - if (id != LLAMA_TOKEN_NULL) { - common_sampler_accept(slot.smpl.get(), id, false); - } - } - // extract the logits only for the last token batch.logits[batch.n_tokens - 1] = true; @@ -2473,6 +2529,8 @@ private: SLT_INF(slot, "prompt done, n_tokens = %d, batch.n_tokens = %d\n", slot.prompt.n_tokens(), batch.n_tokens); + slot.init_sampler(); + const auto pos_min = llama_memory_seq_pos_min(llama_get_memory(ctx), slot.id); const auto pos_max = llama_memory_seq_pos_max(llama_get_memory(ctx), slot.id); @@ -2519,11 +2577,6 @@ private: } } - if (batch.n_tokens == 0) { - SRV_WRN("%s", "no tokens to decode\n"); - return; - } - SRV_DBG("decoding batch, n_tokens = %d\n", batch.n_tokens); if (slot_batched) { @@ -2540,6 +2593,10 @@ private: llama_set_embeddings(ctx, slot_batched->need_embd()); } + if (batch.n_tokens == 0) { + SRV_WRN("%s", "no tokens to decode\n"); + } + int32_t i_next = 0; // process the created batch of tokens @@ -2591,7 +2648,7 @@ private: // note: it's complicated to keep track of how much of the current batch has been // processed before the error occurred, so we simply clear the entire context - clear_slot(slot); + slot.clear(false); } } @@ -2615,27 +2672,34 @@ private: // on successful decode, restore the original batch size n_batch = llama_n_batch(ctx); + // handle `n_cmpl > 1` tasks - when the main prompt is processed, activate all child tasks too for (auto & slot : slots) { - // may need to copy state to other slots if (slot.state == SLOT_STATE_DONE_PROMPT && slot.is_parent()) { - std::vector child_slots; + SLT_INF(slot, "parent task prompt done, n_children = %d\n", slot.task->n_children); + + std::vector children; for (auto & other : slots) { if (other.state == SLOT_STATE_WAIT_OTHER && slot.task->id == other.task->id_parent) { - child_slots.push_back(&other); + children.push_back(&other); } } // we can only proceed if all child slots are having the correct tasks - if (child_slots.size() == slot.task->n_children) { + if (slot.task->n_children == (int) children.size()) { // copy state to the child slots - for (auto & child : child_slots) { - SLT_INF(slot, "copying state to child %d\n", child->id); + for (auto & child : children) { + SLT_INF(slot, " - copying state to child %d\n", child->id); + + GGML_ASSERT(child->state == SLOT_STATE_WAIT_OTHER); + slot.copy_state_to(*child); child->state = SLOT_STATE_DONE_PROMPT; } } } + } + for (auto & slot : slots) { // optionally send prompt processing progress if (slot.state == SLOT_STATE_PROCESSING_PROMPT || slot.state == SLOT_STATE_DONE_PROMPT) { if (slot.task->params.stream && slot.task->params.return_progress) { @@ -2720,7 +2784,7 @@ private: continue; } - size_t n_draft = slot.drafted.size(); + const size_t n_draft = slot.drafted.size(); // the accepted tokens from the speculation const auto ids = common_sampler_sample_and_accept_n(slot.smpl.get(), ctx, slot.i_batch_dft, slot.drafted); @@ -2923,9 +2987,11 @@ std::unique_ptr server_routes::handle_completions_impl( task.params.oaicompat_cmpl_id = completion_id; task.params.oaicompat_model = meta->model_name; + // prepare child tasks if (task.params.n_cmpl > 1) { task.n_children = task.params.n_cmpl - 1; - for (size_t j = 0; j < task.n_children; j++) { + + for (int j = 0; j < task.n_children; j++) { server_task child = task.create_child(task.id, rd.get_new_id()); // use different sampling seed for each child @@ -2938,7 +3004,8 @@ std::unique_ptr server_routes::handle_completions_impl( } } - tasks.push_back(std::move(task)); + // note: the parent task always launches first + tasks.insert(tasks.begin(), std::move(task)); } rd.post_tasks(std::move(tasks)); diff --git a/tools/server/server-task.cpp b/tools/server/server-task.cpp index ed4f6546e..aa4590e4e 100644 --- a/tools/server/server-task.cpp +++ b/tools/server/server-task.cpp @@ -160,6 +160,7 @@ task_params server_task::params_from_json_cmpl( defaults.n_keep = params_base.n_keep; defaults.n_predict = params_base.n_predict; defaults.n_cache_reuse = params_base.n_cache_reuse; + defaults.cache_prompt = params_base.cache_prompt; defaults.antiprompt = params_base.antiprompt; // enabling this will output extra debug information in the HTTP responses from the server @@ -169,7 +170,7 @@ task_params server_task::params_from_json_cmpl( params.stream = json_value(data, "stream", false); auto stream_opt = json_value(data, "stream_options", json::object()); params.include_usage = json_value(stream_opt, "include_usage", false); - params.cache_prompt = json_value(data, "cache_prompt", true); + params.cache_prompt = json_value(data, "cache_prompt", defaults.cache_prompt); params.return_tokens = json_value(data, "return_tokens", false); params.return_progress = json_value(data, "return_progress", false); params.n_predict = json_value(data, "n_predict", json_value(data, "max_tokens", defaults.n_predict)); diff --git a/tools/server/server-task.h b/tools/server/server-task.h index ead149118..cf08fced6 100644 --- a/tools/server/server-task.h +++ b/tools/server/server-task.h @@ -121,8 +121,8 @@ struct server_task { int id_slot = -1; // used by parallel sampling (multiple completions from same prompt) - size_t n_children = 0; // number of tasks reusing this prompt - int id_parent = -1; + int n_children = 0; // number of tasks reusing this prompt + int id_parent = -1; // used by SERVER_TASK_TYPE_INFERENCE task_params params; @@ -173,11 +173,13 @@ struct server_task { server_task create_child(int id_parent, int id_child) const { server_task copy; + copy.id = id_child; copy.id_parent = id_parent; copy.params = params; copy.type = type; copy.tokens = tokens.clone(); + return copy; } diff --git a/tools/server/tests/unit/test_completion.py b/tools/server/tests/unit/test_completion.py index ef1757db2..2a980601e 100644 --- a/tools/server/tests/unit/test_completion.py +++ b/tools/server/tests/unit/test_completion.py @@ -393,12 +393,12 @@ def test_completion_unified(n_ctx, n_slots, n_predict_vals, expected_success): for res, n_predict, expect_ok in zip(results, n_predict_vals, expected_success): if expect_ok: assert res.status_code == 200 + + # note: https://github.com/ggml-org/llama.cpp/pull/18700#issuecomment-3728695581 + if res.status_code == 200: assert "content" in res.body if "timings" in res.body: assert res.body["timings"]["predicted_n"] == n_predict - else: - assert res.status_code == 500 - assert "content" not in res.body @pytest.mark.parametrize( diff --git a/vendor/cpp-httplib/CMakeLists.txt b/vendor/cpp-httplib/CMakeLists.txt index 8f0d15d1f..172b92545 100644 --- a/vendor/cpp-httplib/CMakeLists.txt +++ b/vendor/cpp-httplib/CMakeLists.txt @@ -1,4 +1,5 @@ set(TARGET cpp-httplib) +license_add_file("cpp-httplib" "LICENSE") find_package(Threads REQUIRED) @@ -8,7 +9,7 @@ if (NOT MSVC) target_compile_options(${TARGET} PRIVATE -w) endif() -target_link_libraries (${TARGET} PRIVATE Threads::Threads) +target_link_libraries(${TARGET} PRIVATE Threads::Threads) if (WIN32 AND NOT MSVC) target_link_libraries(${TARGET} PRIVATE ws2_32) @@ -67,6 +68,8 @@ if (LLAMA_BUILD_BORINGSSL) set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS}) set(BUILD_TESTING ${SAVED_BUILD_TESTING}) + license_add_file("BoringSSL" "${boringssl_SOURCE_DIR}/LICENSE") + set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE) target_link_libraries(${TARGET} PUBLIC ssl crypto) @@ -108,6 +111,8 @@ elseif (LLAMA_BUILD_LIBRESSL) set(BUILD_SHARED_LIBS ${SAVED_BUILD_SHARED_LIBS}) set(BUILD_TESTING ${SAVED_BUILD_TESTING}) + license_add_file("LibreSSL" "${libressl_SOURCE_DIR}/COPYING") + set(CPPHTTPLIB_OPENSSL_SUPPORT TRUE) target_link_libraries(${TARGET} PUBLIC ssl crypto) diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index a437a36ed..d707e65fd 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -1138,6 +1138,7 @@ int getaddrinfo_with_timeout(const char *node, const char *service, return ret; #elif TARGET_OS_MAC + if (!node) { return EAI_NONAME; } // macOS implementation using CFHost API for asynchronous DNS resolution CFStringRef hostname_ref = CFStringCreateWithCString( kCFAllocatorDefault, node, kCFStringEncodingUTF8); @@ -5569,14 +5570,11 @@ bool Server::read_content(Stream &strm, Request &req, Response &res) { strm, req, res, // Regular [&](const char *buf, size_t n) { - // Prevent arithmetic overflow when checking sizes. - // Avoid computing (req.body.size() + n) directly because - // adding two unsigned `size_t` values can wrap around and - // produce a small result instead of indicating overflow. - // Instead, check using subtraction: ensure `n` does not - // exceed the remaining capacity `max_size() - size()`. - if (req.body.size() >= req.body.max_size() || - n > req.body.max_size() - req.body.size()) { + // Limit decompressed body size to payload_max_length_ to protect + // against "zip bomb" attacks where a small compressed payload + // decompresses to a massive size. + if (req.body.size() + n > payload_max_length_ || + req.body.size() + n > req.body.max_size()) { return false; } req.body.append(buf, n); diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index 43cdbc583..613020d12 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.30.0" -#define CPPHTTPLIB_VERSION_NUM "0x001E00" +#define CPPHTTPLIB_VERSION "0.30.1" +#define CPPHTTPLIB_VERSION_NUM "0x001E01" /* * Platform compatibility check @@ -205,7 +205,10 @@ #pragma comment(lib, "ws2_32.lib") +#ifndef _SSIZE_T_DEFINED using ssize_t = __int64; +#define _SSIZE_T_DEFINED +#endif #endif // _MSC_VER #ifndef S_ISREG @@ -2443,16 +2446,20 @@ namespace detail { #if defined(_WIN32) inline std::wstring u8string_to_wstring(const char *s) { - std::wstring ws; + if (!s) { return std::wstring(); } + auto len = static_cast(strlen(s)); + if (!len) { return std::wstring(); } + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); - if (wlen > 0) { - ws.resize(wlen); - wlen = ::MultiByteToWideChar( - CP_UTF8, 0, s, len, - const_cast(reinterpret_cast(ws.data())), wlen); - if (wlen != static_cast(ws.size())) { ws.clear(); } - } + if (!wlen) { return std::wstring(); } + + std::wstring ws; + ws.resize(wlen); + wlen = ::MultiByteToWideChar( + CP_UTF8, 0, s, len, + const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { ws.clear(); } return ws; } #endif