perf(moe): single-launch prefill buffer invalidation without hidden sync - #500
alvarorsouza-arch wants to merge 3 commits into
Conversation
|
We integrated and load-tested this on a production box and the hidden-sync diagnosis holds — the numbers below are from this branch vs 1. The kernel-only path breaks the existing CPU-device tests. (verified on Linux/py3.13/triton 3.6). 2. The kernel can address out of bounds of the cache. 3. Nothing exercised the kernel on GPU. A/B on the same machine (RTX 5090 D 32 GB, 125 GB RAM, NVIDIA Qwen3.8-Flash-Next NVFP4 nvfp4 experts via Triton,
The two commits are on |
|
Thanks for the thorough review and for validating in production, that A/B across 120 turns is exactly the evidence I could not produce on my own card. On the three findings:
Please open the PR against |
…idation helper CPU-resident test caches reach this helper through prefetch_prefill_layer and cannot run the Triton kernel (triton is a Linux-only dependency). The eager path clamped via Python slices; raw pointer arithmetic cannot, so refuse a buffer that does not fit instead of corrupting the heap. Assisted-by: pi
…ference on cuda Element-wise compare over three configs x 20 random trials, covering duplicate expert ids, empty slots and a nonzero slot_start, plus the bounds guard. Assisted-by: pi
|
Status update on this branch: the follow-up commits from @chrisqianz (eager fallback for non-CUDA devices, bounds guard, CUDA parity test) are merged after replication on our side. On RTX 4070 Ti SUPER 16 GB, py3.12, torch 2.11.0+cu130: the full |
The offload MoE cache cleared its prefill buffer slot map with a boolean-mask index, whose data-dependent shape hides a device-to-host synchronization; with two buffer reuses per chunk across 48 layers the host stalled on all enqueued GPU work per layer, serializing the prefill-overlap pipeline (upstream: ~3 s chunks becoming 20-150 s turns, GPU 0-3% busy). One fixed-shape Triton launch now does the same work. This fork already carried an earlier revision of the same kernel (7881875), so the merge is mostly additive: * invalidate.py -- took upstream's revision: identical kernel body plus a bounds guard and an in-wrapper CPU fallback. Ours had neither, so upstream's file is the superset. * moe/offload_cache.py -- kept ours: the call stays inside this fork's prefill profiler phase (FREETOKEN_PREFILL_PROFILE) and keeps the caller-side CPU fallback. NOTE for any deployment: the kernel is new to this tree and has only been exercised on CPU here. A first GPU run must pass compute-sanitizer memcheck on a small geometry plus the post-run Xid delta check before it serves traffic (~/.dsh/AGENTS.md rule 3). Verified: 621 passed, 129 skipped (tests/moe, tests/kvcache, tests/scheduler).
What
OffloadMoeCache._invalidate_prefill_bufferclears the two prefill-overlap buffer slot ranges with a boolean-mask index:A boolean index produces a data-dependent shape, so every call hides a device-to-host synchronization. This runs twice per prefill chunk (once per double-buffer slot), and each hidden sync drains ALL work already enqueued on the device, including the attention over the full cached context. That serializes the prefill-overlap pipeline: per layer, the host waits for the enqueued kernels before prefetching the next one, and the wait grows with the cached-context length.
Replacement
One fixed-shape Triton launch (
kernel/triton/moe/invalidate.py) does the whole invalidation: clearslot_for_idfor the held expert ids, mark the slots empty, zerousage. No data-dependent shapes, no sync, identical result.Evidence (py-spy, engine scheduler process, cached-prefix turn, 9,194 samples)
synchronize(_process_last_data, scheduler.py:314)_invalidate_prefill_buffer(offload_cache.py:635)replay(CUDA graph)nvidia-smi --query-gpu=utilization.gpusampled at 1 Hz during the turn reads 0-3%.After the fix,
_invalidate_prefill_bufferdisappears from the profile. Honest note: on a synthetic cached-prefix turn the wall time did not change (the remaining cost is the legitimate forward over the long context), but on real multi-turn agent workloads (WorkBuddy, ~130k cached context) the user-visible turn latency dropped from 20-150 s to low single digits on good turns. We believe the invalidation syncs were serializing the overlap pipeline against the enqueued context-sized attention work.Relationship to other code
This is the same boolean-indexing anti-pattern found in
kvcache/kv_host_offload.py::ensure_write_pagesin our other PR (host-RAM KV tier); both are fixed with fixed-shape kernels.Files
kernel/triton/moe/__init__.py,kernel/triton/moe/invalidate.pymoe/offload_cache.py(_invalidate_prefill_bufferbody only)