[feat](kt-kernel): --kt-mmap-experts-dir to keep CPU expert weights in a file, not anonymous RAM - #2190
Open
tim-odonnell wants to merge 1 commit into
Open
[feat](kt-kernel): --kt-mmap-experts-dir to keep CPU expert weights in a file, not anonymous RAM#2190tim-odonnell wants to merge 1 commit into
tim-odonnell wants to merge 1 commit into
Conversation
…n a file, not anon RAM The CPU MoE kernels hold each CPU-resident expert's repacked/quantized BufferB (gate/up/down) in a std::aligned_alloc(64, ~5 MiB) block. For a large MoE that is tens of GiB of anonymous memory per model -- the OOM killer's target, reclaimable only via swap. Those blocks are written once at load and read-only afterwards. --kt-mmap-experts-dir <dir> makes each block a MAP_SHARED slice of one file per (layer, NUMA part) under <dir> instead. File-backed pages are clean once written, so the kernel reclaims a cold expert's pages without swap and refaults them from the file (NVMe) on the rare cold route. A host whose CPU expert working set is close to total RAM can then serve the model. Unset (default) => unchanged std::aligned_alloc path. - operators/kt_weight_arena.hpp: KtWeightArena -- open()/alloc()/dtor. alloc() returns a 2 MiB-rounded mmap(MAP_SHARED) slice or falls back to std::aligned_alloc on any error; madvise(MADV_RANDOM); numa_tonode_memory to the constructing thread's node so first-touch stays local on multi-socket; dtor munmaps all slices. - GeneralMOEConfig::mmap_weights_dir (operators/common.hpp) + pybind (ext_bindings.cpp), next to the existing path/save/load. - AMX_MOE_BASE / AVX2_MOE_BASE: a KtWeightArena member; init() opens it once (no-op when unset) and routes the 3 per-expert BufferB allocs through it. AMX never frees BufferB (non-owning views) so the arena's dtor is the only cleanup; AVX2 keeps freeing owned_aligned_allocs_ and simply doesn't add file-backed pointers to that list. - Python: KTMoEWrapper(mmap_experts_dir=...) -> _create_inference_wrapper sets it as an attribute on the wrapper (backend __init__ untouched); each backend load_weights() copies self.mmap_experts_dir into MOEConfig.mmap_weights_dir. SFT path untouched. Measured on 1x CMP-170HX (sm_80) + 128 GB DDR5, DeepSeek-V4-Flash-0731 native MXFP4/FP8, ne=80, ctx 32k: decode 12.7 tok/s steady (unchanged vs anonymous), VRAM identical, CUDA-graph capture identical, scheduler anonymous RSS 105 GiB -> 2.4 GiB (rest file-backed Private_Clean, Swap 0), outputs bit-identical. First decode pass after a cold start is ~30% slower while routed-expert pages fault in; steady state is unchanged. Lightweight complement to kvcache-ai#1421 / MESH (kvcache-ai#2003): no io_uring, no residency manager -- the kernel page cache does the tiering. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KW36fR7syvcTaG7Yqqq8pP
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.
What
--kt-mmap-experts-dir <dir>: keep the CPU-resident per-expertBufferBweightblocks (the repacked/quantized gate/up/down matrices) in a
MAP_SHAREDfileinstead of anonymous heap.
The CPU MoE kernels allocate one
std::aligned_alloc(64, ~5 MiB)block perCPU-resident expert matrix. For a large MoE that is tens of GiB of anonymous
memory per model — the OOM killer's target, reclaimable only via swap. Those
blocks are written once during weight load and read-only afterwards.
With the flag set, each block becomes a
MAP_SHAREDslice of one file per(layer, NUMA part)under<dir>. File-backed pages are clean once written, sothe kernel reclaims a cold expert's pages without swap and refaults them from
the file (NVMe) on the rare cold route. A host whose CPU expert working set is
close to total RAM can then serve the model.
Unset (the default) → unchanged
std::aligned_allocpath, zero overhead.This is a lightweight complement to #1421 (SSD offload request) and MESH (#2003):
no io_uring, no residency manager — the kernel page cache does the tiering. Useful
as the "just don't OOM" floor.
How
operators/kt_weight_arena.hpp(new):KtWeightArena—open()creates andsizes the backing file;
alloc(n, &file_backed)returns a 2 MiB-roundedmmap(MAP_SHARED)slice, or falls back tostd::aligned_allocon any error(setting
file_backed = false);madvise(MADV_RANDOM);numa_tonode_memorytothe constructing thread's NUMA node so first-touch stays local on multi-socket;
the destructor
munmaps every slice and closes the fd.GeneralMOEConfig::mmap_weights_dir(operators/common.hpp) + pybind(
ext_bindings.cpp), next to the existingpath/save/load.AMX_MOE_BASEandAVX2_MOE_BASEeach get aKtWeightArena bb_arena_member;
init()opens it once (no-op when the dir is empty) and routes the 3per-expert
BufferBallocs throughbb_arena_.alloc().AMX_MOE_BASEneverfrees
BufferBitself (non-owning views), so the arena's destructor is the onlycleanup;
AVX2_MOE_BASEkeeps freeing itsowned_aligned_allocs_and simplydoes not add file-backed pointers to that list.
KTMoEWrapper(..., mmap_experts_dir=...)→_create_inference_wrappersets it as an attribute on the returned wrapper (sono backend
__init__signature changes) → each backend'sload_weights()copies
self.mmap_experts_dirintoMOEConfig.mmap_weights_dir.BaseMoEWrapperdefaults the attribute to"". SFT path untouched.The matching sglang-side flag (
--kt-mmap-experts-dirinserver_args.py, carriedon
KTConfig, passed to the inferenceKTMoEWrapper) is a companion PR againstkvcache-ai/sglang.
Testing
Measured on 1× CMP-170HX (GA100, sm_80) + Ryzen 9 9900X (single NUMA node) +
128 GB DDR5-5600, DeepSeek-V4-Flash-0731 native MXFP4/FP8 via SGLang + kt-kernel,
--kt-method MXFP4 --kt-num-gpu-experts 80, ctx 32k, fp8 KV, CUDA graphs on:--kt-mmap-experts-dirPrivate_Clean, file-backed;Swap: 0)On this box the model does not otherwise fit in 128 GB. The first decode pass
after a cold start is ~30 % slower while the routed experts' pages fault in from
the file; steady state is unchanged. A future
MADV_WILLNEED/ warm-on-load couldremove that.
The AVX2 path and multi-socket
numa_tonode_memorybehaviour are written tomirror the AMX path but I don't have that hardware to exercise them — review welcome
there in particular.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KW36fR7syvcTaG7Yqqq8pP