Repository navigation
llama : hybrid MoE expert cache with profile prefilling and zero-upload inference - #30594
voidstackloop wants to merge 10 commits into
Conversation
|
Hi @voidstackloop, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
Resolve conflicts with master (1b29621): - llama-moe-cache: master turned the LRU into an SLRU (ggml-org#30637). Rename the PR's profile prefill, hit counting and adaptive-swap code to the SLRU struct (moe_cache_slru); plan() keeps n_hit_profile and clears from_profile on a fill. - llama-graph: keep the PR's host_ids parameter and add master's build_hadamard_input call in build_lora_mm_id; apply master's MELLUM precision condition inside the PR's build_experts lambda. - llama-context: keep the hybrid graph-node reservation next to master's new sampler node count. - server-context: keep master's "graph reallocs" log and the PR's MoE cache stats. - test-llama-archs: master refactored tensor filling; port the quantized expert branch to it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
/bot review |
Automated code reviewCode review: hybrid MoE expert cache (PR 30594)I read Blocking (scope / process)(point 1) This is a very large, multi-area PR with no prior issue or discussion: it adds a new public ggml header ( (point 2) (point 3) The "Strata STRP" profile format references an external repo ( Correctness / robustness(point 4) (point 5) (point 6) The (point 7) (point 8) Will slow the review(point 9) Leftover development tooling shipped in the diff: (point 10) Comments referencing "ADR-002" ( (point 11) The new (point 12) (point 13) For plain (non-hybrid) Nits(point 14) (point 15) (point 16) (point 17) Overall: the engineering inside the mailbox and hybrid split logic is careful and the security-sensitive parsing is properly bounded, but this needs to be broken up and discussed with maintainers before it can be reviewed line-by-line; points 4, 5, 6 and 7 should be fixed in whatever form moves forward. This review was generated automatically by pi coding agent using |
build_mailbox, build_mailbox_recv and build_hybrid_ids are const and passed their per-layer state to ggml_custom_4d with C-style (void *) casts, which -Wcast-qual rejects under LLAMA_FATAL_WARNINGS. Use const_cast: the impl object itself is not const, the ops write their state through userdata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ggml-cpu : read GGML_MMID_CHUNK once in ggml_cpu_init() instead of lazily from every compute thread (data race on a non-atomic static) - ggml : spell out the GGML_TENSOR_FLAG_NO_SYNC contract on the flag itself - llama-moe-cache : hybrid layers held whole counted every hit as a profile hit; count them only while every expert of the layer is still from the profile (adaptive fills clear from_profile) - llama-moe-cache : document that do_rebalance leaves the SLRU lists alone because adaptive implies hybrid, which never calls plan() - remove development-only tooling: GGML_SCHED_PROFILE, GGML_CPU_OP_PROFILE and LLAMA_MOE_CACHE_NO_MAILBOX - drop references to an out-of-tree ADR document, clarify the llama4 (weight_before_ffn) input shape, note STRP header "slots" is informational, keep the CUDA include list sorted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@am17an @ggerganov this is a big PR, so I think it needs some rescoping or other suggestions. |
The mailbox's CPU op aborted the process when the GPU did not hand a layer over within 20 s, taking llama-server down with it. Now it computes no expert for the step (all -1 ids), sets an error flag, and llama_context::graph_compute turns the flag into GGML_STATUS_FAILED, so llama_decode returns -3 and rolls the step back. Later layers of the same step skip the wait once one has timed out. The GPU-side receive is launched after the CPU split, so giving up cannot deadlock it, and a late send carries an epoch the next step rejects. Checked by forcing the timeout: one error line, decode fails, exit code 1 instead of 134. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the conflict with 8baabd5 (llama : scale moe down projection per expert token, ggml-org#30861): master replaced the MISTRAL4/MELLUM F32 precision override on the MoE down projection with an amax rescale of src1. Move that rescale into the PR's build_experts lambda, the single path every expert computation goes through (plain, cache, hybrid hot/cold, mailbox), in place of the old ggml_prec_set_src there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the conflict with d454a20 (llama: create new fused op for QSA mask, ggml-org#30640) in ggml-cuda.cu: master added GGML_OP_TOP_K_MASK where this branch added GGML_OP_CUSTOM (the mailbox ops) to the compute dispatch and to supports_op. Keep both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I tested this out and wrote some notes, maybe they'll be of some use:
Qwen3.6-35B-A3B: slower
Flint Preview: faster, but I noticed perplexity is worse
I didn't do a perplexity test with Qwen as it didn't make sense to use this PR. |
Conflict in build_lora_mm_id: master's Hadamard change (ggml-org#30910: the folded weight reads the transformed input, the LoRA delta reads cur) applied to the PR's host-ids and cache-slot branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adaptive cache filled its free slots at 16 experts a generated token (128 every 8 tokens), so a large cache took hundreds of tokens to fill and the hybrid ran behind the plain cache until then. Now every token that missed uploads up to 512 of the missed experts, hottest first, into the free slots (fill_only: nothing is swapped out), and does nothing while no expert missed. The swap rounds once the cache is full are unchanged. L4, 16 GB cache, 256 generated tokens: Qwen3-Coder-Next IQ4_XS 21.1 -> 29.6 t/s (1024 tokens 32.7 -> 42.9; upstream's cache 38.5 and 46.6), Qwen3.5-35B Q6_K 32.4 -> 34.3 (1024 tokens 47.0 -> 50.1; upstream 50.5), 5 GB cache IQ4_XS 41.4 -> 46.5. RTX 4060 Laptop, 4 GB cache: unchanged (42 / 46 t/s). KL divergence unchanged (mean 0.0040, max 0.13). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflict in ggml/src/ggml-cpu/ggml-cpu.c: master (ggml-org#26631) now skips a -1 expert id in MUL_MAT_ID itself, which is what the hybrid MoE cache's CPU side already did, so the master version of that block is kept. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Overview
This PR adds hybrid MoE expert execution to
--cpu-moeand--moe-cache-mib:--moe-cache-profile <file.strp>: Pre-populates the GPU expert cache using offline-profiled routing distributions so the hottest experts start resident in VRAM.--moe-cache-hybrid: Keeps profiled hot experts on the GPU while the CPU computes the remaining misses directly from host RAM in parallel. Misses do not wait for PCIe uploads during generation.--moe-cache-audit <file.jsonl>: Provides a structured JSONL audit trail recording cache hit rates, resident expert IDs, and transfer overhead per generation.When to use it
The hybrid mode is for MoE models that do not fit in VRAM. It helps most when the GPU can hold only a small part of the experts: the CPU then computes many experts every token, and the hybrid runs that work in parallel with the GPU instead of after it. The further the model is beyond the VRAM, the more it gains; when most of the model fits, upstream's default offload (
-fit) is faster.-fit)-fit), 50.5 (cache)-fit)-fit)256 generated tokens unless noted. The laptop row is from the laptop tables below; the L4 rows are from Google Colab (12 vCPUs). * measured before the faster cache fill described below.
Not every case is covered yet: on a 12 GB RTX 4070 SUPER a tester in this thread measured the reverse for Qwen3.6-35B-A3B IQ4_XS (upstream cache 101 t/s, hybrid 85, before the faster fill), which has not been re-measured.
Additional information
test-llama-archs: all pass with 0 failures.llama-completionachieves 0 bytes uploaded over PCIe during generation with 100% profile cache hits.ggml/src/ggml-cuda/mailbox.cufor coordinating asynchronous CPU/GPU token routing without stalls.--moe-cache-mibusers (without the hybrid flags): the expert biases of the cached host layers (e.g. gpt-oss) are now copied to the GPU, and their size counts against the--moe-cache-mibbudget, so the same value holds slightly fewer expert slots than on master. Models without expert biases are unaffected.Benchmarks (vs. upstream llama.cpp)
Generation speed with the MoE experts in host RAM and attention on the GPU.
Hardware: RTX 4060 Laptop (8 GB), 16 threads (8 used), 88 GB RAM. CUDA 13.4.
Setup:
llama-completion,-c 2048 -n 256 --temp 0 -s 1, same 63-token prompt for every run,--moe-cache-mib 4096where a cache is used.Builds: upstream = ggml-org/llama.cpp master
1b29621; PR = this branch merged with that same master.No expert profile (
--moe-cache-profile) was used.--moe-cache-adaptivestarts with an empty cache and fills it while generating.Qwen3.5-35B-A3B Q6_K (mean of 2 rounds)
-fitchooses the offload)-ngl 99 --cpu-moe-ngl 99 --cpu-moe --moe-cache-mib 4096-ngl 99 --cpu-moe --moe-cache-mib 4096... --moe-cache-hybrid --moe-cache-adaptiveQwen3-Coder-Next Q6_K (1 round)
-fitchooses the offload)-ngl 99 --cpu-moe-ngl 99 --cpu-moe --moe-cache-mib 4096-ngl 99 --cpu-moe --moe-cache-mib 4096... --moe-cache-hybrid --moe-cache-adaptiveHybrid-adaptive is about 1.7x the best upstream configuration on both models.
Notes
test-llama-archschecks the hybrid configs against the CPU (NMSE <= 1e-4): 396/396 pass on CUDA (RTX 4060 Laptop and L4).Qwen3.5-35B-A3B IQ4_XS on a second machine (2 rounds)
Hardware: NVIDIA L4 (24 GB) on Google Colab, 12 vCPUs, 53 GB RAM.
Builds: upstream = ggml-org/llama.cpp master
d454a20; PR =3d37f74(this branch merged withd454a20).Setup: same prompt and flags as above,
--moe-cache-mib 5000to approximate a 12 GB card.-fitchooses the offload)-ngl 99 --cpu-moe-ngl 99 --cpu-moe --moe-cache-mib 5000-ngl 99 --cpu-moe --moe-cache-mib 5000... --moe-cache-hybrid --moe-cache-adaptive-fitrow keeps every expert on the GPU and is the fastest; it is listed for completeness only. The hybrid mode is for models that do not fit in VRAM. Against the configs that keep the experts in host RAM, hybrid-adaptive is about 1.25x here (1.7x on the laptop above, where the CPU share is larger).Faster fill of the adaptive cache (
1967019)The adaptive cache used to fill its free slots at 16 experts per generated token, so a large cache took hundreds of tokens to fill and the hybrid ran behind upstream's cache until then. It now uploads the experts that missed right after the token that missed them (up to 512 per token, only while the cache has free slots). The swaps once the cache is full are unchanged.
L4 24 GB, same prompt as above; upstream = master
84746c5, before =4f025fa, after =1967019. Generation t/s:† on the earlier head
3d37f74. ‡ with the change one small edit before1967019(the periodic swap round now also runs while the cache fills).test-llama-archs402/402 on the L4.Accuracy (KL divergence)
llama-perplexity --kl-divergence,-ub 1so every token takes the hybrid path, 20 chunks of 512 tokens (the repo's docs). The base is the same build with-ngl 99 --cpu-moeand no cache.Both configs differ from the base by the same small amount, which comes from running experts on GPU kernels instead of CPU kernels; the hybrid mode adds no divergence of its own on these runs.
Known limitation
Requirements