|
36 | 36 | #include <cstdlib> |
37 | 37 | #include <fstream> |
38 | 38 | #include <iterator> |
| 39 | +#include <list> |
| 40 | +#include <map> |
39 | 41 | #include <mutex> |
40 | 42 | #include <signal.h> |
41 | 43 | #include <thread> |
@@ -443,10 +445,22 @@ static void params_parse(server_context& /*ctx_server*/, const backend::ModelOpt |
443 | 445 | // Draft model for speculative decoding |
444 | 446 | if (!request->draftmodel().empty()) { |
445 | 447 | params.speculative.draft.mparams.path = request->draftmodel(); |
446 | | - // Default to draft type if a draft model is set but no explicit type |
| 448 | + // Default to draft type if a draft model is set but no explicit type. |
| 449 | + // Upstream (post ggml-org/llama.cpp#22838) made the speculative type a |
| 450 | + // vector; the turboquant fork still uses the legacy scalar. The |
| 451 | + // LOCALAI_LEGACY_LLAMA_CPP_SPEC macro is injected by |
| 452 | + // backend/cpp/turboquant/patch-grpc-server.sh for fork builds only. |
| 453 | +#ifdef LOCALAI_LEGACY_LLAMA_CPP_SPEC |
447 | 454 | if (params.speculative.type == COMMON_SPECULATIVE_TYPE_NONE) { |
448 | 455 | params.speculative.type = COMMON_SPECULATIVE_TYPE_DRAFT; |
449 | 456 | } |
| 457 | +#else |
| 458 | + const bool no_spec_type = params.speculative.types.empty() || |
| 459 | + (params.speculative.types.size() == 1 && params.speculative.types[0] == COMMON_SPECULATIVE_TYPE_NONE); |
| 460 | + if (no_spec_type) { |
| 461 | + params.speculative.types = { COMMON_SPECULATIVE_TYPE_DRAFT }; |
| 462 | + } |
| 463 | +#endif |
450 | 464 | } |
451 | 465 |
|
452 | 466 | // params.model_alias ?? |
@@ -673,10 +687,35 @@ static void params_parse(server_context& /*ctx_server*/, const backend::ModelOpt |
673 | 687 | } |
674 | 688 | // Speculative decoding options |
675 | 689 | } else if (!strcmp(optname, "spec_type") || !strcmp(optname, "speculative_type")) { |
676 | | - auto type = common_speculative_type_from_name(optval_str); |
| 690 | +#ifdef LOCALAI_LEGACY_LLAMA_CPP_SPEC |
| 691 | + // Fork only knows a single scalar `type`. Take the first comma- |
| 692 | + // separated value and assign it via the singular helper. |
| 693 | + std::string first = optval_str; |
| 694 | + const auto comma = first.find(','); |
| 695 | + if (comma != std::string::npos) first = first.substr(0, comma); |
| 696 | + auto type = common_speculative_type_from_name(first); |
677 | 697 | if (type != COMMON_SPECULATIVE_TYPE_COUNT) { |
678 | 698 | params.speculative.type = type; |
679 | 699 | } |
| 700 | +#else |
| 701 | + // Upstream switched to a vector of types (comma-separated for multi-type |
| 702 | + // chaining via common_speculative_types_from_names). We keep accepting a |
| 703 | + // single value here, but also tolerate comma-separated lists. |
| 704 | + std::vector<std::string> names; |
| 705 | + std::string item; |
| 706 | + for (char c : optval_str) { |
| 707 | + if (c == ',') { |
| 708 | + if (!item.empty()) { names.push_back(item); item.clear(); } |
| 709 | + } else { |
| 710 | + item.push_back(c); |
| 711 | + } |
| 712 | + } |
| 713 | + if (!item.empty()) names.push_back(item); |
| 714 | + auto parsed = common_speculative_types_from_names(names); |
| 715 | + if (!parsed.empty()) { |
| 716 | + params.speculative.types = parsed; |
| 717 | + } |
| 718 | +#endif |
680 | 719 | } else if (!strcmp(optname, "spec_n_max") || !strcmp(optname, "draft_max")) { |
681 | 720 | if (optval != NULL) { |
682 | 721 | try { params.speculative.draft.n_max = std::stoi(optval_str); } catch (...) {} |
@@ -710,10 +749,155 @@ static void params_parse(server_context& /*ctx_server*/, const backend::ModelOpt |
710 | 749 | try { params.speculative.draft.n_gpu_layers = std::stoi(optval_str); } catch (...) {} |
711 | 750 | } |
712 | 751 | } else if (!strcmp(optname, "draft_ctx_size")) { |
| 752 | + // The draft context size is no longer a separate field upstream: the draft |
| 753 | + // shares the target context size. Accept the option for backward |
| 754 | + // compatibility but silently ignore it. |
| 755 | + |
| 756 | +// Everything below relies on struct shape introduced in ggml-org/llama.cpp#22838 |
| 757 | +// (parallel drafting): `ngram_mod`, `ngram_map_k`, `ngram_map_k4v`, |
| 758 | +// `ngram_cache`, and the `draft.{cache_type_*, cpuparams*, tensor_buft_overrides}` |
| 759 | +// fields. The turboquant fork branched before that, so its build defines |
| 760 | +// LOCALAI_LEGACY_LLAMA_CPP_SPEC via patch-grpc-server.sh and these option |
| 761 | +// keys become unrecognized (silently dropped, like any unknown opt) for it. |
| 762 | +// |
| 763 | +// The `#ifdef LOCALAI_LEGACY_LLAMA_CPP_SPEC` / `#else` split below sits at the |
| 764 | +// closing-brace position of the `draft_ctx_size` branch on purpose: in the |
| 765 | +// legacy build the chain ends here (the brace closes draft_ctx_size), and in |
| 766 | +// the modern build the chain continues with `} else if (...)` instead, so the |
| 767 | +// brace count stays balanced under both branches of the preprocessor. |
| 768 | +#ifdef LOCALAI_LEGACY_LLAMA_CPP_SPEC |
| 769 | + } |
| 770 | +#else |
| 771 | + // --- ngram_mod family (upstream --spec-ngram-mod-*) --- |
| 772 | + } else if (!strcmp(optname, "spec_ngram_mod_n_min")) { |
713 | 773 | if (optval != NULL) { |
714 | | - try { params.speculative.draft.n_ctx = std::stoi(optval_str); } catch (...) {} |
| 774 | + try { params.speculative.ngram_mod.n_min = std::stoi(optval_str); } catch (...) {} |
715 | 775 | } |
716 | | - } |
| 776 | + } else if (!strcmp(optname, "spec_ngram_mod_n_max")) { |
| 777 | + if (optval != NULL) { |
| 778 | + try { params.speculative.ngram_mod.n_max = std::stoi(optval_str); } catch (...) {} |
| 779 | + } |
| 780 | + } else if (!strcmp(optname, "spec_ngram_mod_n_match")) { |
| 781 | + if (optval != NULL) { |
| 782 | + try { params.speculative.ngram_mod.n_match = std::stoi(optval_str); } catch (...) {} |
| 783 | + } |
| 784 | + |
| 785 | + // --- ngram_map_k family (upstream --spec-ngram-map-k-*) --- |
| 786 | + } else if (!strcmp(optname, "spec_ngram_map_k_size_n")) { |
| 787 | + if (optval != NULL) { |
| 788 | + try { params.speculative.ngram_map_k.size_n = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 789 | + } |
| 790 | + } else if (!strcmp(optname, "spec_ngram_map_k_size_m")) { |
| 791 | + if (optval != NULL) { |
| 792 | + try { params.speculative.ngram_map_k.size_m = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 793 | + } |
| 794 | + } else if (!strcmp(optname, "spec_ngram_map_k_min_hits")) { |
| 795 | + if (optval != NULL) { |
| 796 | + try { params.speculative.ngram_map_k.min_hits = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 797 | + } |
| 798 | + |
| 799 | + // --- ngram_map_k4v family (upstream --spec-ngram-map-k4v-*) --- |
| 800 | + } else if (!strcmp(optname, "spec_ngram_map_k4v_size_n")) { |
| 801 | + if (optval != NULL) { |
| 802 | + try { params.speculative.ngram_map_k4v.size_n = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 803 | + } |
| 804 | + } else if (!strcmp(optname, "spec_ngram_map_k4v_size_m")) { |
| 805 | + if (optval != NULL) { |
| 806 | + try { params.speculative.ngram_map_k4v.size_m = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 807 | + } |
| 808 | + } else if (!strcmp(optname, "spec_ngram_map_k4v_min_hits")) { |
| 809 | + if (optval != NULL) { |
| 810 | + try { params.speculative.ngram_map_k4v.min_hits = (uint16_t)std::stoi(optval_str); } catch (...) {} |
| 811 | + } |
| 812 | + |
| 813 | + // --- ngram lookup caches (upstream --lookup-cache-static / -dynamic) --- |
| 814 | + } else if (!strcmp(optname, "spec_lookup_cache_static") || !strcmp(optname, "lookup_cache_static")) { |
| 815 | + params.speculative.ngram_cache.lookup_cache_static = optval_str; |
| 816 | + } else if (!strcmp(optname, "spec_lookup_cache_dynamic") || !strcmp(optname, "lookup_cache_dynamic")) { |
| 817 | + params.speculative.ngram_cache.lookup_cache_dynamic = optval_str; |
| 818 | + |
| 819 | + // --- draft model KV cache types (upstream --spec-draft-type-k / -v) --- |
| 820 | + } else if (!strcmp(optname, "draft_cache_type_k") || !strcmp(optname, "spec_draft_cache_type_k")) { |
| 821 | + params.speculative.draft.cache_type_k = kv_cache_type_from_str(optval_str); |
| 822 | + } else if (!strcmp(optname, "draft_cache_type_v") || !strcmp(optname, "spec_draft_cache_type_v")) { |
| 823 | + params.speculative.draft.cache_type_v = kv_cache_type_from_str(optval_str); |
| 824 | + |
| 825 | + // --- draft model thread counts (upstream --spec-draft-threads / -batch) --- |
| 826 | + } else if (!strcmp(optname, "draft_threads") || !strcmp(optname, "spec_draft_threads")) { |
| 827 | + if (optval != NULL) { |
| 828 | + try { |
| 829 | + int n = std::stoi(optval_str); |
| 830 | + if (n <= 0) n = (int)std::thread::hardware_concurrency(); |
| 831 | + params.speculative.draft.cpuparams.n_threads = n; |
| 832 | + } catch (...) {} |
| 833 | + } |
| 834 | + } else if (!strcmp(optname, "draft_threads_batch") || !strcmp(optname, "spec_draft_threads_batch")) { |
| 835 | + if (optval != NULL) { |
| 836 | + try { |
| 837 | + int n = std::stoi(optval_str); |
| 838 | + if (n <= 0) n = (int)std::thread::hardware_concurrency(); |
| 839 | + params.speculative.draft.cpuparams_batch.n_threads = n; |
| 840 | + } catch (...) {} |
| 841 | + } |
| 842 | + |
| 843 | + // --- draft model MoE on CPU (upstream --spec-draft-cpu-moe / --spec-draft-n-cpu-moe) --- |
| 844 | + } else if (!strcmp(optname, "draft_cpu_moe") || !strcmp(optname, "spec_draft_cpu_moe")) { |
| 845 | + // Bool-style flag: optval may be missing, "true"/"1"/"yes" enables. |
| 846 | + const bool enable = (optval == NULL) || |
| 847 | + optval_str == "true" || optval_str == "1" || optval_str == "yes" || |
| 848 | + optval_str == "on" || optval_str == "enabled"; |
| 849 | + if (enable) { |
| 850 | + params.speculative.draft.tensor_buft_overrides.push_back(llm_ffn_exps_cpu_override()); |
| 851 | + } |
| 852 | + } else if (!strcmp(optname, "draft_n_cpu_moe") || !strcmp(optname, "spec_draft_n_cpu_moe")) { |
| 853 | + if (optval != NULL) { |
| 854 | + try { |
| 855 | + int n = std::stoi(optval_str); |
| 856 | + if (n < 0) n = 0; |
| 857 | + // Keep override-name storage alive for the lifetime of the params struct |
| 858 | + // (mirrors upstream arg.cpp behavior with a function-local static). |
| 859 | + static std::list<std::string> buft_overrides_draft; |
| 860 | + for (int i = 0; i < n; ++i) { |
| 861 | + buft_overrides_draft.push_back(llm_ffn_exps_block_regex(i)); |
| 862 | + params.speculative.draft.tensor_buft_overrides.push_back( |
| 863 | + {buft_overrides_draft.back().c_str(), ggml_backend_cpu_buffer_type()}); |
| 864 | + } |
| 865 | + } catch (...) {} |
| 866 | + } |
| 867 | + |
| 868 | + // --- draft model tensor buffer overrides (upstream --spec-draft-override-tensor) --- |
| 869 | + } else if (!strcmp(optname, "draft_override_tensor") || !strcmp(optname, "spec_draft_override_tensor")) { |
| 870 | + // Format: <tensor regex>=<buffer type>,<tensor regex>=<buffer type>,... |
| 871 | + // We replicate upstream's parse_tensor_buffer_overrides (static in arg.cpp). |
| 872 | + ggml_backend_load_all(); |
| 873 | + std::map<std::string, ggml_backend_buffer_type_t> buft_list; |
| 874 | + for (size_t i = 0; i < ggml_backend_dev_count(); ++i) { |
| 875 | + auto * dev = ggml_backend_dev_get(i); |
| 876 | + auto * buft = ggml_backend_dev_buffer_type(dev); |
| 877 | + if (buft) { |
| 878 | + buft_list[ggml_backend_buft_name(buft)] = buft; |
| 879 | + } |
| 880 | + } |
| 881 | + static std::list<std::string> draft_override_names; |
| 882 | + std::string cur; |
| 883 | + auto flush = [&](const std::string & spec) { |
| 884 | + auto pos = spec.find('='); |
| 885 | + if (pos == std::string::npos) return; |
| 886 | + const std::string name = spec.substr(0, pos); |
| 887 | + const std::string type = spec.substr(pos + 1); |
| 888 | + auto it = buft_list.find(type); |
| 889 | + if (it == buft_list.end()) return; // unknown buffer type: ignore |
| 890 | + draft_override_names.push_back(name); |
| 891 | + params.speculative.draft.tensor_buft_overrides.push_back( |
| 892 | + {draft_override_names.back().c_str(), it->second}); |
| 893 | + }; |
| 894 | + for (char c : optval_str) { |
| 895 | + if (c == ',') { if (!cur.empty()) { flush(cur); cur.clear(); } } |
| 896 | + else { cur.push_back(c); } |
| 897 | + } |
| 898 | + if (!cur.empty()) flush(cur); |
| 899 | + } |
| 900 | +#endif // LOCALAI_LEGACY_LLAMA_CPP_SPEC — closes the `else`/`#ifdef` opened at draft_ctx_size |
717 | 901 | } |
718 | 902 |
|
719 | 903 | // Set params.n_parallel from environment variable if not set via options (fallback) |
@@ -2704,7 +2888,7 @@ class BackendServiceImpl final : public backend::Backend::Service { |
2704 | 2888 |
|
2705 | 2889 | tasks.reserve(documents.size()); |
2706 | 2890 | for (size_t i = 0; i < documents.size(); i++) { |
2707 | | - auto tmp = format_prompt_rerank(ctx_server.impl->model, ctx_server.impl->vocab, ctx_server.impl->mctx, request->query(), documents[i]); |
| 2891 | + auto tmp = format_prompt_rerank(ctx_server.impl->model_tgt, ctx_server.impl->vocab, ctx_server.impl->mctx, request->query(), documents[i]); |
2708 | 2892 | server_task task = server_task(SERVER_TASK_TYPE_RERANK); |
2709 | 2893 | task.id = rd.queue_tasks.get_new_id(); |
2710 | 2894 | task.index = i; |
@@ -2882,7 +3066,7 @@ class BackendServiceImpl final : public backend::Backend::Service { |
2882 | 3066 | // Get template source and reconstruct a common_chat_template for analysis |
2883 | 3067 | std::string tmpl_src = common_chat_templates_source(ctx_server.impl->chat_params.tmpls.get()); |
2884 | 3068 | if (!tmpl_src.empty()) { |
2885 | | - const auto * vocab = llama_model_get_vocab(ctx_server.impl->model); |
| 3069 | + const auto * vocab = llama_model_get_vocab(ctx_server.impl->model_tgt); |
2886 | 3070 | std::string token_bos, token_eos; |
2887 | 3071 | if (vocab) { |
2888 | 3072 | auto bos_id = llama_vocab_bos(vocab); |
|
0 commit comments