Repository navigation
fix(umbp): create the standalone server's HIP queues before it serves - #800
Merged
Merged
Conversation
With the GPU nearly full, creating a hardware queue can stall a process for 30-92 s at a time on amdgpu 6.16 (reproduced on DKMS 6.16.6 and 6.16.13). libhsakmt allocates a queue's context-save area as an SVM range and migrates it into VRAM. The migration holds the process's mmap lock while TTM evicts one of the process's other SVM buffers; that eviction's worker needs the same lock and queues behind the process's other threads' mmap writers, so nothing moves until the fence wait times out. Every other HIP call in the server backs up behind it. The server created its queues lazily, mid-serving, when sglang has already filled the GPU: the first gather streams on a device create its compute queues, and the first copy on each copy engine creates that engine's SDMA queue (libhsakmt gives SDMA_BY_ENG_ID queues a context-save area too). A TP8 run with the server stalled for 926 s. HbmCopyEngine::Prewarm() now creates them at startup: 8 pooled gather streams per GPU, each with its descriptor staging sized and one empty gather run, one copy each way on every copy engine, and a hipMemcpy each way on the null stream. It also caps each device's pool at that many streams, so a gather beyond them waits for one instead of allocating another stream's device memory mid-serving. The server calls it before constructing its backend, because the host pool's background registration would otherwise slow each queue creation to about a second. UMBP_HBM_GATHER_PREWARM_STREAMS sets the stream count; 0 turns it off. Emulated on seven GPUs, one client per GPU registering a 2 GiB KV buffer, filling the GPU to ~250 MiB free and running 16 threads of 8192-range batch puts and gets: without the prewarm the server froze within seconds, calls taking up to 342 s; with it, 607k calls over 300 s took at most 1.3 s. A TP8 Kimi-K3 c14 hour on the node that had stalled ran without a stall: 1371 requests, no errors, 6191 tok/s per GPU against 6247 for the unfixed run.
Prewarm copied once on every copy engine of the GPU, so the server got an SDMA queue for each of them, 16 per MI355X. libhsakmt gives an SDMA queue a context-save area as large as a compute queue's, about 173 MiB of VRAM, so that was 2.7 GiB per GPU. With the pool capped at n streams, at most n gathers and one null-stream copy hold an engine at once, and clr gives each the lowest free one. The empty gathers and the null-stream copies already run while all of a device's streams are held, so they take exactly the engines the server can be given and create those queues; the per-engine copies are removed. After Prewarm(8) a process has 5 compute and 2 + 7 SDMA queues per GPU and 2.9 GB of VRAM instead of 4.2 GB. Its first null-stream copies, a first 8192-fragment gather and 16 threads of gathers and fallback copies in both directions then create no queue and allocate no VRAM, and the emulated server at 16 threads per GPU kept 9 SDMA queues per GPU throughout.
isytwu
force-pushed
the
fix/umbp-sa-prewarm-queues
branch
from
October 19, 2026 04:02
88938fb to
528b6ff
Compare
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.
Why
With the GPU nearly full, creating a hardware queue can stall a process for 30-92 s at a time on amdgpu 6.16 (reproduced on DKMS 6.16.6 and 6.16.13). libhsakmt allocates a queue's context-save area as an SVM range and migrates it into VRAM. The migration holds the process's mmap lock while TTM evicts one of the process's other SVM buffers; that eviction's worker (
svm_range_evict_svm_bo_worker) needs the same lock and queues behind the process's other threads' mmap writers (munmap,mprotect,kfd_ioctl_alloc_memory_of_gpu), so nothing moves until the fence wait times out. Every other HIP call in the server backs up behind it, on clr's SDMA engine allocator lock and libhsakmt's FMM lock.The server created its queues lazily, mid-serving, when sglang has already filled the GPU: a device's first gather streams create its compute queues, and the first copy on each copy engine creates that engine's SDMA queue. SDMA queues get a context-save area too (~173 MiB of VRAM on MI355X), because libhsakmt's
handle_concrete_asicskips it only forKFD_IOC_QUEUE_TYPE_SDMAandSDMA_XGMI, not for theSDMA_BY_ENG_IDqueues ROCr 7.2 creates (develop still does this). With #799 alone, a TP8 hour stalled for 926 s and 108 s. The first launch of the gather kernel on a device is another such stall: HIP loads the code object then, and with the GPU full that blocked every launch for ~28 s.Change
HbmCopyEngine::Prewarm(n), called by the server before it constructs its backend (the host pool's background registration would otherwise slow each queue creation to about a second):npooled gather streams per GPU, which creates the compute queues;hipMemcpyeach way on the null stream, which the fallback copies use.With the pool capped at
n, at mostngathers and one null-stream copy hold a copy engine at once, and clr gives each the lowest free one, so these copies take exactly the engines the server can be given and create only those SDMA queues (2 + 7 per GPU forn = 8, out of 16 engines).Each device's pool is capped at
nstreams: past that, a gather waits for a stream instead of allocating another one's device memory mid-serving. The gather path holds one stream at a time, so the wait cannot deadlock. Processes that never callPrewarmkeep the uncapped pool.UMBP_HBM_GATHER_PREWARM_STREAMSsetsn: default 8, the TP8 peak of concurrent gathers per GPU; 0 turns the prewarm off. With the gather kernel disabled, the null stream is still warmed.Test
HbmCopyEngine.GathersRunOnPrewarmedStreams: afterPrewarm(3), six concurrent gathers create no stream and copy correctly.The second commit removes a step of the first: copying once on every copy engine created SDMA queues for all 16 engines, 2.7 GiB of VRAM per GPU, 1.2 GiB of it for engines the server is never given.
Verification
Prewarm(8)a process has 5 compute and 2 + 7 SDMA queues per GPU and 2.8 GiB of VRAM (4.0 GiB with an SDMA queue for every engine). Its first null-stream copies in both directions, a first 8192-fragment gather, and 16 threads of gathers (64 and 8192 fragments) mixed with fallback copies in both directions then create no queue and allocate no VRAM. Without the prewarm the same steps create the queues as they go.test_transfer_engine: 24 tests pass. WithUMBP_DRAM_GATHER_KERNEL=0, the two gather tests skip and the rest pass.Not fixed here: KFD still evicts processes' queues under memory pressure on that node. In each TP8 hour there was one node-wide eviction, which stopped the sglang ranks for 3-6 s each at staggered times and so paused serving for 12 s and 35 s; the server had no copy waiting on it the second time. In the emulator the server's queues were evicted once per run as the clients filled their GPUs, for 0.6-46 s. The KFD lock interaction and the libhsakmt
SDMA_BY_ENG_IDcontext-save area will be reported to ROCm separately.