-
Notifications
You must be signed in to change notification settings - Fork 22.8k
llama: GPU-resident LRU cache for host-offloaded MoE expert weights #27861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
|
|
@@ -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 && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| !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] | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -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; | ||
| } | ||
|
|
@@ -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); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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 == 1here 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.There was a problem hiding this comment.
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