Skip to content

--moe-cache-auto ignores --num-tokens / --num-pages: expert fill overruns the budget and the KV pool OOMs at boot #383

Description

@gdevenyi

Before you start

What happened

--moe-cache-auto (the default) ignores --num-tokens / --num-pages when it plans the expert-slot fill, so a large explicit KV size boots into a CUDA OOM.

What I did: serve RadixArk/Qwen3.8-Flash-Next-NVFP4 (qwen4_exp, 512 experts x 48 layers, 262,144-token context) on a 48 GiB RTX 6000 Ada with the KV pool sized to the model's full context:

ft serve --model <RadixArk NVFP4 snapshot> --moe-backend offload --num-tokens 262144 \
  --max-running-requests 8 --cuda-graph-max-bs 8

What I expected: the auto planner reserves the 262,144 tokens of KV (+ the GDN state pool) and fills the rest of the budget with expert slots.

What happened instead: the planner reserves only kv_reserve_tokens (the small default floor), fills the budget with expert slots, logs --moe-cache-auto resolved moe_cache_size=... num_pages=... with a page count far below the request, and then the pool honours --num-tokens anyway: create_kv_pool allocates 4096 pages (6.2 GiB of QSA KV) plus the GDN state pool on top of an already-full card, and boot dies with torch.OutOfMemoryError in the KV / linear-state allocation after the experts are resident. Small --num-tokens values happen to fit inside the planner's slack, so this only shows on models with a large context and a per-token KV cost.

Root cause

Engine._resolve_auto_moe_cache_size (python/freetoken/engine/engine.py) passes kv_reserve_tokens=max(config.kv_reserve_tokens, min_reserve) to resolve_moe_cache_auto. config.num_page_override (set by --num-tokens / --num-pages) is not consulted, so the plan and the pool disagree about the KV size whenever the override exceeds the floor. The override is a requirement, not a floor: the pool will allocate exactly that many pages later.

Fix

Clamp the reserve to the override before planning; the expert fill then sees the KV the pool will actually allocate:

kv_reserve_tokens = max(config.kv_reserve_tokens, min_reserve)
if config.num_page_override is not None:
    # --num-pages / --num-tokens is a requirement, not a floor: plan the expert fill
    # against the KV the pool will actually allocate, or the two together overrun
    # the budget and the pool OOMs after the experts are already resident.
    kv_reserve_tokens = max(kv_reserve_tokens, config.num_page_override * page_tokens)
return resolve_moe_cache_auto(..., kv_reserve_tokens=kv_reserve_tokens, page_size=page_tokens, ...)

With this, the same command boots (moe_cache_size=8984 num_pages=4096, 2.6 GiB free after init) and serves the full 262k context (a 261,988-token prompt completes). A regression test added to tests/engine/test_cache_budget.py::test_engine_resolve_auto_moe_cache_size_maps_kwargs (a stub config with num_page_override = 20 against a stub without it) fails on main and passes with the clamp; tests/engine is 108 passed with it. I can open a PR with the fix + test if wanted.

Related: #340 adds the pool's dummy page to the same reserve; both apply together cleanly (measured: plan moves from 8984/4096 to 8983/4097, boot and throughput unchanged).

How did you install FreeToken

Built from source

FreeToken version

86214a9

OS

Ubuntu

OS details

Ubuntu 24.04, kernel 7.1.8 (Zabbly), no sudo on the box

GPU and driver

2x RTX 6000 Ada 48 GB (sm_89), CUDA 13.3 toolkit, torch 2.11.0+cu130

CPU and system RAM

2x Xeon Gold 6526Y, 503 GiB RAM

Checkpoint

RadixArk/Qwen3.8-Flash-Next-NVFP4 (also reproduces with Qwen/Qwen3.8-Flash-Next-FP8)

Command

ft serve --model <RadixArk NVFP4 snapshot> --moe-backend offload --num-tokens 262144 --max-running-requests 8 --cuda-graph-max-bs 8

Full log

The failing boot log was not kept (the run predates my harness); the planner line and the OOM location are as described above. Happy to reproduce and attach one on request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions