Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions common/arg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2535,6 +2535,20 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
LOG_WRN("DEPRECATED: --defrag-thold is deprecated and no longer necessary to specify\n");
}
).set_env("LLAMA_ARG_DEFRAG_THOLD"));
add_opt(common_arg(
{"--moe-expert-cache"}, "N",
string_format("GPU cache slots per host-resident MoE expert layer, 0 = disabled (default: %d)", params.n_moe_cache_slots),
[](common_params & params, int value) {
params.n_moe_cache_slots = value;
}
).set_env("LLAMA_ARG_MOE_EXPERT_CACHE"));
add_opt(common_arg(
{"--moe-expert-cache-inserts"}, "N",
string_format("max expert uploads per layer per decode step for the MoE expert cache (default: %d)", params.n_moe_cache_inserts),
[](common_params & params, int value) {
params.n_moe_cache_inserts = value;
}
).set_env("LLAMA_ARG_MOE_EXPERT_CACHE_INSERTS"));
if (ex == LLAMA_EXAMPLE_SERVER) {
// this is to make sure this option appears in the server-specific section of the help message
add_opt(common_arg(
Expand Down
2 changes: 2 additions & 0 deletions common/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
cparams.n_outputs_max_per_seq = std::max(params.n_outputs_max_per_seq, 0);
cparams.n_batch = params.n_batch;
cparams.n_ubatch = params.n_ubatch;
cparams.n_moe_cache_slots = params.n_moe_cache_slots;
cparams.n_moe_cache_inserts = params.n_moe_cache_inserts;
cparams.n_threads = params.cpuparams.n_threads;
cparams.n_threads_batch = params.cpuparams_batch.n_threads == -1 ?
params.cpuparams.n_threads : params.cpuparams_batch.n_threads;
Expand Down
2 changes: 2 additions & 0 deletions common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ struct common_params {
int32_t n_ctx = 0; // context size, 0 == context the model was trained with
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
int32_t n_moe_cache_slots = 0; // GPU cache slots per host-resident MoE expert layer (0 = disabled)
int32_t n_moe_cache_inserts = 2; // max expert uploads per layer per decode step
int32_t n_keep = 0; // number of tokens to keep from initial prompt
int32_t n_chunks = -1; // max number of chunks to process (-1 = unlimited)
int32_t n_parallel = 1; // number of parallel sequences to decode
Expand Down
9 changes: 9 additions & 0 deletions ggml/include/ggml.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@ extern "C" {
// Returns the old callback for chaining
GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t callback);

struct ggml_tensor;

// MoE expert-routing observation callback: invoked by the CPU mul_mat_id
// with the op's expert-id tensor (I32 [n_expert_used, n_tokens]). Used by
// the llama MoE expert cache to drive LRU placement decisions.
typedef void (*ggml_moe_obs_cb_t)(const char * tensor_name, const struct ggml_tensor * ids, void * ud);
GGML_API void ggml_set_moe_obs_callback(ggml_moe_obs_cb_t cb, void * ud);
GGML_API ggml_moe_obs_cb_t ggml_get_moe_obs_callback(void ** ud);

GGML_NORETURN GGML_ATTRIBUTE_FORMAT(3, 4)
GGML_API void ggml_abort(const char * file, int line, const char * fmt, ...);

Expand Down
52 changes: 52 additions & 0 deletions ggml/src/ggml-cpu/ggml-cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1623,17 +1623,69 @@ static void ggml_compute_forward_mul_mat_id(
// initialize matrix_row_counts
memset(matrix_row_counts, 0, n_as*sizeof(int64_t));

// llama MoE expert cache: when src[3] is set it is an I32 table mapping
// expert id -> device cache slot, with op_params[0] holding the "not
// cached" dummy value. Cached ids are served by the device-side cache
// chain, so this op skips them and zeroes their dst rows instead.
const int32_t * moe_tbl = NULL;
int32_t moe_dummy = 0;
if (dst->src[3]) {
moe_tbl = (const int32_t *) dst->src[3]->data;
moe_dummy = ggml_get_op_params_i32(dst, 0);
}

// group rows by src0 matrix
for (int64_t iid1 = 0; iid1 < ids->ne[1]; ++iid1) {
for (int id = 0; id < n_ids; ++id) {
const int32_t i02 = *(const int32_t *) ((const char *) ids->data + iid1*ids->nb[1] + id*ids->nb[0]);

assert(i02 >= 0 && i02 < n_as);

if (moe_tbl && moe_tbl[i02] != moe_dummy) {
memset((char *) dst->data + id*nb1 + iid1*nb2, 0, ne0*sizeof(float));
continue;
}

MMID_MATRIX_ROW(i02, matrix_row_counts[i02]) = (struct mmid_row_mapping) {id, iid1};
matrix_row_counts[i02] += 1;
}
}

// MoE routing observation for the llama expert cache
{
void * moe_obs_ud = NULL;
ggml_moe_obs_cb_t moe_obs_cb = ggml_get_moe_obs_callback(&moe_obs_ud);
if (moe_obs_cb && strstr(src0->name, "ffn_gate_exps")) {
moe_obs_cb(src0->name, ids, moe_obs_ud);
}
}

// GGML_MOE_LOG: append the routed expert ids of every ffn_gate_exps
// mul_mat_id to the file named by the env var. Diagnostic only; the
// whole block is inert unless GGML_MOE_LOG is set at first use.
{
static FILE * moe_log_file = NULL;
static int moe_log_state = -1;
if (moe_log_state == -1) {
const char * moe_log_path = getenv("GGML_MOE_LOG");
if (moe_log_path && moe_log_path[0]) {
moe_log_file = fopen(moe_log_path, "a");
}
moe_log_state = moe_log_file ? 1 : 0;
}
if (moe_log_state == 1 && strstr(src0->name, "ffn_gate_exps")) {
flockfile(moe_log_file);
for (int64_t iid1 = 0; iid1 < ids->ne[1]; ++iid1) {
fprintf(moe_log_file, "%s", src0->name);
for (int id = 0; id < n_ids; ++id) {
const int32_t i02 = *(const int32_t *) ((const char *) ids->data + iid1*ids->nb[1] + id*ids->nb[0]);
fprintf(moe_log_file, " %d", i02);
}
fputc('\n', moe_log_file);
}
funlockfile(moe_log_file);
}
}
}

// reset current_chunk
Expand Down
15 changes: 15 additions & 0 deletions ggml/src/ggml.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t cal
return ret_val;
}

static ggml_moe_obs_cb_t g_moe_obs_cb = NULL;
static void * g_moe_obs_ud = NULL;

void ggml_set_moe_obs_callback(ggml_moe_obs_cb_t cb, void * ud) {
g_moe_obs_cb = cb;
g_moe_obs_ud = ud;
}

ggml_moe_obs_cb_t ggml_get_moe_obs_callback(void ** ud) {
if (ud) {
*ud = g_moe_obs_ud;
}
return g_moe_obs_cb;
}

void ggml_abort(const char * file, int line, const char * fmt, ...) {
fflush(stdout);

Expand Down
4 changes: 4 additions & 0 deletions include/llama.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ extern "C" {
uint32_t yarn_orig_ctx; // YaRN original context size
float defrag_thold; // [DEPRECATED] defragment the KV cache if holes/size > thold, <= 0 disabled (default)

// GPU-resident LRU cache for host-offloaded MoE expert weights [EXPERIMENTAL]
int32_t n_moe_cache_slots; // cache slots per host-resident expert layer (0 = disabled)
int32_t n_moe_cache_inserts; // max expert uploads per layer per decode step

ggml_backend_sched_eval_callback cb_eval;
void * cb_eval_user_data;

Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ add_library(llama
llama-model-loader.cpp
llama-model-saver.cpp
llama-model.cpp
llama-moecache.cpp
llama-quant.cpp
llama-sampler.cpp
llama-vocab.cpp
Expand Down
9 changes: 9 additions & 0 deletions src/llama-context.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "llama-context.h"

#include "llama-moecache.h"

#include "ggml.h"
#include "llama-arch.h"
#include "llama-graph.h"
Expand Down Expand Up @@ -91,6 +93,8 @@ llama_context::llama_context(
// may need to be backend-dependent
LLAMA_LOG_INFO("%s: constructing llama_context\n", __func__);

llama_moe_cache_init(model, params.n_moe_cache_slots, params.n_moe_cache_inserts);

t_start_us = model.t_start_us;
t_load_us = model.t_load_us;

Expand Down Expand Up @@ -2030,6 +2034,9 @@ int llama_context::decode(const llama_batch & batch_inp) {
// wait for the computation to finish (automatically done when obtaining the model output)
//synchronize();

// apply throttled MoE expert-cache updates between graph executions
llama_moe_cache_step();

return 0;
}

Expand Down Expand Up @@ -3613,6 +3620,8 @@ llama_context_params llama_context_default_params() {
/*.yarn_beta_slow =*/ -1.0f,
/*.yarn_orig_ctx =*/ 0,
/*.defrag_thold =*/ -1.0f,
/*.n_moe_cache_slots =*/ 0,
/*.n_moe_cache_inserts =*/ 2,
/*.cb_eval =*/ nullptr,
/*.cb_eval_user_data =*/ nullptr,
/*.type_k =*/ GGML_TYPE_F16,
Expand Down
69 changes: 69 additions & 0 deletions src/llama-graph.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "llama-graph.h"

#include "llama-moecache.h"

#include "llama-impl.h"
#include "llama-model.h"
#include "llama-batch.h"
Expand Down Expand Up @@ -2102,7 +2104,27 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
//call early so that topk-moe can be used
ggml_build_forward_expand(gf, weights);

// MoE expert cache (see llama-moecache.h): during single-token decode on a
// layer whose experts live in host memory, run a parallel mul_mat_id chain
// over a device-resident cache of hot experts. Cached ids are skipped by
// the CPU chain (src[3] table) and served by the cache chain; uncached ids
// map to the cache's zero slot. The two outputs sum to the exact result.
const llama_moe_cache_layer * mcache = nullptr;
ggml_tensor * mc_slot_ids = nullptr;
if (n_tokens == 1 && !gate_up_exps && gate_exps && down_exps &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the n_tokens == 1 here means that this will basically never trigger if there's a draft model. You'd (usually) validate N tokens per round and generate 1 in parallel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually mentioned that in the initial comment, nevermind

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also related to this, by doing this you have different graphs (and number of nodes) between decode and prefill, and that can cause reallocs all the time. When doing the first decode with n_tokens==1 with a different topology, a realloc happens that uses the new small n_kv. Then over time the KV cache fills up, QSA inputs that are depending on n_kv grow above their reserved limits and then there's a realloc every 250-300 decode steps (which wouldn't happen if the prefill allocation was kept, for example).

!up_exps_b && !gate_exps_b && !down_exps_b &&
!up_exps_s && !gate_exps_s && !down_exps_s &&
type_op == LLM_FFN_SILU && !weight_before_ffn && loras->empty()) {
mcache = llama_moe_cache_lookup(up_exps);
}
if (mcache) {
mc_slot_ids = ggml_get_rows(ctx0, mcache->dev_table, selected_experts); // [1, n_expert_used, 1]
mc_slot_ids = ggml_reshape_2d(ctx0, mc_slot_ids, n_expert_used, 1);
cb(mc_slot_ids, "ffn_moe_cache_slots", il);
}

cur = ggml_reshape_3d(ctx0, cur, n_embd, 1, n_tokens);
ggml_tensor * mc_inp = cur;

if (weight_before_ffn) {
// repeat cur to [n_embd, n_expert_used, n_tokens]
Expand Down Expand Up @@ -2138,6 +2160,11 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
up = build_lora_mm_id(up_exps, cur, selected_experts, up_exps_s); // [n_ff, n_expert_used, n_tokens]
cb(up, "ffn_moe_up", il);

if (mcache) {
up->src[3] = mcache->host_table;
up->op_params[0] = mcache->n_slots;
}

if (up_exps_s) {
cb(up, "ffn_moe_up_scaled", il);
}
Expand All @@ -2150,6 +2177,11 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
if (gate_exps) {
cur = build_lora_mm_id(gate_exps, cur, selected_experts, gate_exps_s); // [n_ff, n_expert_used, n_tokens]
cb(cur, "ffn_moe_gate", il);

if (mcache) {
cur->src[3] = mcache->host_table;
cur->op_params[0] = mcache->n_slots;
}
} else {
cur = up;
}
Expand Down Expand Up @@ -2255,6 +2287,43 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
experts = build_lora_mm_id(down_exps, cur, selected_experts, down_exps_s); // [n_embd, n_expert_used, n_tokens]
cb(experts, "ffn_moe_down", il);

if (mcache) {
experts->src[3] = mcache->host_table;
experts->op_params[0] = mcache->n_slots;

// device-side chain over the cached experts, mirroring the LLM_FFN_SILU
// activation above (the only type_op the cache path is enabled for)
ggml_tensor * up_g = ggml_mul_mat_id(ctx0, mcache->up_c, mc_inp, mc_slot_ids);
ggml_tensor * gate_g = ggml_mul_mat_id(ctx0, mcache->gate_c, mc_inp, mc_slot_ids);
cb(up_g, "ffn_moe_cache_up", il);
cb(gate_g, "ffn_moe_cache_gate", il);

ggml_tensor * act_g = nullptr;
{
const float limit = il >= 0 ? hparams.swiglu_clamp_exp[il] : 0.0f;
constexpr float eps = 1e-6f;
if (limit > eps) {
up_g = ggml_clamp(ctx0, up_g, -limit, limit);
if (arch == LLM_ARCH_DEEPSEEK4 || (arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0)) {
gate_g = ggml_clamp(ctx0, gate_g, -INFINITY, limit);
act_g = ggml_swiglu_split(ctx0, gate_g, up_g);
} else {
ggml_tensor * ga = ggml_silu(ctx0, gate_g);
ga = ggml_clamp(ctx0, ga, -INFINITY, limit);
act_g = ggml_mul(ctx0, ga, up_g);
}
} else {
act_g = ggml_swiglu_split(ctx0, gate_g, up_g);
}
}

ggml_tensor * down_g = ggml_mul_mat_id(ctx0, mcache->down_c, act_g, mc_slot_ids);
cb(down_g, "ffn_moe_cache_down", il);

experts = ggml_add(ctx0, experts, down_g);
cb(experts, "ffn_moe_cache_merged", il);
}

if (down_exps_s) {
cb(experts, "ffn_moe_down_scaled", il);
}
Expand Down
Loading