CUDA streaming: per-size-class expert caches, keep cache warm across prefills#504
CUDA streaming: per-size-class expert caches, keep cache warm across prefills#504iCreil wants to merge 1 commit into
Conversation
…prefills Mixed-quant GGUFs (e.g. the official Flash q2-q4-imatrix) interleave layers whose routed experts have different byte sizes. The CUDA expert cache was a single global slab keyed on the last-seen size: every q2<->q4 layer transition reset the runtime cap and released the whole cache, so decode on mixed models was stuck at ~1 t/s with a permanent cap-notice flood. Additionally, prefill batch loads released the entire resident expert cache unconditionally before allocating their staging buffers, forcing a full cache re-warm on every request (this also affects uniform models). Changes: - keep up to 4 expert caches, one per (gate,down) byte-size class; runtime caps and cap notices are tracked per class - begin_compact_load: try the staging allocation first and only sacrifice the expert cache when VRAM is actually short (retry once) - let the prefill batch path read the global cache (hits are device-to- device copies instead of host uploads), appending on miss only while free capacity remains, never evicting: a long prompt cannot cycle out the decode working set On an RTX PRO 6000 Blackwell 96GB with the official Flash q2-q4-imatrix GGUF, a 200-word completion goes from not finishing within 400s (~0.9 t/s) to ~25 t/s sustained decode with --ssd-streaming-cache-experts 9472 and DS4_CUDA_STREAMING_EXPERT_CACHE_RESERVE_GB=12 (97% cache hit rate per the VERBOSE stats). Uniform-quant GGUFs keep their existing behaviour.
…fills Port of iCreil's PR against upstream main, applied to the glm-local CUDA streaming stack (comments translated to English): 1. Per-size-class expert caches (4 classes keyed on gate/down expert bytes). Mixed-quant models no longer thrash the cache on layer-size transitions. For this fork's GLM merge it means the Q2_K MTP draft layer (blk.78) can be cached alongside the IQ2_XXS layers instead of falling back to model views, and the slab-budget gate now sees a per-class budget. 2. Prefill batch loads try their staging allocation before releasing the resident expert cache, and only sacrifice it when VRAM is truly short. Short chat prompts no longer throw away the warm decode working set. 3. Prefill loads may append to free cache capacity but never evict valid slots, so long prompts cannot cycle out the decode working set. Prefill hits become device-to-device copies. Tested on the 4060 Ti / GLM-5.2 rig: one-shot regression clean (0.33 t/s, correct output), two-turn chat clean (turn 2 resume 0.35/0.32 t/s, memory low point 7.7GB, no OOM).
…t-register branch The mixed model alternates expert size classes across layers; without per-size-class caches the single global cache was released and re-allocated on every class flip (9.5ms/layer of host time, zero hits, as measured by DS4_CUDA_SELECTED_LOAD_PROFILE). antirez#504 is the fix.
|
Some real-workload numbers from running this PR against the mixed-quant file it targets (DeepSeek V4 Flash, 37 routed layers IQ2XXS/Q2K + 6 layers with Q4_K experts, 91 GB GGUF fully page-cache resident; RTX PRO 6000 Blackwell 96 GB, Chat-shaped test against
A timing probe around the selected-expert load shows where the time was going: without the fix each layer's load costs ~10.6 ms, of which only ~1.1 ms is the actual H2D wait — the other ~9.5 ms is host-side Happy to share the exact scripts and configs if useful. |
Fixes #503.
See the issue for the full analysis and benchmarks. Summary of the three changes, all in
ds4_cuda.cu:1. Per-size-class expert caches.
g_stream_expert_cachebecomes a fixed array of 4 caches keyed by(gate_expert_bytes, down_expert_bytes).note_size()is replaced byclass_index()(assigns a free slot on first sight; recycles class 0 in the — never observed — case of more than 4 distinct sizes). Runtime caps and cap-notice dedup move to per-class arrays.prepare()operates on the class cache; cross-class transitions no longer release anything.2. Conditional release in
begin_compact_load. The unconditionalrelease_all()on theallow_global_cache=0path becomes: tryensure_slots()first; on allocation failure release the expert caches and retry once. Long-prompt VRAM pressure is still handled, short prompts keep the warm cache.3. Prefill batch reads the cache.
prepare_selected_batchnow passesallow_global_cache=1plus a newallow_cache_evict=0mode: batch loads copy hits device-to-device and may append to free capacity, but never evict valid slots (an LRU cache smaller than a long prompt's expert union would otherwise be fully cycled, destroying the decode working set). The single-token decode path keeps full LRU behaviour (allow_cache_evict=1).Notes:
memsetover the struct containing astd::vectoris replaced by explicit member resets (vector().swap()), which also fixes a latent leak of the slots buffer.make cuda-genericwith Flash q2-imatrix (uniform: behaviour unchanged) and Flash q2-q4-imatrix (fixed: ~0.9 → ~25 t/s sustained decode, 97% cache hit rate perDS4_CUDA_STREAMING_EXPERT_CACHE_VERBOSE).