mirror of
https://github.com/Helldez/BigMoeOnEdge.git
synced 2026-10-15 13:53:21 +00:00
The benchmark call is pinned, and both pages were written for the maintainer rather than for the people landing on them. community-benchmarks.md opened with a list of hardware we want, which reads as an entry requirement, and neither page ever answered the first question a contributor has: what do I set? community-benchmarks.md: - a "start here" for the three cases someone is actually in (PC or laptop, Android phone, Apple hardware), each with the command and what to paste back - the settings-override table, which used to be one buried sentence - an explicit adb protocol for phones - "hardware we want to see" moved to the end as open questions: any hardware is a useful row, the list is what we cannot answer ourselves benchmark-method.md: - reference device out of the opening, named once at the end as the provenance of the published numbers - new "choosing the parameters": lossless, lossy and experimental knobs kept in separate tables, each with its default and the telemetry field that says whether moving it worked - the hard-won rules kept as method rather than as the story of one session The community protocol now pins --ubatch 512, which the app has always done and bench-report.sh never did: prefill width costs resident memory the expert cache would otherwise get, so a host row was running a different configuration from the app it is compared against. UBATCH= overrides it. Two platform limits documented for the first time: macOS has no O_DIRECT and the engine does not call the F_NOCACHE equivalent, so expert reads there go through the page cache while the metrics still report o_direct=1; and there is no iOS target at all. Both were already true.
4.9 KiB
4.9 KiB
Limitations and prior art
Prior art
BigMoeOnEdge is an engineering package, not a new technique. The ideas it combines:
- AirLLM — layer-by-layer streaming of >RAM models from disk.
- Apple, "LLM in a flash" — flash-aware weight streaming, windowing, sparsity-driven loading.
- FlexGen — offloading and I/O-bound throughput scheduling for large models.
- PowerInfer / EdgeMoE — hot/cold expert locality and expert-granularity residency on the edge.
The contribution here is a clean, modular, llama.cpp-native implementation of
expert-selective streaming that stays lossless and runs on the public API — no fork for the
serial path, and only a single ~25-line hook (with an explicit sunset) for the optional
--overlap feature. See seam.md § 3.
Limitations
- One setting makes output non-reproducible. Every other knob is deterministic given a
configuration:
--n-expert-usedchanges the output, but changes it the same way on every run.--drop-cold-expertsdecides per routing from live cache state, so the same prompt and the same flags can decode differently run to run, and the byte-identity gates cannot cover its output — only its machinery. Off by default in the CLI. - n=1 only. The expert sparsity exists only for single-token decode, so streaming is incompatible with speculative decoding or batching. Prefill streams the union of the prompt's routed experts (still far below the full bank, but larger than one token's).
- CPU experts. Streamed experts are computed on CPU; the rebind targets host memory. GPU offload of the streamed experts is not supported (the dense parts can still use the GPU). Decode is flash-I/O-bound anyway, so this is rarely the bottleneck.
- Shared experts stay resident. Architectures with an always-on shared expert (e.g.
gemma4,deepseek4) stream the routed experts but keep the shared expert — and any dense layers — resident (in the page cache, or in the engine's own buffers under--dense-weights anon), so the streamed fraction (and the memory saving) is smaller than for a purely routed model likeqwen3moe. The same applies to architectures whose first blocks are dense by design (lfm2moehas aleading_dense_block_count): those blocks name no expert tensors, so they are never streamed. - A resident tensor can be larger than RAM, and then it is only ever mmap'd.
qwen4exp(Qwen3.8-Flash-Next) carries a 51B n-gram embedding table (per_layer_token_embd, ~28.8 GB at IQ4_NL) that the graph reads sixteen rows at a time throughget_rows. It is not indexed by expert, so the streamer does not bind it, and it is bigger than any phone's memory, so no dense policy can make it resident: such a tensor stays mmap'd whatever--dense-weightsasks, and the engine says so at load. The streamed fraction of this architecture is therefore unusually low, and its per-token cost on that table is page faults on kilobyte reads rather than streamed expert bytes. That cost has not been measured on a device yet. - Streaming does not help a model that fits. The engine's reason to exist is a model larger than RAM. Registering an architecture says the layout streams losslessly, not that streaming is the fast way to run every model using it — a small MoE that fits in memory is faster loaded resident, and the registry rows are about coverage, not a recommendation.
- Repack must stay off. Loading uses
use_extra_bufts=false; you cannot combine streaming with weight repacking. - macOS does not bypass the page cache, and the telemetry does not say so. Apple has no
O_DIRECT;platform_iocompiles it away to0, and thefcntl(F_NOCACHE)equivalent is not called, so expert reads on a Mac go through the page cache the whole design exists to avoid. Worse,o_directin the metrics records the requested configuration rather than what the open actually did, so a macOS run reportso_direct=1while running buffered. macOS builds and produces correct output; its cache-hit and flash-per-token columns are not comparable with a Linux or Android row until this is fixed. - No iOS target. The core is portable C++ and the streaming path has no Android dependency, but there is no Xcode project here and iOS does not run command-line binaries, so there is no supported way to run or benchmark the engine on an iPhone or iPad.
- Windows throughput. The cache's reserve-then-commit-per-slice path is heavier on Windows than the POSIX lazy-commit path. The gates run on Windows; the throughput targets are stated for Android/Linux.
- Depends on a ggml scheduling behaviour (documented in seam.md) that is not a stability-guaranteed contract. Re-verified by the gates on each submodule bump.
Not goals
- Distributing a model across devices (a different axis).
- Beating a model that already fits in RAM — if it fits, run it resident.