--prefetch-experts-slots: lookahead H2D prefetch of host-resident MoE expert weights - #146
Closed
leshchukandrej wants to merge 1 commit into
Closed
leshchukandrej wants to merge 1 commit into
leshchukandrej wants to merge 1 commit into
Conversation
…-resident MoE experts During prefill the scheduler must upload each expert's weight tensor from host (or system RAM, via --n-cpu-moe / auto-offload fit margin) to the GPU right before its MUL_MAT_ID split launches, serializing H2D behind compute. With large batches every expert is exercised, so routing ids offer nothing worth waiting for; prefetch instead uploads full expert tensors through a second backend instance on the same device into rotating staging slots while the current split computes (1-deep lookahead), then the consuming split does a per-split cross-stream event wait that is already satisfied by launch time. - new flag --prefetch-experts-slots N (default 0 = off; >=2 = full-tensor prefetch with 1-deep lookahead; recommended 3; capped at 4) - GPU staging cost = slots * max expert tensor, lazy-allocated on first fire and gracefully disabled if the device lacks async/event caps or allocation fails - decode is unaffected: fires are gated on MUL_MAT_ID splits with batch >= 2*n_expert (prefill-scale) and are skipped entirely in callback_eval mode - lossless: prefetch only changes WHEN the bytes land on device - the staged copy carries the same host weights and the consuming kernels run unchanged after the ready-event wait; with slots = 0 no code path changes at all - measured TTFT/prefill speedups on host-expert configs (24B A3B ncmoe 20: -11% at ~42k-token prompt, -20% at 200 tokens; 21.8GB 35B A3B auto-offload: -22% at ~42k tokens) with flat decode and unchanged output
Owner
|
This is a pretty complicated addition that's not directly adjacent to BeeLLama's features. I'll prefer it to go through upstream's review process via your PR there: ggml-org#28414. Thanks. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR body —
--prefetch-experts-slots: lookahead H2D prefetch of host-resident MoE expert weightsBranch:
feat/prefetch-experts-slots— commite502dd002on top ofv0.4.5(99be96e2e)Scope: 8 files, +327 / −0, this feature only (no other changes ride along)
What it does
When a MoE model's expert weights are not GPU-resident (
--n-cpu-moe N, orauto-offload with a fit margin such that part of the model streams from host RAM),
every prefill batch forces the scheduler to upload each used expert tensor
host→device right before the
MUL_MAT_IDsplit that consumes it. That H2Dtransfer sits on the critical path and serializes behind the compute of the
previous split, so large-batch prefill (prompt processing) is dominated by
waiting on PCIe/NVLink instead of by kernels.
This PR makes those uploads overlap compute. During prefill it fires full-tensor
H2D copies ahead of need through a second backend instance on the same
device into rotating staging buffers, and the consuming split performs a
per-split cross-stream event wait that — because the copy was fired one split
earlier — is already satisfied by launch time.
User-visible surface
--prefetch-experts-slots N— default0= off, zero memory overhead.N >= 2enables the pipeline (needs at least 2 slots to rotate);3isrecommended,
4is the cap.N × max_expert_tensor, lazy-allocated on first fire.llama_context_params.prefetch_experts_slots/ggml_backend_sched_set_prefetch_experts_slots()for library users.How it works (mechanism)
ggml_backend_schedgains a small prefetch state machine(
ggml/src/ggml-backend.cpp):GGML_OP_MUL_MAT_ID,whose expert weight input is host-resident with
GGML_BACKEND_BUFFER_USAGE_WEIGHTS, and whose routed batch is prefill-scale(
ids ≥ 2 × n_expert). Incallback_evalmode (decode, MTP draft) it neverfires — decode is unaffected by construction.
ggml_backend_dev_initon the samedevice, only if the device advertises
async+eventscaps) uploads thefull expert tensor into slot buffers via
ggml_backend_tensor_set_async.With a large batch essentially every expert is used, so routing ids carry no
information worth waiting for — full-tensor prefetch is the right trade.
icomputes, the copy for spliti + 1 + LOOKAHEADis already in flight (
LOOKAHEAD = 1).ggml_backend_event_wait(ready[slot])on its backend right before graph launch. Because of the lookahead this is a
no-op in steady state; it is the only synchronization point that preserves
tool-call/
tool_choicesemantics (an earlier one-wait-per-graph variant wasdropped for that reason).
duration of its split and restored right after launch (kernels have already
captured the address), so any fallback path can never observe a dangling
slot. On any allocation/cap failure prefetch disables itself and the regular
copy path is used — correctness never depends on prefetch.
The only user-visible knob is the slot count: lookahead depth and wait mode are
hardcoded to their measured-optimal values.
Lossless
Prefetch changes when bytes arrive on device, never what is computed:
allocations, and the scheduler's compute loop behaves exactly as before.
is unchanged (same tensors, same kernels, same order); the ready-event wait
guarantees the data is on device before launch, making the overlap invisible
to the numerics. The only mutation is a temporary re-point of the input copy's
buffer/data pointer, restored before the next eval.
Empirical evidence (mindport build, greedy
temperature 0, identicalprompt/seed,
--n-cpu-moe 20on a 24B A3B, q4_0 KV cache):Measured effect (host-expert configs)
Large prefill batches are where the feature pays; decode is untouched by design.
-ncmoe 20-ncmoe 20Notes: on the 35B no
-ncmoewas used — the auto-offload fit margin(
-fitt 700) alone leaves ~half the expert weights host-resident, which is therealistic deployment this feature targets. At 200 tokens the ON arm's 0.76→1.64 s
TTFT is the staging fixed cost dominating a tiny prefill — the feature is aimed
at large batches and is simply off by default for everyone else. Recall/outputs
are identical across arms in every row.
Files
common/arg.cpp--prefetch-experts-slots Nflagcommon/common.{h,cpp}common_params.prefetch_experts_slotsplumbinginclude/llama.h,src/llama-cparams.h0)src/llama-context.cppsched_reserve()ggml/include/ggml-backend.h,ggml/src/ggml-backend.cpp