Add --defer-experts flag to defer expert mmap residency on Linux (#1634)

* Add --defer-experts flag to defer expert mmap residency on Linux

* Disable warmup when defer-experts is enabled
This commit is contained in:
dmaivel 2026-04-16 02:54:44 -04:00 committed by GitHub
parent 0b81212dea
commit 4f4bcfbe67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 303 additions and 7 deletions

View File

@ -1559,6 +1559,11 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa
params.fit = true; params.fit = true;
return true; return true;
} }
if (arg == "--defer-experts") {
params.defer_experts = true;
params.warmup = false;
return true;
}
if (arg == "--fit-margin") { if (arg == "--fit-margin") {
CHECK_ARG; CHECK_ARG;
int32_t margin = std::stoi(argv[i]); int32_t margin = std::stoi(argv[i]);
@ -2595,6 +2600,7 @@ void gpt_params_print_usage(int /*argc*/, char ** argv, const gpt_params & param
options.push_back({ "*", " --run-time-repack", "repack tensors if interleaved variant is available"}); options.push_back({ "*", " --run-time-repack", "repack tensors if interleaved variant is available"});
options.push_back({ "*", " --cpu-moe", "keep all MoE weights in CPU memory"}); options.push_back({ "*", " --cpu-moe", "keep all MoE weights in CPU memory"});
options.push_back({ "*", " --n-cpu-moe N", "keep MoE weights of the first N layers in CPU memory"}); options.push_back({ "*", " --n-cpu-moe N", "keep MoE weights of the first N layers in CPU memory"});
options.push_back({ "*", " --defer-experts", "defer expert mmap residency on Linux to reduce model load time"});
options.push_back({ "*", " --fit-margin N", "safety margin in MiB when auto-fitting model offloading"}); options.push_back({ "*", " --fit-margin N", "safety margin in MiB when auto-fitting model offloading"});
options.push_back({ "*", "-wgt, --worst-graph-tokens N", "number of tokens to use for worst-case graph"}); options.push_back({ "*", "-wgt, --worst-graph-tokens N", "number of tokens to use for worst-case graph"});
options.push_back({ "*", " --fit", "automatically determine which tensors to offload to the GPU(s)"}); options.push_back({ "*", " --fit", "automatically determine which tensors to offload to the GPU(s)"});
@ -3460,6 +3466,7 @@ struct llama_model_params common_model_params_to_llama(const gpt_params & params
mparams.merge_up_gate_exps = params.merge_up_gate_exps; mparams.merge_up_gate_exps = params.merge_up_gate_exps;
mparams.mtp = params.has_mtp; mparams.mtp = params.has_mtp;
mparams.flash_attn = params.flash_attn; mparams.flash_attn = params.flash_attn;
mparams.defer_experts = params.defer_experts;
if (params.kv_overrides.empty()) { if (params.kv_overrides.empty()) {
mparams.kv_overrides = NULL; mparams.kv_overrides = NULL;
} else { } else {
@ -4527,6 +4534,7 @@ void yaml_dump_non_result_info(FILE * stream, const gpt_params & params, const l
fprintf(stream, "validate_quants: %s # default: false\n", params.validate_quants ? "true" : "false"); fprintf(stream, "validate_quants: %s # default: false\n", params.validate_quants ? "true" : "false");
fprintf(stream, "merge_qkv: %s # default: false\n", params.merge_qkv ? "true" : "false"); fprintf(stream, "merge_qkv: %s # default: false\n", params.merge_qkv ? "true" : "false");
fprintf(stream, "merge_up_gate_exps: %s # default: false\n", params.merge_up_gate_exps ? "true" : "false"); fprintf(stream, "merge_up_gate_exps: %s # default: false\n", params.merge_up_gate_exps ? "true" : "false");
fprintf(stream, "defer_experts: %s # default: false\n", params.defer_experts ? "true" : "false");
fprintf(stream, "max_extra_alloc: %d # default: 256\n", params.max_extra_alloc_MiB); fprintf(stream, "max_extra_alloc: %d # default: 256\n", params.max_extra_alloc_MiB);
fprintf(stream, "penalize_nl: %s # default: false\n", sparams.penalize_nl ? "true" : "false"); fprintf(stream, "penalize_nl: %s # default: false\n", sparams.penalize_nl ? "true" : "false");
fprintf(stream, "ppl_output_type: %d # default: 0\n", params.ppl_output_type); fprintf(stream, "ppl_output_type: %d # default: 0\n", params.ppl_output_type);

View File

@ -370,6 +370,7 @@ struct gpt_params {
bool only_active_exps = true; // if true, offload only active experts (relevant only for hybrid CPU/GPU) bool only_active_exps = true; // if true, offload only active experts (relevant only for hybrid CPU/GPU)
bool merge_qkv = false; // if true, merge separate Q, K, V tensors into a single, contiguous tensor bool merge_qkv = false; // if true, merge separate Q, K, V tensors into a single, contiguous tensor
bool merge_up_gate_exps= false; // if true, merge ffn_up_exps and ffn_gate_exps into a single, contiguous tensor bool merge_up_gate_exps= false; // if true, merge ffn_up_exps and ffn_gate_exps into a single, contiguous tensor
bool defer_experts = false; // if true, defer expert mmap residency to speed up model loading (Linux only)
bool k_cache_hadamard = false; // if true, use Hadamard transform for the K-cache (only makes sense with quantized cache) bool k_cache_hadamard = false; // if true, use Hadamard transform for the K-cache (only makes sense with quantized cache)
bool v_cache_hadamard = false; // if true, use Hadamard transform for the V-cache (only makes sense with quantized cache, which requires FA) bool v_cache_hadamard = false; // if true, use Hadamard transform for the V-cache (only makes sense with quantized cache, which requires FA)
bool split_mode_graph_scheduling = false; // if true, force split mode graph scheduling bool split_mode_graph_scheduling = false; // if true, force split mode graph scheduling

View File

@ -269,6 +269,7 @@ struct cmd_params {
bool no_ooae = false; bool no_ooae = false;
bool mqkv = false; bool mqkv = false;
bool muge = false; bool muge = false;
bool defer_experts = false;
bool rcache = false; bool rcache = false;
bool sas = false; bool sas = false;
int max_gpu = 0; int max_gpu = 0;
@ -317,6 +318,7 @@ static const cmd_params cmd_params_defaults = {
/* no_ooae */ false, /* no_ooae */ false,
/* mqkv */ false, /* mqkv */ false,
/* muge */ false, /* muge */ false,
/* defer_experts */ false,
/* rcache */ false, /* rcache */ false,
/* sas */ false, /* sas */ false,
/* max_gpu */ 0, /* max_gpu */ 0,
@ -367,6 +369,7 @@ static void print_usage(int /* argc */, char ** argv) {
printf(" -cuda, --cuda-params <string> (default: %s)\n", cmd_params_defaults.cuda_params.c_str()); printf(" -cuda, --cuda-params <string> (default: %s)\n", cmd_params_defaults.cuda_params.c_str());
printf(" -mqkv, --merge-qkv (default: %s)\n", cmd_params_defaults.mqkv ? "1" : "0"); printf(" -mqkv, --merge-qkv (default: %s)\n", cmd_params_defaults.mqkv ? "1" : "0");
printf(" -muge, --merge-up-gate-experts (default: %s)\n", cmd_params_defaults.muge ? "1" : "0"); printf(" -muge, --merge-up-gate-experts (default: %s)\n", cmd_params_defaults.muge ? "1" : "0");
printf(" --defer-experts (Linux only, default: %s)\n", cmd_params_defaults.defer_experts ? "1" : "0");
printf(" -rcache, --rope-cache (default: %s)\n", cmd_params_defaults.rcache ? "1" : "0"); printf(" -rcache, --rope-cache (default: %s)\n", cmd_params_defaults.rcache ? "1" : "0");
printf(" -thp, --transparent-huge-pages <0|1> (default: %s)\n", cmd_params_defaults.use_thp? "1" : "0"); printf(" -thp, --transparent-huge-pages <0|1> (default: %s)\n", cmd_params_defaults.use_thp? "1" : "0");
printf(" -ot, --override-tensor pattern (default: none)\n"); printf(" -ot, --override-tensor pattern (default: none)\n");
@ -813,6 +816,8 @@ static cmd_params parse_cmd_params(int argc, char ** argv) {
break; break;
} }
params.muge = std::stoi(argv[i]); params.muge = std::stoi(argv[i]);
} else if (arg == "--defer-experts") {
params.defer_experts = true;
} else if (arg == "-sas" || arg == "--scheduler-async") { } else if (arg == "-sas" || arg == "--scheduler-async") {
if (++i >= argc) { if (++i >= argc) {
invalid_param = true; invalid_param = true;
@ -981,6 +986,7 @@ struct cmd_params_instance {
bool no_ooae = false; bool no_ooae = false;
bool mqkv = false; bool mqkv = false;
bool muge = false; bool muge = false;
bool defer_experts = false;
bool rcache = false; bool rcache = false;
bool sas = false; bool sas = false;
int max_gpu = 0; int max_gpu = 0;
@ -1003,6 +1009,7 @@ struct cmd_params_instance {
mparams.use_thp = use_thp; mparams.use_thp = use_thp;
mparams.merge_qkv = mqkv; mparams.merge_qkv = mqkv;
mparams.merge_up_gate_exps = muge; mparams.merge_up_gate_exps = muge;
mparams.defer_experts = defer_experts;
mparams.tensor_buft_overrides = buft_overrides; mparams.tensor_buft_overrides = buft_overrides;
mparams.mla = mla_attn; mparams.mla = mla_attn;
mparams.max_gpu = max_gpu; mparams.max_gpu = max_gpu;
@ -1024,6 +1031,7 @@ struct cmd_params_instance {
repack == other.repack && repack == other.repack &&
mqkv == other.mqkv && mqkv == other.mqkv &&
muge == other.muge && muge == other.muge &&
defer_experts == other.defer_experts &&
use_thp == other.use_thp && use_thp == other.use_thp &&
sas == other.sas && sas == other.sas &&
fit == other.fit && fit == other.fit &&
@ -1119,6 +1127,7 @@ static std::vector<cmd_params_instance> get_cmd_params_instances(const cmd_param
/* .no_ooae = */ params.no_ooae, /* .no_ooae = */ params.no_ooae,
/* .mqkv = */ params.mqkv, /* .mqkv = */ params.mqkv,
/* .muge = */ params.muge, /* .muge = */ params.muge,
/* .defer_experts= */ params.defer_experts,
/* .rcache = */ params.rcache, /* .rcache = */ params.rcache,
/* .sas = */ params.sas, /* .sas = */ params.sas,
/* .max_gpu = */ params.max_gpu, /* .max_gpu = */ params.max_gpu,
@ -1165,6 +1174,7 @@ static std::vector<cmd_params_instance> get_cmd_params_instances(const cmd_param
/* .no_ooae = */ params.no_ooae, /* .no_ooae = */ params.no_ooae,
/* .mqkv = */ params.mqkv, /* .mqkv = */ params.mqkv,
/* .muge = */ params.muge, /* .muge = */ params.muge,
/* .defer_experts= */ params.defer_experts,
/* .rcache = */ params.rcache, /* .rcache = */ params.rcache,
/* .sas = */ params.sas, /* .sas = */ params.sas,
/* .max_gpu = */ params.max_gpu, /* .max_gpu = */ params.max_gpu,
@ -1211,6 +1221,7 @@ static std::vector<cmd_params_instance> get_cmd_params_instances(const cmd_param
/* .no_ooae = */ params.no_ooae, /* .no_ooae = */ params.no_ooae,
/* .mqkv = */ params.mqkv, /* .mqkv = */ params.mqkv,
/* .muge = */ params.muge, /* .muge = */ params.muge,
/* .defer_experts= */ params.defer_experts,
/* .rcache = */ params.rcache, /* .rcache = */ params.rcache,
/* .sas = */ params.sas, /* .sas = */ params.sas,
/* .max_gpu = */ params.max_gpu, /* .max_gpu = */ params.max_gpu,
@ -1257,6 +1268,7 @@ static std::vector<cmd_params_instance> get_cmd_params_instances(const cmd_param
/* .no_ooae = */ params.no_ooae, /* .no_ooae = */ params.no_ooae,
/* .mqkv = */ params.mqkv, /* .mqkv = */ params.mqkv,
/* .muge = */ params.muge, /* .muge = */ params.muge,
/* .defer_experts= */ params.defer_experts,
/* .rcache = */ params.rcache, /* .rcache = */ params.rcache,
/* .sas = */ params.sas, /* .sas = */ params.sas,
/* .max_gpu = */ params.max_gpu, /* .max_gpu = */ params.max_gpu,
@ -1314,6 +1326,7 @@ struct test {
bool no_ooae = false; bool no_ooae = false;
bool mqkv = false; bool mqkv = false;
bool muge = false; bool muge = false;
bool defer_experts = false;
bool rcache = false; bool rcache = false;
bool sas = false; bool sas = false;
bool max_gpu = 0; bool max_gpu = 0;
@ -1356,6 +1369,7 @@ struct test {
repack = inst.repack; repack = inst.repack;
mqkv = inst.mqkv; mqkv = inst.mqkv;
muge = inst.muge; muge = inst.muge;
defer_experts = inst.defer_experts;
fmoe = inst.fmoe; fmoe = inst.fmoe;
ger = inst.ger; ger = inst.ger;
rcache = inst.rcache; rcache = inst.rcache;
@ -1474,7 +1488,7 @@ struct test {
field == "gpu_blas" || field == "blas" || field == "sycl" || field == "no_kv_offload" || field == "gpu_blas" || field == "blas" || field == "sycl" || field == "no_kv_offload" ||
field == "flash_attn" || field == "use_mmap" || field == "embeddings" || field == "repack" || field == "use_thp" || field == "flash_attn" || field == "use_mmap" || field == "embeddings" || field == "repack" || field == "use_thp" ||
field == "fused_moe" || field == "grouped_er" || field == "no_fused_up_gate" || field == "no_ooae" || field == "mqkv" || field == "fused_moe" || field == "grouped_er" || field == "no_fused_up_gate" || field == "no_ooae" || field == "mqkv" ||
field == "rcache" || field == "reuse" || field == "muge" || field == "sas") { field == "rcache" || field == "reuse" || field == "muge" || field == "defer_experts" || field == "sas") {
return BOOL; return BOOL;
} }
if (field == "avg_ts" || field == "stddev_ts") { if (field == "avg_ts" || field == "stddev_ts") {
@ -1517,7 +1531,7 @@ struct test {
std::to_string(main_gpu), std::to_string(no_kv_offload), std::to_string(flash_attn), std::to_string(main_gpu), std::to_string(no_kv_offload), std::to_string(flash_attn),
std::to_string(mla_attn), std::to_string(attn_max_batch), ser_to_string(ser), std::to_string(reuse), std::to_string(mla_attn), std::to_string(attn_max_batch), ser_to_string(ser), std::to_string(reuse),
tensor_split_str, std::to_string(use_mmap), std::to_string(embeddings), tensor_split_str, std::to_string(use_mmap), std::to_string(embeddings),
std::to_string(repack), std::to_string(mqkv), std::to_string(muge), std::to_string(fmoe), std::to_string(ger), std::to_string(repack), std::to_string(mqkv), std::to_string(muge), std::to_string(defer_experts), std::to_string(fmoe), std::to_string(ger),
std::to_string(no_fug), std::to_string(use_thp), std::to_string(no_ooae), std::to_string(rcache), std::to_string(sas), std::to_string(no_fug), std::to_string(use_thp), std::to_string(no_ooae), std::to_string(rcache), std::to_string(sas),
std::to_string(max_gpu), std::to_string(max_gpu),
cuda_params, override_tensor, cuda_params, override_tensor,
@ -1539,7 +1553,7 @@ struct test {
"n_threads", "type_k", "type_v", "n_threads", "type_k", "type_v",
"n_gpu_layers", "split_mode", "n_gpu_layers", "split_mode",
"main_gpu", "no_kv_offload", "flash_attn", "mla_attn", "attn_max_batch", "ser", "reuse", "main_gpu", "no_kv_offload", "flash_attn", "mla_attn", "attn_max_batch", "ser", "reuse",
"tensor_split", "use_mmap", "embeddings", "repack", "mqkv", "muge", "fused_moe", "grouped_er", "tensor_split", "use_mmap", "embeddings", "repack", "mqkv", "muge", "defer_experts", "fused_moe", "grouped_er",
"no_fused_up_gate", "use_thp", "no_ooae", "rcache", "sas", "max_gpu", "cuda_params", "override_tensor", "no_fused_up_gate", "use_thp", "no_ooae", "rcache", "sas", "max_gpu", "cuda_params", "override_tensor",
"n_prompt", "n_gen", "test_time", "n_prompt", "n_gen", "test_time",
"avg_ns", "stddev_ns", "avg_ns", "stddev_ns",
@ -1727,6 +1741,9 @@ struct markdown_printer : public printer {
if (field == "muge") { if (field == "muge") {
return 4; return 4;
} }
if (field == "defer_experts") {
return 5;
}
if (field == "sas") { if (field == "sas") {
return 3; return 3;
} }
@ -1803,6 +1820,9 @@ struct markdown_printer : public printer {
if (field == "muge") { if (field == "muge") {
return "muge"; return "muge";
} }
if (field == "defer_experts") {
return "defer";
}
if (field == "sas") { if (field == "sas") {
return "sas"; return "sas";
} }
@ -1925,6 +1945,9 @@ struct markdown_printer : public printer {
if (params.muge != cmd_params_defaults.muge) { if (params.muge != cmd_params_defaults.muge) {
fields.emplace_back("muge"); fields.emplace_back("muge");
} }
if (params.defer_experts != cmd_params_defaults.defer_experts) {
fields.emplace_back("defer_experts");
}
if (params.use_thp != cmd_params_defaults.use_thp) { if (params.use_thp != cmd_params_defaults.use_thp) {
fields.emplace_back("use_thp"); fields.emplace_back("use_thp");
} }

View File

@ -426,6 +426,7 @@ extern "C" {
bool mtp; // if true, load MTP layers if present bool mtp; // if true, load MTP layers if present
bool dry_run; // skip loading tensors bool dry_run; // skip loading tensors
bool flash_attn; bool flash_attn;
bool defer_experts; // defer expert mmap residency to speed up model loading (Linux only)
}; };
// NOTE: changing the default values of parameters marked as [EXPERIMENTAL] may cause crashes or incorrect results in certain configurations // NOTE: changing the default values of parameters marked as [EXPERIMENTAL] may cause crashes or incorrect results in certain configurations

24
src/llama-expert-io.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <cstddef>
#include <vector>
struct llama_file_range {
size_t first = 0;
size_t last = 0;
bool empty() const {
return first >= last;
}
};
struct llama_expert_tensor_index {
size_t deferred_bytes = 0;
size_t dense_bytes = 0;
std::vector<std::vector<llama_file_range>> file_ranges;
bool empty() const {
return deferred_bytes == 0;
}
};

View File

@ -366,6 +366,26 @@ struct llama_mmap::impl {
} }
} }
void dontneed_fragment(size_t first, size_t last) {
int page_size = mapped_page_size > 0 ? mapped_page_size : sysconf(_SC_PAGESIZE);
align_range(&first, &last, page_size);
size_t len = last - first;
if (len == 0) {
return;
}
GGML_ASSERT(first % page_size == 0);
GGML_ASSERT(last % page_size == 0);
GGML_ASSERT(last >= first);
#ifdef __linux__
if (madvise((uint8_t *) addr + first, len, MADV_DONTNEED)) {
LLAMA_LOG_WARN("warning: madvise(..., MADV_DONTNEED) failed: %s\n", strerror(errno));
}
#endif
}
void unmap_fragment(size_t first, size_t last) { void unmap_fragment(size_t first, size_t last) {
int page_size = mapped_page_size > 0 ? mapped_page_size : sysconf(_SC_PAGESIZE); int page_size = mapped_page_size > 0 ? mapped_page_size : sysconf(_SC_PAGESIZE);
align_range(&first, &last, page_size); align_range(&first, &last, page_size);
@ -454,6 +474,11 @@ struct llama_mmap::impl {
} }
} }
void dontneed_fragment(size_t first, size_t last) {
GGML_UNUSED(first);
GGML_UNUSED(last);
}
void unmap_fragment(size_t first, size_t last) { void unmap_fragment(size_t first, size_t last) {
GGML_UNUSED(first); GGML_UNUSED(first);
GGML_UNUSED(last); GGML_UNUSED(last);
@ -474,6 +499,13 @@ struct llama_mmap::impl {
throw std::runtime_error("mmap not supported"); throw std::runtime_error("mmap not supported");
} }
void dontneed_fragment(size_t first, size_t last) {
GGML_UNUSED(first);
GGML_UNUSED(last);
throw std::runtime_error("mmap not supported");
}
void unmap_fragment(size_t first, size_t last) { void unmap_fragment(size_t first, size_t last) {
GGML_UNUSED(first); GGML_UNUSED(first);
GGML_UNUSED(last); GGML_UNUSED(last);
@ -494,6 +526,7 @@ llama_mmap::~llama_mmap() = default;
size_t llama_mmap::size() const { return pimpl->size; } size_t llama_mmap::size() const { return pimpl->size; }
void * llama_mmap::addr() const { return pimpl->addr; } void * llama_mmap::addr() const { return pimpl->addr; }
void llama_mmap::dontneed_fragment(size_t first, size_t last) { pimpl->dontneed_fragment(first, last); }
void llama_mmap::unmap_fragment(size_t first, size_t last) { pimpl->unmap_fragment(first, last); } void llama_mmap::unmap_fragment(size_t first, size_t last) { pimpl->unmap_fragment(first, last); }
#if defined(_POSIX_MEMLOCK_RANGE) || defined(_WIN32) #if defined(_POSIX_MEMLOCK_RANGE) || defined(_WIN32)

View File

@ -42,6 +42,8 @@ struct llama_mmap {
size_t size() const; size_t size() const;
void * addr() const; void * addr() const;
void dontneed_fragment(size_t first, size_t last);
void unmap_fragment(size_t first, size_t last); void unmap_fragment(size_t first, size_t last);
static const bool SUPPORTED; static const bool SUPPORTED;

View File

@ -20,6 +20,7 @@
#include <set> #include <set>
#include <map> #include <map>
#include <array> #include <array>
#include <charconv>
#include <future> #include <future>
#include <regex> #include <regex>
#include <algorithm> #include <algorithm>
@ -204,8 +205,100 @@ namespace GGUFMeta {
}; };
} }
static bool parse_tensor_layer_index(const std::string & name, uint32_t & layer) {
if (name.rfind("blk.", 0) != 0) {
return false;
}
const char * first = name.data() + 4;
const char * last = first;
const char * end = name.data() + name.size();
while (last < end && *last != '.') {
++last;
}
if (last == first || last == end) {
return false;
}
auto result = std::from_chars(first, last, layer);
return result.ec == std::errc() && result.ptr == last;
}
static bool is_split_expert_tensor(const std::string & name, uint32_t & expert) {
static const char * prefixes[] = { "ffn_gate.", "ffn_down.", "ffn_up." };
const size_t layer_end = name.find('.', 4);
if (layer_end == std::string::npos) {
return false;
}
const size_t prefix_begin = layer_end + 1;
for (const char * prefix : prefixes) {
const size_t prefix_len = std::char_traits<char>::length(prefix);
if (name.compare(prefix_begin, prefix_len, prefix) != 0) {
continue;
}
const size_t expert_begin = prefix_begin + prefix_len;
const size_t expert_end = name.find('.', expert_begin);
if (expert_end == std::string::npos || expert_end == expert_begin) {
continue;
}
auto result = std::from_chars(name.data() + expert_begin, name.data() + expert_end, expert);
if (result.ec == std::errc() && result.ptr == name.data() + expert_end) {
return true;
}
}
return false;
}
static bool is_merged_expert_tensor(llm_tensor tensor_type) {
switch (tensor_type) {
case LLM_TENSOR_FFN_NORM_EXPS:
case LLM_TENSOR_FFN_DOWN_EXPS:
case LLM_TENSOR_FFN_GATE_EXPS:
case LLM_TENSOR_FFN_UP_EXPS:
case LLM_TENSOR_FFN_GATE_UP_EXPS:
case LLM_TENSOR_FFN_EXP_PROBS_B:
return true;
default:
return false;
}
}
static void coalesce_ranges(std::vector<llama_file_range> & ranges) {
ranges.erase(std::remove_if(ranges.begin(), ranges.end(), [](const llama_file_range & range) {
return range.empty();
}), ranges.end());
std::sort(ranges.begin(), ranges.end(), [](const llama_file_range & lhs, const llama_file_range & rhs) {
if (lhs.first != rhs.first) {
return lhs.first < rhs.first;
}
return lhs.last < rhs.last;
});
std::vector<llama_file_range> merged;
merged.reserve(ranges.size());
for (const auto & range : ranges) {
if (merged.empty() || range.first > merged.back().last) {
merged.push_back(range);
continue;
}
merged.back().last = std::max(merged.back().last, range.last);
}
ranges = std::move(merged);
}
llama_model_loader::llama_model_loader(const std::string & fname, int ncmoe, bool use_mmap, bool check_tensors, llama_model_loader::llama_model_loader(const std::string & fname, int ncmoe, bool use_mmap, bool check_tensors,
bool repack_tensors, bool use_thp, bool merge_qkv, bool merge_up_gate_exps, bool repack_tensors, bool use_thp, bool merge_qkv, bool merge_up_gate_exps, bool defer_experts,
const llama_model_kv_override * param_overrides_p, const llama_model_kv_override * param_overrides_p,
const llama_model_tensor_buft_override * param_tensor_buft_overrides_p) { const llama_model_tensor_buft_override * param_tensor_buft_overrides_p) {
int trace = 0; int trace = 0;
@ -500,6 +593,7 @@ llama_model_loader::llama_model_loader(const std::string & fname, int ncmoe, boo
this->use_thp = use_thp; this->use_thp = use_thp;
this->merge_qkv = merge_qkv; this->merge_qkv = merge_qkv;
this->merge_up_gate_exps = merge_up_gate_exps; this->merge_up_gate_exps = merge_up_gate_exps;
this->defer_experts = defer_experts;
} }
llama_model_loader::~llama_model_loader() { llama_model_loader::~llama_model_loader() {
@ -511,6 +605,74 @@ llama_model_loader::~llama_model_loader() {
} }
} }
void llama_model_loader::build_expert_tensor_index(const llama_hparams & hparams) {
expert_tensor_index = {};
if (hparams.n_expert == 0 || hparams.n_layer == 0) {
return;
}
expert_tensor_index.file_ranges.resize(files.size());
size_t deferred_bytes = 0;
const llm_arch arch = get_arch();
for (const auto & weight : weights) {
const std::string name(weight.tensor->name);
uint32_t layer = 0;
if (!parse_tensor_layer_index(name, layer)) {
continue;
}
if (layer >= hparams.n_layer) {
throw std::runtime_error(format("expert tensor '%s' has invalid layer index %u", name.c_str(), layer));
}
// check for split expert tensors (blk.N.ffn_gate.E.weight) by name pattern,
// since llm_tensor_type can't resolve these (two %d in the format string)
uint32_t expert = 0;
if (is_split_expert_tensor(name, expert)) {
if (expert >= hparams.n_expert) {
throw std::runtime_error(format("expert tensor '%s' has invalid expert index %u", name.c_str(), expert));
}
} else {
const llm_tensor tensor_type = llm_tensor_type(arch, name, int(layer));
if (!is_merged_expert_tensor(tensor_type)) {
continue;
}
}
const size_t tensor_bytes = ggml_nbytes(weight.tensor);
deferred_bytes += tensor_bytes;
expert_tensor_index.file_ranges.at(weight.idx).push_back({ weight.offs, weight.offs + tensor_bytes });
}
for (auto & ranges : expert_tensor_index.file_ranges) {
coalesce_ranges(ranges);
}
expert_tensor_index.deferred_bytes = deferred_bytes;
expert_tensor_index.dense_bytes = n_bytes > deferred_bytes ? n_bytes - deferred_bytes : 0;
}
bool llama_model_loader::should_defer_expert_mmaps() const {
return defer_experts && use_mmap && !expert_tensor_index.empty();
}
void llama_model_loader::drop_mmap_expert_pages() const {
if (!use_mmap || mappings.empty() || expert_tensor_index.file_ranges.empty()) {
return;
}
const size_t n_range_sets = std::min(mappings.size(), expert_tensor_index.file_ranges.size());
for (size_t idx = 0; idx < n_range_sets; ++idx) {
const auto & ranges = expert_tensor_index.file_ranges[idx];
for (const auto & range : ranges) {
mappings[idx]->dontneed_fragment(range.first, range.last);
}
}
}
template<typename T> template<typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type typename std::enable_if<std::is_integral<T>::value, bool>::type
llama_model_loader::get_arr_n(const std::string & key, T & result, const bool required) { llama_model_loader::get_arr_n(const std::string & key, T & result, const bool required) {

View File

@ -2,8 +2,10 @@
#include "llama.h" #include "llama.h"
#include "llama-impl.h" #include "llama-impl.h"
#include "llama-expert-io.h"
#include "llama-mmap.h" #include "llama-mmap.h"
#include "llama-arch.h" #include "llama-arch.h"
#include "llama-hparams.h"
#include <cstdint> #include <cstdint>
#include <cstddef> #include <cstddef>
@ -48,6 +50,7 @@ struct llama_model_loader {
bool use_thp = false; bool use_thp = false;
bool merge_qkv = false; bool merge_qkv = false;
bool merge_up_gate_exps = false; bool merge_up_gate_exps = false;
bool defer_experts = false;
llama_files files; llama_files files;
llama_ftype ftype; llama_ftype ftype;
@ -81,9 +84,10 @@ struct llama_model_loader {
std::string arch_name; std::string arch_name;
LLM_KV llm_kv = LLM_KV(LLM_ARCH_UNKNOWN); LLM_KV llm_kv = LLM_KV(LLM_ARCH_UNKNOWN);
llama_expert_tensor_index expert_tensor_index;
llama_model_loader(const std::string & fname, int ncmoe, bool use_mmap, bool check_tensors, bool repack_tensors, bool use_thp, llama_model_loader(const std::string & fname, int ncmoe, bool use_mmap, bool check_tensors, bool repack_tensors, bool use_thp,
bool merge_qkv, bool merge_up_gate_exps, bool merge_qkv, bool merge_up_gate_exps, bool defer_experts,
const llama_model_kv_override * param_overrides_p, const llama_model_kv_override * param_overrides_p,
const llama_model_tensor_buft_override * param_tensor_buft_overrides_p); const llama_model_tensor_buft_override * param_tensor_buft_overrides_p);
@ -158,6 +162,12 @@ struct llama_model_loader {
void init_mappings(bool prefetch = true, llama_mlocks * mlock_mmaps = nullptr, bool use_thp = false); void init_mappings(bool prefetch = true, llama_mlocks * mlock_mmaps = nullptr, bool use_thp = false);
void build_expert_tensor_index(const llama_hparams & hparams);
bool should_defer_expert_mmaps() const;
void drop_mmap_expert_pages() const;
void get_mapping_range(size_t * first, size_t * last, void ** addr, int idx, ggml_context * ctx) const; void get_mapping_range(size_t * first, size_t * last, void ** addr, int idx, ggml_context * ctx) const;
// for backwards compatibility, does not support ggml-backend // for backwards compatibility, does not support ggml-backend

View File

@ -1037,7 +1037,8 @@ static void llama_model_quantize_internal(const std::string & fname_inp, const s
kv_overrides = v->data(); kv_overrides = v->data();
} }
llama_model_loader ml(fname_inp, 0, use_mmap, /*check_tensors*/ true, /* repack_tensors */ false, llama_model_loader ml(fname_inp, 0, use_mmap, /*check_tensors*/ true, /* repack_tensors */ false,
/* use_thp */ false, /* merge_qkv */ false, /* merge_up_gate_exps */ false, kv_overrides, nullptr); /* use_thp */ false, /* merge_qkv */ false, /* merge_up_gate_exps */ false,
/* defer_experts */ false, kv_overrides, nullptr);
ml.init_mappings(false); // no prefetching ml.init_mappings(false); // no prefetching
llama_model model; llama_model model;

View File

@ -2665,9 +2665,19 @@ static bool llm_load_tensors(
ml.use_mmap = false; ml.use_mmap = false;
} }
bool defer_expert_mmap = ml.should_defer_expert_mmaps();
if (defer_expert_mmap && use_mlock) {
LLAMA_LOG_WARN("%s: deferred expert loading disabled because mlock keeps mmap ranges resident\n", __func__);
defer_expert_mmap = false;
}
if (defer_expert_mmap && (ml.check_tensors || validate_quants)) {
LLAMA_LOG_WARN("%s: deferred expert loading disabled because tensor validation would fault expert pages eagerly\n", __func__);
defer_expert_mmap = false;
}
ml.done_getting_tensors(); ml.done_getting_tensors();
ml.init_mappings(true, use_mlock ? &model.mlock_mmaps : nullptr, ml.use_thp); ml.init_mappings(!defer_expert_mmap, use_mlock ? &model.mlock_mmaps : nullptr, ml.use_thp);
model.mappings.reserve(ml.mappings.size()); model.mappings.reserve(ml.mappings.size());
// create the backend buffers // create the backend buffers
@ -2804,6 +2814,10 @@ static bool llm_load_tensors(
return false; return false;
} }
} }
if (defer_expert_mmap) {
ml.drop_mmap_expert_pages();
}
} }
if (model.arch == LLM_ARCH_DEEPSEEK2 || model.arch == LLM_ARCH_GLM_DSA || model.arch == LLM_ARCH_MISTRAL4) { if (model.arch == LLM_ARCH_DEEPSEEK2 || model.arch == LLM_ARCH_GLM_DSA || model.arch == LLM_ARCH_MISTRAL4) {
@ -2842,6 +2856,14 @@ static bool llm_load_tensors(
} }
} }
if (defer_expert_mmap && !dry_run) {
LLAMA_LOG_INFO("%s: dense parameters loaded in %.2fs (%.2f GiB), expert parameters deferred (%.2f GiB)\n",
__func__,
(ggml_time_us() - model.t_start_us) / 1000000.0,
ml.expert_tensor_index.dense_bytes / 1024.0 / 1024.0 / 1024.0,
ml.expert_tensor_index.deferred_bytes / 1024.0 / 1024.0 / 1024.0);
}
if (!ml.use_mmap && ml.repack_tensors) { if (!ml.use_mmap && ml.repack_tensors) {
int n_repacked = 0; int n_repacked = 0;
for (auto& it : model.tensors_by_name) { for (auto& it : model.tensors_by_name) {
@ -2899,6 +2921,7 @@ static int llama_model_load(const std::string & fname, llama_model & model, llam
try { try {
llama_model_loader ml(fname, params.ncmoe, params.use_mmap, params.check_tensors, llama_model_loader ml(fname, params.ncmoe, params.use_mmap, params.check_tensors,
params.repack_tensors, params.use_thp, params.merge_qkv, params.merge_up_gate_exps, params.repack_tensors, params.use_thp, params.merge_qkv, params.merge_up_gate_exps,
params.defer_experts,
params.kv_overrides, params.tensor_buft_overrides); params.kv_overrides, params.tensor_buft_overrides);
model.hparams.vocab_only = params.vocab_only; model.hparams.vocab_only = params.vocab_only;
@ -2913,6 +2936,13 @@ static int llama_model_load(const std::string & fname, llama_model & model, llam
} catch(const std::exception & e) { } catch(const std::exception & e) {
throw std::runtime_error("error loading model hyperparameters: " + std::string(e.what())); throw std::runtime_error("error loading model hyperparameters: " + std::string(e.what()));
} }
if (params.defer_experts && params.use_mmap) {
#ifdef __linux__
ml.build_expert_tensor_index(model.hparams);
#else
LLAMA_LOG_WARN("%s: deferred expert loading is only supported on Linux; ignoring defer_experts\n", __func__);
#endif
}
try { try {
LLM_KV kv(model.arch); LLM_KV kv(model.arch);
model.vocab.load(ml, kv); model.vocab.load(ml, kv);
@ -4973,6 +5003,7 @@ struct llama_model_params llama_model_default_params() {
/*.mtp =*/ false, /*.mtp =*/ false,
/*.dry_run =*/ false, /*.dry_run =*/ false,
/*.flash_attn =*/ true, /*.flash_attn =*/ true,
/*.defer_experts =*/ false,
}; };
#ifdef GGML_USE_METAL #ifdef GGML_USE_METAL