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.
Before you start
main(86214a9).What happened
--moe-cache-auto(the default) ignores--num-tokens/--num-pageswhen 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: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-tokensanyway:create_kv_poolallocates 4096 pages (6.2 GiB of QSA KV) plus the GDN state pool on top of an already-full card, and boot dies withtorch.OutOfMemoryErrorin the KV / linear-state allocation after the experts are resident. Small--num-tokensvalues 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) passeskv_reserve_tokens=max(config.kv_reserve_tokens, min_reserve)toresolve_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:
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 totests/engine/test_cache_budget.py::test_engine_resolve_auto_moe_cache_size_maps_kwargs(a stub config withnum_page_override = 20against a stub without it) fails on main and passes with the clamp;tests/engineis 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
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.