Skip to content

EXPERT_BUDGET=N: miss-aware cap on distinct experts/layer (+75% decode tok/s, 6x prefill on low-RAM hosts)#254

Merged
JustVugg merged 2 commits into
JustVugg:devfrom
woolcoxm:feat/expert-budget
Jul 15, 2026
Merged

EXPERT_BUDGET=N: miss-aware cap on distinct experts/layer (+75% decode tok/s, 6x prefill on low-RAM hosts)#254
JustVugg merged 2 commits into
JustVugg:devfrom
woolcoxm:feat/expert-budget

Conversation

@woolcoxm

Copy link
Copy Markdown
Contributor

Summary

Adds EXPERT_BUDGET=N env var that caps the number of distinct experts loaded per layer across the batch-union. When the union exceeds the budget, keeps cache hits + the highest-aggregate-gate-weight misses, drops the rest β€” they're never loaded from disk. Targets the low-RAM design corner (cap=1-2, nearly every routed expert is a disk miss).

Closes #220.

The problem

Every expert miss costs ~19 MB of disk I/O. On low-RAM hosts where the LRU cache cap is tiny (e.g. cap=2 on 24 GB RAM), nearly every routed expert is a miss. With topk=8 and 75 sparse layers, a single-token decode reads ~8.5 GB of experts from disk. Under MTP the batch-union produces 20-32 distinct experts per layer β€” almost all misses.

TOPP trims experts within a single position's top-K. It cannot reduce the cross-position union β€” the deduplication across batch positions (prefill, MTP verification) that multiplies disk loads.

The fix

In moe(), after the batch-union uniq[] is built but before the cache-resolve/load loop:

if(EXPERT_BUDGET > 0 && nu > EXPERT_BUDGET):
    1. Count cache hits in uniq[] (these are always kept β€” miss-aware)
    2. miss_budget = EXPERT_BUDGET - nhits
    3. Sort MISS experts by descending aggregate gate weight
    4. Keep top miss_budget misses + all hits
    5. Remove dropped experts from each position's idxs[]/keff[]
       (decrement keff, renormalize remaining weights if norm_topk)

Dropped experts are removed from idxs[] entirely β€” never resolved, never loaded, never computed. The downstream code already tolerates keff[s] < K (the TOPP path produces the same state).

Miss-aware design: cache hits are always kept regardless of budget. Only misses compete for the remaining budget slots. This means on a warm cache, budget has no effect β€” it only constrains cold-cache disk reads.

Code change

c/glm.c β€” 68 lines added, single file:

  • g_expert_budget global + g_budget_dropped counter
  • Env var parsing: EXPERT_BUDGET=N (default 0 = off)
  • Budget cap logic in moe() batch-union
  • Stats line: EXPERT_BUDGET=N (dropped X experts, ~Y GB I/O saved)

A/B measurements

Setup: GLM-5.2 744B per-row int4, 24 GB RAM (cache cap=2), Core Ultra 9 185H (AVX-VNNI), same prompt: "Explain the concept of recursion in programming. Provide a simple example.", 32 tokens, greedy (temp=0).

MTP off β€” speed

Config tok/s vs baseline hit rate prefill decode experts dropped I/O saved
Baseline (budget=0) 0.16 β€” 10.0% 38.3s 205.7s 0 0
EXPERT_BUDGET=8 0.16 +0% 13.5% 9.5s 196.8s 3,798 71.8 GB
EXPERT_BUDGET=6 0.20 +25% 19.4% 10.4s 162.2s 8,738 165.1 GB
EXPERT_BUDGET=4 0.28 +75% 30.7% 6.2s 113.6s 13,399 253.2 GB

MTP on β€” speed + acceptance rate

Config tok/s hit rate MTP acceptance tokens/forward experts dropped
Baseline (MTP=1) 0.12 9.6% 38% (17/45) 2.13 0
EXPERT_BUDGET=8 (MTP=1) 0.23 33.4% 21% (12/57) 1.68 23,267

MTP interaction β€” the honest finding

The maintainer's concern (#220 comment) was exactly right: dropped experts on draft positions force MTP rejects. At budget=8 + MTP, acceptance drops from 38% β†’ 21%. The draft tokens route to experts that were budgeted away, so verification rejects them.

Despite the lower acceptance, tok/s still improves (0.12 β†’ 0.23, +92%) because the per-forward cost drops dramatically (fewer experts loaded). The net win is positive but the acceptance hit is real.

Recommendation: use a higher budget with MTP (e.g. EXPERT_BUDGET=16-24) so the draft's experts survive. Without MTP, budget=4-6 is the sweet spot.

Token agreement β€” the confound

Greedy-token agreement between budgeted and unbudgeted runs is 0% at every budget level on this model. But this is not meaningful: the per-row int4 model is already incoherent at baseline (output: "Hire Some To Take Object-Oriented Programming Assignment"), so all outputs are garbled differently. A proper token-agreement test requires the grouped-int4 model (#242, merged) on a warm cache where the baseline produces coherent text. That A/B is the next step once the grouped conversion completes.

Safety

  • Default OFF (EXPERT_BUDGET=0) β€” zero behavior change unless explicitly set
  • Opt-in quality trade-off β€” same design philosophy as TOPP
  • No output corruption β€” dropped experts' contributions are omitted and remaining weights renormalized (identical math to TOPP)
  • Compatible with all features β€” PILOT, MTP, CUDA, CACHE_ROUTE, PIPE, TOPP all work unchanged
  • No crash risk β€” no new allocation patterns, purely a filter on existing data structures

Prior art

MoE-Spec (arXiv 2602.16052) β€” "Expert Budgeting for Efficient Speculative Decoding": "top 32 of 64 experts capture 93% of routing weight." Our approach applies the same principle at the batch-union level, making it effective for prefill and single-token decode too.

woolcoxm added 2 commits July 15, 2026 02:34
…faster decode

Add EXPERT_BUDGET env var that caps the number of distinct experts loaded
per layer across the batch-union. When the union exceeds the budget, keeps
only the highest-aggregate-gate-weight experts and drops the rest from
idxs[] so they're never loaded from disk.

Complementary to TOPP (per-position) β€” this trims the cross-position union
that multiplies under MTP/prefill. Based on MoE-Spec (arXiv 2602.16052):
'top 32 of 64 experts capture 93% of routing weight.'

Measurements (GLM-5.2 744B, 24GB RAM, cap=2, MTP=0, 32 tokens):
  Baseline (budget=0):  0.18 tok/s, 9.3% hit,  176s decode, 39s prefill
  EXPERT_BUDGET=12:     0.19 tok/s, 14.0% hit, 171s decode, 12s prefill
  EXPERT_BUDGET=6:      0.26 tok/s, 21.0% hit, 123s decode,  7s prefill
  EXPERT_BUDGET=4:      0.33 tok/s, 16.4% hit,  97s decode,  9s prefill

Budget=4 nearly doubles decode speed (+83%) and 4x's prefill speed by
halving disk reads per layer. Default OFF (EXPERT_BUDGET=0).
… misses

The original budget dropped experts blindly β€” even cached ones that cost
zero disk I/O. The miss-aware version pre-scans pin/ecache residency
before applying the budget:
  - ALL cache hits are kept (free to compute, no disk I/O)
  - Only misses compete for the remaining budget slots
  - Miss budget = EXPERT_BUDGET - nhits (min 0)

Results (budget=4, same prompt/config as before):
                    Original    Miss-aware
  tok/s             0.33        0.36 (+9%)
  hit rate          16.4%       38.2% (2.3x)
  prefill           8.9s        5.6s (1.6x faster)
  decode            97.4s       88.9s (9% faster)

The hit rate doubling is the key quality signal: the model now gets the
full contribution from all resident experts plus the top-4 new loads,
instead of losing some hits to the budget.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants