Repository navigation
Dual GPU lockup fix on UD-Q4_K_XL - #2
Conversation
…ond GPU's launcher Dual-GPU decode deadlocked when AdaptiveTier::pump_bytes fed pageable arena blobs to cudaMemcpyBatchAsync: the driver's staging enqueue blocked the host thread that launches GPU1's next-layer graph, so GPU0's wait_flag_ge never saw its flag. Stage every pageable slice through a pinned ring before the batch copy, cap outstanding batches at kMaxFlying (retire() recycles the blocks when a batch's event lands), and skip the per-layer rows_in pump while the second-GPU flag is behind.
…r froze past ~6 pool workers) Since eddoursul#2 (ed8a888) the pumps call retire() to free staging blocks and flight slots, and discarded the moves-sent index it found. A batch landing just before a pump was then never admitted: apply_pending's own retire() saw nothing in flight. Its evicted residents stayed empty and, as an update waits for the previous one's in burst mode (one GPU), the tier never ranked again. It took a request ending with a batch in flight and the next one starting on the prompt path, which shorter windows made the rule: on a Ryzen 9 5950X with an RTX 3090 and UD-IQ4_XS with 6-14 pool workers the edit prompt decoded at 69 tok/s on 35% hits instead of 138 at 79%. retire() now raises landed_, a member, and apply_pending admits from it. STRATA_TIER_TRACE=1 prints one line per update (skipped or ranked) with the queue and the residency. Six bench prompts, decode geomean. Ryzen 9 5950X + RTX 3090: 4 workers 68.3 (unchanged), 12 workers 80.0 -> 97.2, 14 workers 98.2. Ryzen 7 5700X: 4 workers stay best and the speeds are unchanged (3090 63.5, dual 80.9). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for this fix - it is what made UD-Q4_K_XL usable on our RTX 3090 + RTX 5060 Ti box, and we have been running it on two machines since. One follow-up you may want to fold in. With the pumps calling Fix: Six-prompt decode geomean on the 5950X + 3090 (UD-IQ4_XS): 4 workers 68.3 (unchanged), 12 workers 80.0 -> 97.2, 14 workers 98.2. On a Ryzen 7 5700X the speeds are unchanged (4 workers stay best there). |
…r froze past ~6 pool workers) Since Niko1221#2 (ed8a888) the pumps call retire() to free staging blocks and flight slots, and discarded the moves-sent index it found. A batch landing just before a pump was then never admitted: apply_pending's own retire() saw nothing in flight. Its evicted residents stayed empty and, as an update waits for the previous one's in burst mode (one GPU), the tier never ranked again. It took a request ending with a batch in flight and the next one starting on the prompt path, which shorter windows made the rule: on a Ryzen 9 5950X with an RTX 3090 and UD-IQ4_XS with 6-14 pool workers the edit prompt decoded at 69 tok/s on 35% hits instead of 138 at 79%. retire() now raises landed_, a member, and apply_pending admits from it. STRATA_TIER_TRACE=1 prints one line per update (skipped or ranked) with the queue and the residency. Six bench prompts, decode geomean. Ryzen 9 5950X + RTX 3090: 4 workers 68.3 (unchanged), 12 workers 80.0 -> 97.2, 14 workers 98.2. Ryzen 7 5700X: 4 workers stay best and the speeds are unchanged (3090 63.5, dual 80.9). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit b320a6f)
|
Thanks I have incorporated your additional improvement into this pull. |
|
Thank you for the careful diagnosis. Merged into (The commits reach GitHub with the next push of |
…fix Gyro-S size Gyro-S is 58.5 GB (54.5 GiB). Gyro-M and AMD have now run end to end; long-prompt prefill is measured. The measured section now has the int8 decode numbers (rc1 includes them and --prefill auto), the server protocol, Gyro-M and the 24 GB RTX 3090; the cache-size rows stay, labelled as the 2026-10-02 build. Ported from agentionai/Strata rc2 c494121 (rc2 = 9ff02c9), author Laurent.
Many thanks for your work on this repo I have been using it over the upstream as it is still ~2x faster for me. However when I tried to run UD-Q4_K_XL with a Dual RTX3090 (one on x16 one x4 links) the output always locked on the first decode token. Here is the fix that I have been running. Submitting as It might help others with a similar problem.
It gives me ~90tk/s decode on UD-Q4_K_XL with 2x RTX3090, Intel 12500k, 128GB DDR4 3600.
Disclaimer most of the code was written by UD-Q4_K_XL
The problem
Dual-GPU decode on UD-Q4_K_XL froze completely after a few tokens (one GPU spinning ~100% on the second-GPU flag wait, the other ~0%). The IQ3_S model worked fine on the same code and the same dual-GPU config - it was only UD-Q4_K_XL that locked up.
Test machine: two RTX 3090s on an asymmetric link setup - the main GPU on a full Gen4 x16 slot, the second GPU on a x4 link (~3.3 GB/s measured).
Repro:
--main-gpu 1 --second-gpu 0 --adapt-every 1; the trace always ended the same way:With
--adapt-every 0the run always completed, so the adaptive-tier pump was the blocker. Free VRAM headroom (--vram-reserve-mib 2560) did not prevent it, so it was not paging. The lockup also appeared on the swapped config (second GPU on the fast x16 link), so it was not the slow link itself.Root cause
A four-party circular wait:
rows_incallback pumps adaptive-tier refills viacudaMemcpyBatchAsyncfrom the arena.cudaHostAllocfails beyond that). A pump batch with blobs from the 9 pageable layers needs driver staging, and the enqueue blocks while pending copies cannot complete against the spinning main stream.IQ3_S never locks because all 48 arena layers pin successfully, so the enqueue never needs staging. The swap config (fast link) locks too - the cycle only needs GPU1 to fall behind once. Freezes clustered at layers 39-47 (the pageable tail) with 9-45 MB batches; small pinned-layer batches always returned in ~0.2 ms.
The fix - three layers of defense
adaptive_tier.cpp): an 8 x 8 MiBcudaHostAllocstaging arena; every blob from a pageable source layer is memcpy'd into a pinned block before the batch copy, socudaMemcpyBatchAsyncsources are always pinned. Blocks recycle when their batch event retires; if all 8 are busy the pump stops early (never blocks). Falls back to the old path if the arena can't be allocated.kMaxFlying = 2+retire()): at most 2 copy batches outstanding; the pump is opportunistic, so a skipped tick is harmless.verify.cpp): skip the per-layerrows_inpump when the second-GPU flag is behind, so pump host time never delays the launch it is waiting on.A pinned-only A/B variant confirmed defense 1 alone stops the lockup; adding defenses 2-3 on top gave measurably improved decode performance (without them the pump's host time delays the very launch it waits on), so they are performance guards, not correctness-critical. The staging is a byte-identical pass-through (same source, length, destination), so output is fix-neutral.
Verification (2026-10-02)
--second-gpu 1 --adapt-every 1, the config that froze at window 3): 3/3 complete, but slower TK/s--second-gpu 0 --adapt-every 1): 3/3 complete, faster tk/s--second-gpu 0+--adapt-every 1: working, ~70-90 tok/s at ~96% expert-cache hits.Working config (UD-Q4_K_XL, second GPU on the fast link)
{ "exe": "./build/strata.exe", "args": [ "--pack", "d:/Strata-data/packs/ud-q4_k_xl", "--native", "d:/Strata-data/models/UD-Q4_K_XL/Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf", "--expert-profile", "./data/expert-profile.bin", "--expert-cache", "auto", "--vram-reserve-mib", "700", "--prefill", "16384", "--feed-max", "52", "--spec", "4", "--spec-min-p", "0.5", "--spec-lookup", "16", "--adapt-every", "1", "--mtp", "D:/Strata-data/mtp/rt", "--max-context", "200192", "--kv", "int8", "--pcie-mode", "kernel", "--pcie-frac", "0", "--second-gpu", "0", "--main-gpu", "1", "--head-split", "0" ], "cwd": ".", "tokenizer": "d:/Strata-data/packs/ud-q4_k_xl/tokenizer", "model_name": "qwen3.8-flash-next-ud-q4_k_xl", "log": "./strata-ud-q4_k_xl_dual_swap.log", "sampling": { "temperature": 1.0, "top_p": 0.95, "top_k": 20, "min_p": 0.0, "presence_penalty": 0.0, "repetition_penalty": 1.0 }, "lib_dirs": [] }