From 80cf6e2d898f0abba44ce3c956512d5c3cfffc82 Mon Sep 17 00:00:00 2001 From: EZForever <34133756+EZForever@users.noreply.github.com> Date: Thu, 11 Jun 2026 02:08:28 +0800 Subject: [PATCH 1/6] common: support --models-dir loading MTP assistant models --- common/preset.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/common/preset.cpp b/common/preset.cpp index 51ea984d8cd..497cf64c963 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -382,6 +382,7 @@ struct local_model { std::string name; std::string path; std::string path_mmproj; + std::string path_mtp; }; common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const { @@ -395,10 +396,13 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m common_file_info model_file; common_file_info first_shard_file; common_file_info mmproj_file; + common_file_info mtp_file; for (const auto & file : files) { if (string_ends_with(file.name, ".gguf")) { if (file.name.find("mmproj") != std::string::npos) { mmproj_file = file; + } else if (file.name.find("mtp-") != std::string::npos) { + mtp_file = file; } else if (file.name.find("-00001-of-") != std::string::npos) { first_shard_file = file; } else { @@ -410,7 +414,8 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m local_model model{ /* name */ name, /* path */ first_shard_file.path.empty() ? model_file.path : first_shard_file.path, - /* path_mmproj */ mmproj_file.path // can be empty + /* path_mmproj */ mmproj_file.path, // can be empty + /* path_mtp */ mtp_file.path // can be empty }; if (!model.path.empty()) { models.push_back(model); @@ -428,7 +433,8 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m local_model model{ /* name */ name, /* path */ file.path, - /* path_mmproj */ "" + /* path_mmproj */ "", + /* path_mtp */ "" }; models.push_back(model); } @@ -443,6 +449,10 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m if (!model.path_mmproj.empty()) { preset.set_option(*this, "LLAMA_ARG_MMPROJ", model.path_mmproj); } + if (!model.path_mtp.empty()) { + preset.set_option(*this, "LLAMA_ARG_SPEC_TYPE", "draft-mtp"); + preset.set_option(*this, "LLAMA_ARG_SPEC_DRAFT_MODEL", model.path_mtp); + } out[preset.name] = preset; } From 8296a4bff34831d0770815459b191daca204637f Mon Sep 17 00:00:00 2001 From: Eric Zhang <34133756+EZForever@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:56:34 +0800 Subject: [PATCH 2/6] common: preset: check for MTP models with strict prefix --- common/preset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/preset.cpp b/common/preset.cpp index 497cf64c963..35e1f8c754a 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -401,7 +401,7 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m if (string_ends_with(file.name, ".gguf")) { if (file.name.find("mmproj") != std::string::npos) { mmproj_file = file; - } else if (file.name.find("mtp-") != std::string::npos) { + } else if (file.name.rfind("mtp-", 0) != std::string::npos) { mtp_file = file; } else if (file.name.find("-00001-of-") != std::string::npos) { first_shard_file = file; From c74aeaaef09228f1ea2908dbab95316ea22d4ba6 Mon Sep 17 00:00:00 2001 From: Eric Zhang <34133756+EZForever@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:29:07 +0800 Subject: [PATCH 3/6] common: preset: Take advantage of PR #27005 --- common/preset.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/common/preset.cpp b/common/preset.cpp index 35e1f8c754a..b287b68cdad 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -450,7 +450,6 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m preset.set_option(*this, "LLAMA_ARG_MMPROJ", model.path_mmproj); } if (!model.path_mtp.empty()) { - preset.set_option(*this, "LLAMA_ARG_SPEC_TYPE", "draft-mtp"); preset.set_option(*this, "LLAMA_ARG_SPEC_DRAFT_MODEL", model.path_mtp); } out[preset.name] = preset; From ef16f56ed7fca3f745a36f42719be0b2640febc4 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Fri, 14 Aug 2026 12:29:18 +0200 Subject: [PATCH 4/6] handle other draft types --- common/preset.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/common/preset.cpp b/common/preset.cpp index 40bd324f00a..e60071a423a 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -365,9 +365,11 @@ struct local_model { std::string name; std::string path; std::string path_mmproj; - std::string path_mtp; + std::string path_draft; }; +static const char * draft_prefixes[] = { "mtp-", "dspark-", "dflash-", "eagle3-" }; + common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const { if (!std::filesystem::exists(models_dir) || !std::filesystem::is_directory(models_dir)) { throw std::runtime_error(string_format("error: '%s' does not exist or is not a directory\n", models_dir.c_str())); @@ -379,13 +381,22 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m common_file_info model_file; common_file_info first_shard_file; common_file_info mmproj_file; - common_file_info mtp_file; + common_file_info draft_file; for (const auto & file : files) { if (string_ends_with(file.name, ".gguf")) { + bool is_draft = false; + for (const auto & prefix : draft_prefixes) { + if (file.name.rfind(prefix, 0) == 0) { + is_draft = true; + break; + } + } if (file.name.find("mmproj") != std::string::npos) { mmproj_file = file; - } else if (file.name.rfind("mtp-", 0) != std::string::npos) { - mtp_file = file; + } else if (is_draft) { + if (draft_file.path.empty()) { + draft_file = file; // first sidecar found wins + } } else if (file.name.find("-00001-of-") != std::string::npos) { first_shard_file = file; } else { @@ -398,7 +409,7 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m /* name */ name, /* path */ first_shard_file.path.empty() ? model_file.path : first_shard_file.path, /* path_mmproj */ mmproj_file.path, // can be empty - /* path_mtp */ mtp_file.path // can be empty + /* path_draft */ draft_file.path // can be empty }; if (!model.path.empty()) { models.push_back(model); @@ -417,7 +428,7 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m /* name */ name, /* path */ file.path, /* path_mmproj */ "", - /* path_mtp */ "" + /* path_draft */ "" }; models.push_back(model); } @@ -432,8 +443,8 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m if (!model.path_mmproj.empty()) { preset.set_option(*this, "LLAMA_ARG_MMPROJ", model.path_mmproj); } - if (!model.path_mtp.empty()) { - preset.set_option(*this, "LLAMA_ARG_SPEC_DRAFT_MODEL", model.path_mtp); + if (!model.path_draft.empty()) { + preset.set_option(*this, "LLAMA_ARG_SPEC_DRAFT_MODEL", model.path_draft); } out[preset.name] = preset; } From 41571d380210afaf60b3035f6865bccfea499b45 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Fri, 14 Aug 2026 12:34:33 +0200 Subject: [PATCH 5/6] drop eagle3 --- common/preset.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/preset.cpp b/common/preset.cpp index e60071a423a..433198132dd 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -368,7 +368,8 @@ struct local_model { std::string path_draft; }; -static const char * draft_prefixes[] = { "mtp-", "dspark-", "dflash-", "eagle3-" }; +// TODO @ngxson: handle "eagle3-" when it's supported by common_speculative_types_from_gguf() +static const char * draft_prefixes[] = { "mtp-", "dspark-", "dflash-" }; common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const { if (!std::filesystem::exists(models_dir) || !std::filesystem::is_directory(models_dir)) { From 16c45f3af5a6089360dd80419f103f3cc5c5d04e Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Fri, 14 Aug 2026 12:38:02 +0200 Subject: [PATCH 6/6] clean up --- common/preset.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/common/preset.cpp b/common/preset.cpp index 433198132dd..4c61e93eead 100644 --- a/common/preset.cpp +++ b/common/preset.cpp @@ -371,6 +371,19 @@ struct local_model { // TODO @ngxson: handle "eagle3-" when it's supported by common_speculative_types_from_gguf() static const char * draft_prefixes[] = { "mtp-", "dspark-", "dflash-" }; +static bool is_mmproj_file(const std::string & fname) { + return fname.find("mmproj") != std::string::npos; +} + +static bool is_draft_file(const std::string & fname) { + for (const auto & prefix : draft_prefixes) { + if (fname.rfind(prefix, 0) == 0) { + return true; + } + } + return false; +} + common_presets common_preset_context::load_from_models_dir(const std::string & models_dir) const { if (!std::filesystem::exists(models_dir) || !std::filesystem::is_directory(models_dir)) { throw std::runtime_error(string_format("error: '%s' does not exist or is not a directory\n", models_dir.c_str())); @@ -385,16 +398,9 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m common_file_info draft_file; for (const auto & file : files) { if (string_ends_with(file.name, ".gguf")) { - bool is_draft = false; - for (const auto & prefix : draft_prefixes) { - if (file.name.rfind(prefix, 0) == 0) { - is_draft = true; - break; - } - } - if (file.name.find("mmproj") != std::string::npos) { + if (is_mmproj_file(file.name)) { mmproj_file = file; - } else if (is_draft) { + } else if (is_draft_file(file.name)) { if (draft_file.path.empty()) { draft_file = file; // first sidecar found wins } @@ -422,6 +428,9 @@ common_presets common_preset_context::load_from_models_dir(const std::string & m if (file.is_dir) { scan_subdir(file.path, file.name); } else if (string_ends_with(file.name, ".gguf")) { + if (is_mmproj_file(file.name) || is_draft_file(file.name)) { + continue; // companion file, cannot be loaded as a model on its own + } // single file model std::string name = file.name; string_replace_all(name, ".gguf", "");