Name and Version
mx-llama.cpp tag b10811, run from the Docker image mixa3607/llama.cpp-gfx906:b10811-rocm-7.14-mxxm (ROCm 7.14, RCCL 2.30.4). llama-server --version inside the image prints version: 0.3.0-dev (build 1, commit 4e6a72b).
Host: Debian 13 with the Ubuntu mainline 6.12.98 kernel. I moved to that kernel because the stock Debian 6.12.94 kernel is built without CONFIG_PCI_P2PDMA and CONFIG_HSA_AMD_P2P, so peer access between the two cards never comes up on it.
Operating systems
Linux
GGML backends
HIP
Hardware
Two AMD MI50 32 GB (gfx906) on a B650E board with a Ryzen 5 9600X. Both cards run at PCIe Gen4 x16. There is no bridge or NVLink style link, just the PCIe bus.
Models
Qwen3.8-27B Q8_0 GGUF with the MTP draft head (--spec-type draft-mtp). The problem is not model specific as far as I can tell. It happens on the first request of any size.
Problem description & steps to reproduce
I run the model split across both cards with -sm tensor. On the stock kernel, where peer access is not available, RCCL init fails, the fork logs that it is falling back to the butterfly path, and everything works at about 35 tokens per second.
On the 6.12.98 kernel, peer access works. RCCL init now succeeds. The log shows Connected all rings and Connected all trees. The first prompt-sized AllReduce then fails inside RCCL and the server aborts. Every request does this, including a one-line prompt. The server never answers.
The RCCL warning is enqueue.cc:2061 NCCL WARN HIP failure 'the operation cannot be performed in the present state'. That is hipErrorIllegalState, which HIP returns when you enqueue work while a graph capture is active on the stream. The abort lands in ggml_backend_cuda_comm_allreduce_nccl at ggml-cuda.cu:1366, which is the ncclGroupEnd() of the large-tensor BF16 branch. So the RCCL collective is being issued while the fork's HIP graph is being captured.
Two things in the code make this worse than a slow path:
ggml_backend_cuda_comm_allreduce_nccl can only return true. Every RCCL call in it is wrapped in NCCL_CHECK, which ends in GGML_ABORT. The comment on the comm context at ggml-cuda.cu:1038 says per-call failures return false and the meta backend handles them. That is not what happens for the NCCL variant. There is no way to get back to the butterfly path once RCCL has initialised.
- The size gate in front of the custom AllReduce checks the compile-time
s_have_nccl, not whether RCCL actually initialised. So with GGML_CUDA_ALLREDUCE=internal or none, the large tensors are still declined by the custom AllReduce and go to the butterfly instead of the two-shot path.
Things I tried that did not change the abort:
--shm-size=1g (this only removed a separate /dev/shm warning)
NCCL_PROTO=Simple
NCCL_P2P_DISABLE=1
--ipc=host
- running without
HSA_FORCE_FINE_GRAIN_PCIE=1
Workaround that works: GGML_CUDA_ALLREDUCE=internal. The internal AllReduce is compiled out on HIP, so in practice this means the custom peer-write AllReduce takes the decode-sized tensors and the butterfly takes the rest. With that set I get about 45 tokens per second on the P2P kernel against 35 on the no-P2P kernel, so the P2P path is worth keeping.
What I would like: either keep RCCL calls out of the graph capture (or capture around them), or let the NCCL AllReduce return false on failure so the fallback the comment describes actually happens. I am happy to run a patched build on this box and send back logs. I have a full NCCL_DEBUG=INFO trace of the crash if that helps.
Steps to reproduce:
- Two MI50s on a kernel where
cudaDeviceCanAccessPeer reports true between them.
- Run the image with the default AllReduce selection, for example:
docker run --rm --device /dev/kfd --device /dev/dri --group-add video --security-opt seccomp=unconfined \
-e GGML_ENABLE_CUSTOM_AR=1 -e HSA_FORCE_FINE_GRAIN_PCIE=1 -e HIP_VISIBLE_DEVICES=0,1 \
-v /path/to/models:/models -p 8080:8080 mixa3607/llama.cpp-gfx906:b10811-rocm-7.14-mxxm \
--server --host 0.0.0.0 --port 8080 -m /models/Qwen3.8-27B-Q8_0.gguf -c 262144 \
-ngl 99 -np 1 -lm dio -sm tensor --flash-attn on --jinja --spec-type draft-mtp --spec-draft-n-max 3
- Send any chat completion request. The server aborts on the first AllReduce.
- Add
-e GGML_CUDA_ALLREDUCE=internal and the same request works.
First Bad Commit
Unknown. b10811 is the only tag I have run on the P2P kernel. Earlier tags were only run on the stock kernel, where RCCL never initialises, so they would not have shown it.
Relevant log output
NCCL INFO Channel 00/0 : 0[3000] -> 1[9000] via P2P/direct pointer comm 0x5bf07deecf90 nRanks 02
NCCL INFO Channel 00/0 : 1[9000] -> 0[3000] via P2P/direct pointer comm 0x5bf07e9696d0 nRanks 02
NCCL INFO Connected all rings, use ring PXN 0 GDR 1
NCCL INFO Connected all trees
NCCL INFO ncclCommInitAll_impl comm 0x5bf090ca9630 rank 0 nranks 2 cudaDev 0 nvmlDev 0 busId 3000 - Init COMPLETE
NCCL INFO AllReduce: opCount 0 sendbuff 0x7b773b98d000 recvbuff 0x7b773b98d000 count 215040 datatype 9 op 0 root 0 comm 0x5bf07deecf90 [nranks=2]
NCCL INFO AllReduce: opCount 0 sendbuff 0x7b773b18d000 recvbuff 0x7b773b18d000 count 215040 datatype 9 op 0 root 0 comm 0x5bf07e9696d0 [nranks=2]
.../rccl/build/hipify/src/enqueue.cc:2061 NCCL WARN HIP failure 'the operation cannot be performed in the present state'
NCCL INFO .../rccl/build/hipify/src/group.cc:408 -> 1
NCCL INFO .../rccl/build/hipify/src/group.cc:791 -> 1
NCCL INFO .../rccl/build/hipify/src/group.cc:932 -> 1
/build/llamacpp/ggml/src/ggml-cuda/ggml-cuda.cu:109: ROCm error
E ROCm error: unhandled cuda error (run with NCCL_DEBUG=INFO for details)
E current device: 1, in function ggml_backend_cuda_comm_allreduce_nccl at /build/llamacpp/ggml/src/ggml-cuda/ggml-cuda.cu:1366
libggml-base.so.0(ggml_print_backtrace+0x21c)
libggml-base.so.0(ggml_abort+0x15b)
One more detail from the same trace. With --spec-type draft-mtp the fork creates a second llama_context for the draft, and each context runs its own ggml_backend_cuda_comm_init. In some runs one context's init walked the fallback chain and logged internal AllReduce init failed ... falling back to meta-backend butterfly while the other context held a live RCCL comm and aborted. So seeing the fallback line in the log does not mean RCCL is out of the picture.
Name and Version
mx-llama.cpp tag b10811, run from the Docker image
mixa3607/llama.cpp-gfx906:b10811-rocm-7.14-mxxm(ROCm 7.14, RCCL 2.30.4).llama-server --versioninside the image printsversion: 0.3.0-dev (build 1, commit 4e6a72b).Host: Debian 13 with the Ubuntu mainline 6.12.98 kernel. I moved to that kernel because the stock Debian 6.12.94 kernel is built without
CONFIG_PCI_P2PDMAandCONFIG_HSA_AMD_P2P, so peer access between the two cards never comes up on it.Operating systems
Linux
GGML backends
HIP
Hardware
Two AMD MI50 32 GB (gfx906) on a B650E board with a Ryzen 5 9600X. Both cards run at PCIe Gen4 x16. There is no bridge or NVLink style link, just the PCIe bus.
Models
Qwen3.8-27B Q8_0 GGUF with the MTP draft head (
--spec-type draft-mtp). The problem is not model specific as far as I can tell. It happens on the first request of any size.Problem description & steps to reproduce
I run the model split across both cards with
-sm tensor. On the stock kernel, where peer access is not available, RCCL init fails, the fork logs that it is falling back to the butterfly path, and everything works at about 35 tokens per second.On the 6.12.98 kernel, peer access works. RCCL init now succeeds. The log shows
Connected all ringsandConnected all trees. The first prompt-sized AllReduce then fails inside RCCL and the server aborts. Every request does this, including a one-line prompt. The server never answers.The RCCL warning is
enqueue.cc:2061 NCCL WARN HIP failure 'the operation cannot be performed in the present state'. That ishipErrorIllegalState, which HIP returns when you enqueue work while a graph capture is active on the stream. The abort lands inggml_backend_cuda_comm_allreduce_ncclatggml-cuda.cu:1366, which is thencclGroupEnd()of the large-tensor BF16 branch. So the RCCL collective is being issued while the fork's HIP graph is being captured.Two things in the code make this worse than a slow path:
ggml_backend_cuda_comm_allreduce_ncclcan only return true. Every RCCL call in it is wrapped inNCCL_CHECK, which ends inGGML_ABORT. The comment on the comm context atggml-cuda.cu:1038says per-call failures return false and the meta backend handles them. That is not what happens for the NCCL variant. There is no way to get back to the butterfly path once RCCL has initialised.s_have_nccl, not whether RCCL actually initialised. So withGGML_CUDA_ALLREDUCE=internalornone, the large tensors are still declined by the custom AllReduce and go to the butterfly instead of the two-shot path.Things I tried that did not change the abort:
--shm-size=1g(this only removed a separate/dev/shmwarning)NCCL_PROTO=SimpleNCCL_P2P_DISABLE=1--ipc=hostHSA_FORCE_FINE_GRAIN_PCIE=1Workaround that works:
GGML_CUDA_ALLREDUCE=internal. The internal AllReduce is compiled out on HIP, so in practice this means the custom peer-write AllReduce takes the decode-sized tensors and the butterfly takes the rest. With that set I get about 45 tokens per second on the P2P kernel against 35 on the no-P2P kernel, so the P2P path is worth keeping.What I would like: either keep RCCL calls out of the graph capture (or capture around them), or let the NCCL AllReduce return false on failure so the fallback the comment describes actually happens. I am happy to run a patched build on this box and send back logs. I have a full
NCCL_DEBUG=INFOtrace of the crash if that helps.Steps to reproduce:
cudaDeviceCanAccessPeerreports true between them.-e GGML_CUDA_ALLREDUCE=internaland the same request works.First Bad Commit
Unknown. b10811 is the only tag I have run on the P2P kernel. Earlier tags were only run on the stock kernel, where RCCL never initialises, so they would not have shown it.
Relevant log output
One more detail from the same trace. With
--spec-type draft-mtpthe fork creates a secondllama_contextfor the draft, and each context runs its ownggml_backend_cuda_comm_init. In some runs one context's init walked the fallback chain and loggedinternal AllReduce init failed ... falling back to meta-backend butterflywhile the other context held a live RCCL comm and aborted. So seeing the fallback line in the log does not mean RCCL is out of the picture.