Skip to content

HIP gfx1030, 2x RX 6900 XT: verify hangs after a long prompt when MMQ prompt path, adaptive expert swaps and SDMA meet (any one off avoids it) #884

Closed as not planned

Description

@Vop3p

Summary

On 2x RX 6900 XT (gfx1030, HIP, Linux), engine 0.1.39 hangs on the first verify window after a ~31K-token prompt, once the engine has served about 7 earlier requests: verify: timed out at layer 1; its GPU waits were released but the GPU did not finish within 5 s (#267). Stock 6f32ec0 at default settings hung in 5 of 5 runs of the sequence below (and in 11 of 12 more runs of it with one diagnostic setting that did not avoid it, listed further down).

It needs three things at once; taking away any one of them stops it in my runs: the MMQ prompt path (STRATA_PREFILL_MMQ=0: 0/4), the primary card's adaptive expert swaps (--adapt-every 100000: 0/7), and SDMA copies (HSA_ENABLE_SDMA=0: 0/4). At the hang the primary card is 98-99% busy with no waves resident: its command processor is stuck at a wait, not a kernel. The root cause is not known yet; what is measured and what is not is listed below.

This is not #649, although the message is the same; see the last section.

Environment

  • Strata 6f32ec0 (engine 0.1.39), build-hip with -DSTRATA_PREFILL_MMQ=ON (as setup.py builds for AMD), gfx1030
  • 2x RX 6900 XT 16 GB, PCIe 4.0 x8 each; Ryzen 5 5600X; 128 GB DDR4 (about 69 GB still free at the hang)
  • Linux 7.0, ROCm 10.0
  • Model: Qwen3.8-Flash-Next GSQ-RCO IQ3_S, native pack, --ple-gguf, --mtp draft head
  • Arguments: --expert-cache auto --prefill auto --spec 4 --spec-min-p 0.5 --max-context 131072 --kv int8 --kv-resident 32768 --expert-cache-device1 auto --remote-expert-opt --pcie-frac 0, HIP_VISIBLE_DEVICES=1,0 (the expert arena, 46.84 GiB, is loaded whole and cudaHostRegistered)

Reproduction

A fresh server, then 8 chat requests, one after the other (greedy, thinking off, max_tokens 256): 7 short ones of 1.0K-4.9K tokens, then one of 31,219 tokens. The texts are slices of the repository's own docs and sources; the content does not matter (other slices hang the same way).

  • Stock, default settings, this sequence: 5 of 5 runs hung on the 31K request. With --prompt-cache 12 instead of the default 6: 1 of 2.
  • With 5 or fewer earlier requests before the 31K one: 0 of 5 (one of those with --prompt-cache 2).
  • Every stock hang was the first verify window after the batched prompt (6 tokens at position 31212). The one hang in the batched prompt read itself - no progress for 60 s ... (reading the prompt (batched): waiting for the GPU (attention, router) at layer 1 of the prompt chunk from token 0) - was in the STRATA_SH_STREAM=0 arm below.

STRATA_VERIFY_TRACE=1

The host serves layer 0 within about 3.3 ms (flag 1 A 1 B 1). The GPU's breadcrumbs stop after layer 0's waitCPU; combine is never stamped:

layer 0 group 0: pre(0)=0.0 hc-read0(1)=101.6 ... waitB(21)=661.2 PCIe grp(22)=672.4 waitCPU(23)=3849.1
TIMEOUT  window 410 step 1 layer 1 aux 600 | seq 1 flag 1 A 1 B 1
RELEASE  window 410 ... | seq 1 flag 1 A 1 B 1
RELEASE-NOT-DRAINED  window 410 ... aux 5000 | seq 1 flag 4294967295 A 4294967295 B 4294967295

The same in every hang I traced (5 runs). The CPU pool is not starved: it serves the layer in milliseconds.

The A/B settings suggested for this message in #649

Each was confirmed in the engine's own startup line or log, one per run, stopping at the first hang:

setting result
STRATA_VERIFY_COHERENT=1 ("coherent words explicit") hung on run 1
STRATA_DOORBELL_STORE=1 ("doorbell stored") hung on run 1
STRATA_RESIDENT_PIN=0 does not apply here (only with --resident-experts / --resident-budget-gib); instead STRATA_ARENA_PIN_GIB=1 ("0 slices pinned (0 GiB)", the whole arena pageable): hung on run 1

What the GPU is doing at the hang

  • Busy: gpu_busy_percent of the primary card (0000:0f:00.0) is 98-99% for the whole hang; the helper card is at 0%.
  • Kernel log: no amdgpu / kfd / ttm messages at all.
  • rocgdb: I attached with rocgdb (this needed an opt-in prctl(PR_SET_PTRACER) in a diagnostic build). One HSA queue of the primary card has 2,781 packets pending. The packet at its read index is a dispatch of gr_norm_split_kernel, grid [1536,4,1], which is the hyper-connection read for this window's 6 tokens. Another HSA queue has 376 packets pending, and its head packet is not a dispatch. The process has no AMDGPU waves at all. Together with the 99% busy reading, the command processor is waiting on something; no shader is spinning.
  • Attach changes the outcome: in the two runs where rocgdb attached, the GPU finished about 2.9 s after the release (RELEASE-DRAINED). In the four runs without it, it did not finish within 5 s. Attaching makes KFD unmap and remap the process's queues, so I could not read the head packets' signals with rocgdb before the state changed.

Bisection

One setting per arm, up to 4 runs per arm, stopping at the first hang:

arm hangs
stock, default settings 5 of 5
--adapt-every 100000 0 of 7
primary card's adaptive swaps only (helper's RemoteExpertOpt::adapt switched off in a diagnostic build) hung on run 1
helper's RemoteExpertOpt::adapt only (primary's swaps switched off) 0 of 4
HSA_ENABLE_SDMA=0 0 of 4
STRATA_SH_STREAM=0 hung on run 2
STRATA_PREFILL_MMQ=0 (same -DSTRATA_PREFILL_MMQ=ON binary; checked in the engine's /proc/<pid>/environ) 0 of 4

During the hangs, VRAM and GTT use were flat (sampled every ~1.6 s), and amd-evicted-vram in the engine's DRM fdinfo stayed 0.

Reading (not proven)

Three things are each necessary: the primary card's adaptive swaps (cudaMemcpyAsync H2D on adapt_stream, run on an SDMA engine), SDMA itself, and the MMQ prompt path. Both prompt paths stream the expert blobs over the copy stream, but the MMQ path adds per-group cross-stream waits and events (#372). The sequence is: swaps land in cache slots during the previous request's decode; the next prompt borrows cache slots and MMQ runs its groups there; right after the slots are refilled, the first verify window stalls. At the hang, the primary card's command processor sits on a cross-queue wait that queue remapping releases.

What I have not shown is whether the signal it waits on has already completed (a stale read or lost wakeup on the ROCm side) or genuinely has not (the copy, or an event dependency). Next I plan to read the head packets and their signal values from inside the process through an HSA_TOOLS_LIB shim, because rocgdb disturbs the state. Also not explained: why it needs about 7 earlier requests.

Cost of the three settings that avoid it

Same requests, means of 3-4 runs:

  • --adapt-every 100000: the prompt read is unchanged; decode is 3-11% slower (54-62 vs 60-69 t/s).
  • HSA_ENABLE_SDMA=0: decode is unchanged; the prompt read is 10-24% slower (short prompts 219-235 vs 287-308 t/s, 4K 385-405 vs 450-459, 31K 426 vs 471; the 31K baseline is the --adapt-every 100000 arm's, since the stock runs hung there, and that switch does not touch the prompt read).
  • STRATA_PREFILL_MMQ=0: decode is unchanged; the prompt read is 28-42% slower (short prompts 175-179 vs 285-307 t/s, 4K 297-314 vs 449-458, 31K 339 vs 471).

Not #649

#649 prints the same message, but there it hangs in decode at varying layers, on --resident-budget-gib with 31 GB of RAM, and STRATA_RESIDENT_PIN=0 avoided it. Here it hangs right after a long prompt, always between layers 0 and 1, with 128 GB of RAM, and an unpinned arena does not avoid it.

I can run an instrumented build here, or share the full logs and the request script.

Edited 2026-10-05: the stock run count was corrected from "8 of 10" to 5 of 5 at default settings (the earlier figure mixed in runs with other settings); the prompt-read hang is attributed to the STRATA_SH_STREAM=0 arm, and the 31K baseline of the cost section is marked as the --adapt-every arm's.

Activity

  1. Vop3p commented on Oct 5, 2026

    @Vop3p
    ContributorAuthor

    Update: at the hang, the barrier packet that holds everything up depends on a signal that has already completed, and the command processor does not move past it.

    How it was read

    An HSA_TOOLS_LIB shim (157 lines of C++) that wraps hsa_queue_create and hsa_amd_queue_create and records every AQL queue the process creates. When a trigger file appears, it writes, for each queue with pending packets, the read and write indices, the packets around the read index, and the current value of each packet's completion signal and of each barrier packet's dependency signals. It reads the same signals and queues again 2 s later. It only reads memory from inside the process; there is no debugger and no queue remap.

    Checked on a small program first: stream B waits on an event that stream A records after a kernel spinning on a host flag. The shim showed B's queue head as BARRIER_AND with its dependency signal at 1, and that signal was the completion signal of A's event-record packet. After the host flag was set, the same signal read 0, both queues were empty, and the program's result was correct.

    At the hang

    Stock 6f32ec0, the same reproduction and STRATA_VERIFY_TRACE=1 as above, plus HSA_TOOLS_LIB set to the shim (the only change). It hung on run 1 (RELEASE-NOT-DRAINED). Both reads, 2 s apart, were identical:

    primary card's queue pending head packet its dependency signal
    the verify stream's queue 2,861 BARRIER_AND value 1: the completion signal of a dispatch (grid (2560,6,1)) 7 packets behind the head of the next queue
    another queue 384 BARRIER_AND value 0 (completed); the read index did not move in 2 s

    The signal the second queue waits on is not the completion signal of any packet the shim printed (from two packets before to eight after each queue's read index); it did not print the rest of the pending packets.

    Reading

    The stall starts at a barrier-AND packet whose only dependency has completed and which the command processor does not release; the verify stream then waits behind it, legitimately, on that queue's later dispatch. This fits the earlier observations: the primary card at 98-99% busy with no waves resident, and the queues moving again after a remap (a rocgdb attach). In this snapshot the dependency chain Strata set up is satisfied, so I read this as a ROCm-side problem: the command processor does not see a signal that has completed. I have not shown why.

    Not shown yet: where the completed signal comes from. It is not among the completion signals in the printed windows. Since HSA_ENABLE_SDMA=0 avoids the hang, I suspect an SDMA copy's completion signal, but I have no direct evidence of that. Next, the shim will also record the completion signals passed to hsa_amd_memory_async_copy*, so the next hang can be matched against them. If it is an SDMA copy, I will try to reproduce this outside Strata and report it to ROCm.

    I can share the shim's source if it is useful.

  2. Vop3p commented on Oct 5, 2026

    @Vop3p
    ContributorAuthor

    Update 2: the barrier that never passes is inserted by hipGraphLaunch into the queue that runs the verify graph's shared-expert branch, not by any hipStreamWaitEvent in Strata.

    What was added

    1. The queue shim now samples every queue's read and write index every 20 ms. At a hang it reports how long each read index has not moved and when the head packet first appeared. Checked on a small program, where a kernel holds a queue for about 1 s before the dump: it reported 1,080-1,180 ms.

    2. An LD_PRELOAD wrapper records hipStreamWaitEvent, hipEventRecord and hipGraphLaunch: the time, stream, event and return address of each call. The return addresses are resolved to functions with addr2line. Checked on a small program, where it found both waits and both records, at the right lines.

      A first version also called hipEventQuery inside the wait wrapper. That invalidated the engine's global-mode graph capture ("operation failed due to a previous error during capture"), so that run is discarded. The version used below makes no extra HIP calls except hipStreamIsCapturing, and all five verify windows were captured without error.

    Run

    Stock 6f32ec0, the same reproduction with STRATA_VERIFY_TRACE=1, plus both tools loaded. Run 1 finished all 8 requests without a hang. Run 2 hung (RELEASE-NOT-DRAINED). Both tools dumped on the same trigger; their dump times can differ by up to about 0.2 s.

    Queue shim:

    • The queue whose head is the stuck BARRIER_AND (dependency signal value 0) had not moved its read index for 86.7 s. But its head packet first appeared only 20.3 s before the dump; before that the queue was empty. It had been idle through the whole 31K prompt read.
    • The verify stream's queue also had its head packet appear 20.3 s before the dump.
    • The completed dependency signal was last armed (hsa_signal_silent_store to 1, in a batch of adjacent pool signals) 89.0 s before the dump, at the end of the previous request's decode.

    Wrapper:

    • The last hipStreamWaitEvent outside stream capture was 20.66 s before the dump, in Prefill::run_impl (the prompt's compute stream waiting on an event the copy thread had recorded 0.26 s earlier).
    • The only hipGraphLaunch in the last 30 s was 20.20 s before the dump, from Verifier::run: the hung verify window.
    • No Strata wait was issued around the time the stuck packet appeared.

    Which branch: the first dispatch behind the stuck barrier has grid (15360,1,1), which is 6 tokens x 2560: the shared-expert branch's f32-to-bf16 conversion (STRATA_SH_STREAM, sh_cs_ in the captured graph). So the stuck queue is the one HIP runs that graph branch on.

    Reading

    When the verify graph is launched after its shared-expert branch's queue has been idle for a long time, the BARRIER_AND that HIP puts at the head of that branch depends on a signal last armed 89 s earlier, during the previous request's decode. That signal completed long ago, and the command processor does not release the barrier. The main stream then waits for the branch's result, and the window stalls. I infer, but have not shown, that this dependency is HIP's marker for the branch's previous launch.

    Strata issues no wait here that could be removed, so the barrier itself looks like a HIP/ROCm-side problem. Still not explained: why the MMQ prompt path, the adaptive swaps and SDMA are each needed for it to happen.

    One more data point: the STRATA_SH_STREAM=0 arm in the bisection table above (two runs) did hang once, but in the batched prompt read at layer 1, not in a verify window. So the shared-expert fork may be only one of the places where this happens, and STRATA_SH_STREAM=0 is not a workaround I can confirm. That is a single run, though.

    Next, I will try a standalone HIP reproduction: a captured graph with a forked branch, launched repeatedly; then the branch's queue left idle while other streams run SDMA copies; then launched again. If it reproduces, I will report it to ROCm with the program.

  3. xyzzing commented on Oct 5, 2026

    @xyzzing
    Contributor

    I'm trying to work out the HIP optimizations, please kindly share notes. My are at https://github.com/xyzzing/Strata

  4. Niko1221 commented on Oct 6, 2026

    @Niko1221
    Owner

    Great analysis, thank you. The barrier that never passes looks like a ROCm problem, so please report it to ROCm. Meanwhile 0.1.40 has an opt-in workaround: STRATA_HIP_ADAPT_KERNEL_COPY=1 does the adaptive swaps' host-to-device copies with a kernel instead of SDMA (HSA_ENABLE_SDMA=0 and --adapt-every 100000 also avoid it). We could not test it on 2x gfx1030. Please tell us if it helps.

  5. Vop3p commented on Oct 6, 2026

    @Vop3p
    ContributorAuthor

    Yes, it helps here. STRATA_HIP_ADAPT_KERNEL_COPY=1 on 0.1.40.1 (82f46a8, stock), 2x RX 6900 XT layer split, adaptive swaps on, GFXOFF at the kernel's default, the repository's benchmark.py (4,096 / 32,768 / 128,000 tokens x 3), one cold server per row, run alternately this evening:

    servers stalled where
    STRATA_HIP_ADAPT_KERNEL_COPY=1 3 0 (27 requests, nine at 128K, all answered) -
    without it 3 (one tonight, two this afternoon) 3 the first 128K request, while reading the prompt (chunks from 24576, 40960, 73728); the server restarted the engine

    The engine logs STRATA_HIP_ADAPT_KERNEL_COPY=1: adaptive swaps copy with a kernel, not SDMA (#884) in every run with it, and it costs nothing measurable: prompt 444-448 / 721-724 / 820-821 tok/s and decode 62.0 / 66.9 / 58.9 against 446 / 723 / 821 and 62.1 / 67.0 / 59.0 without it. Three against three is a small sample, but the control stalled in the same hour.

    Two more things found since my last update, both on this machine:

    • GFX power-gating (GFXOFF) is the other half. With it held off through amdgpu's debugfs knob (/sys/kernel/debug/dri/N/amdgpu_gfxoff, 4 bytes, 0), the 0.1.39 stack that hung in 7 of 8 runs of the sequence in this issue hung in 0 of 30, and 0.1.40.1's layer split ran 54 requests at 4K-128K without a stall (the 0.1.40.1 report in bench: community report, 2x RX 6900 XT (gfx1030) + Ryzen 5 5600X, IQ3_S at 131K on 0.1.40.1 - one card, layer split and expert helper; stock, the two gfx103x switches, and #1150 #1151 #1167 #1270 was measured that way for the two-card rows). It does not change speed. One trap if anyone repeats it: the knob is a reference count, not a level - a 0 written is only undone by a later 1, so a wrapper that writes 0 per engine must write exactly one 1 per 0. amdgpu already disables GFXOFF during compute on GC 11 (older MES firmware), GC 12 and GC 9 APUs (amdgpu_amdkfd_set_compute_idle, "temporary workaround ... compute applications"); gfx10.3 is not on that list, which fits what we see.
    • 0.1.40.1 itself stalls less: with the fork on and GFXOFF at its default, the sequence above (helper mode, prompts up to 32K) stalled in 7 of 8 runs on our 0.1.39 stack and 0 of 8 on 0.1.40.1, the same afternoon, alternating. The 128K layer-split stall above remains.

    Reported to ROCm as asked: rocm-systems #12669 and #12638 have our reproduction on 10.0 / 10.1 / 10.2 and the queue state at the hang; I will add the GFXOFF result there. For daily use on this machine either the kernel copy or GFXOFF off around the engine is enough; we run the GFXOFF wrapper in production today.

    Developed with an AI coding assistant; every number above was measured on 2x RX 6900 XT (gfx1030, PCIe 4.0 x8 each) / Ryzen 5 5600X, ROCm 10.0.

  6. Niko1221 commented on Oct 7, 2026

    @Niko1221
    Owner

    The queue stuck on a completed barrier signal inside hipGraphLaunch looks like a ROCm problem, so please report it to ROCm with your trace. In 0.1.40 there is an opt-in switch for the path, STRATA_HIP_ADAPT_KERNEL_COPY=1; try it with and without, and tell us. We keep this open meanwhile.

  7. Vop3p commented on Oct 9, 2026

    @Vop3p
    ContributorAuthor

    Notes are public now: https://github.com/xjc10/dual-6900xt-flash-next-tuning (an interactive timeline of every production state on 2x RX 6900 XT, README in English and Chinese, and the A/B harness).

    What helped on gfx1030, in order of size:

    What did not help here: --spec 3 --spec-min-p 0.70 from your fork (flat to −2–3% on 6900 XT), tensor parallel (+6%), a smaller --kv-resident, --pipeline-windows 2 (+1–6% but the output changes between starts), the 16-point spec/min-p grid (4/0.5 is the peak), and batch slots (resident experts 41% < the 50% gate).

    Your card is gfx1100 with WMMA, so the rocBLAS table and the SIMT scorer are gfx103x-specific; the FP16 path, SH_STREAM, kernel-copy and the encoder placement should carry over.

    Developed with an AI coding assistant; every number above was measured on 2x RX 6900 XT (gfx1030, PCIe 4.0 x8 each) / Ryzen 5 5600X, ROCm 10.0.

  8. Niko1221 commented on Oct 13, 2026

    @Niko1221
    Owner

    Thanks for the report. To keep the tracker current we are closing issues older than three days: 0.1.44 is out with a long list of fixes, and many older reports are covered by it or by 0.1.43.1.

    If this still happens on 0.1.44, please leave a comment here with your engine log (and the version line it prints), and we will reopen it. A new issue that links this one works too.

  9. Vop3p commented on Oct 13, 2026

    @Vop3p
    ContributorAuthor

    Closing is fine from my side - for the record, a re-test on 0.1.43.1 (stock tag, a0116b27) in the same conditions as the 0.1.40.1 run: 2x RX 6900 XT layer split, setup's arguments for an SSD, the adaptive tier at its defaults, GFXOFF at the kernel's default, benchmark.py 4,096 / 32,768 / 128,000 tokens x 3, one cold server per row:

    0.1.43.1 servers stalled requests answered
    without STRATA_HIP_ADAPT_KERNEL_COPY 3 0 30 of 30 (nine at 128K)
    STRATA_HIP_ADAPT_KERNEL_COPY=1 3 0 30 of 30

    On 0.1.40.1 every server without the switch stalled at the first 128K request. The switch's line is printed from adapt_copy_h2d(), so the swaps did run; speed is the same with and without it. I will re-run it on 0.1.44 and come back here only if it returns.

    One line in docs/AMD_HIP.md is out of date: "(untested on the reporter's machine)" could read "(on the reporter's 2x RX 6900 XT it prevented the hang on 0.1.40.1; on 0.1.43.1 the hang no longer reproduces there)".

    Developed with an AI coding assistant; every number above was measured on 2x RX 6900 XT (gfx1030, PCIe 4.0 x8 each) / Ryzen 5 5600X, ROCm 10.0.

  10. zspitzer commented on Oct 17, 2026

    @zspitzer

    Update, 2026-10-17: no need to run it. Ours turned out to be PAL-specific: the same bug as ROCm/rocm-systems#12571, fixed by ROCm/rocm-systems#12952. A hipEventRecord between a kernel and an SDMA copy voids the wait that orders the copy behind compute. With that patch my standalone went from 8/24 hung to 0/24, and the server's first-request hang is gone. That code is Windows-only, so #884 on ROCr/Linux is a different bug with a similar trigger. Sorry for the noise. The original comment is below for the record.


    @Vop3p, this looks like a close relative of a hang I reported to ROCm from Windows: ROCm/rocm-systems#12535. RX 7900 XT (gfx1100), so RDNA3 and the PAL backend rather than RDNA2 and ROCr, but the same shape: small pinned H2D copies go to SDMA, and the next compute submission never retires although everything before it has. 0% busy, no waves, no TDR. Keeping the copies off SDMA cures it there too.

    Since it shows up on two OSes, two driver stacks and two GPU generations, I'm wondering whether it lives below the runtime, and your GFXOFF result points the same way. Would you be up for running the standalone repro on your box? It's ~90 lines of plain PyTorch, no Strata or other app involved, and takes a few minutes:

    https://github.com/zspitzer/rocm-windows-sdma-hang-gfx1100

    export HIP_VISIBLE_DEVICES=0      # one card is enough
    python loop.py 20                 # default
    HSA_ENABLE_SDMA=0 python loop.py 20   # control
    

    loop.py starts a fresh process per run, since it's a first-use hang, and prints hung N of 20. A single run usually passes. On my box the default hangs in roughly 1 run in 3 (14 of 48), and with SDMA off in none. If it hangs with your GFXOFF wrapper on as well, a third arm would be very telling. The script prints SA PAL_DISABLE_SDMA=unset on its first line; that variable only means something on Windows, so ignore it.

    A hang on gfx1030 / Linux would mean this isn't Windows PAL at all, and I'd add it to the ROCm issue (and yours). A clean 0 of 20 is useful too. Thanks either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions