Skip to content

Eval bug: 2x MI50 tensor split aborts in RCCL on the first AllReduce once peer access works, no fallback to butterfly #7

Description

@JCraigWasTaken

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:

  1. 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.
  2. 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:

  1. Two MI50s on a kernel where cudaDeviceCanAccessPeer reports true between them.
  2. 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
  1. Send any chat completion request. The server aborts on the first AllReduce.
  2. 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.

Activity

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