Skip to content

Add nccl support for tensor parrallel - #28967

Draft
zyang-dev wants to merge 2 commits into
ggml-org:rpc_tensorfrom
zyang-dev:rpc_nccl
Draft

zyang-dev wants to merge 2 commits into
ggml-org:rpc_tensorfrom
zyang-dev:rpc_nccl

Conversation

@zyang-dev

@zyang-dev zyang-dev commented Sep 15, 2026

Copy link
Copy Markdown

Overview

Add a backend interface for collectives across ranks running in separate processes, implement it in the CUDA backend using NCCL, and use it in the RPC server when available.

The implementation is on top of PR #26610.

Results on DGX spark:
single node:

./build/bin/llama-bench -hf unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M -ngl 99 -d 0,8192 -r 3
| model                          |       size |     params | backend    | ngl |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | --------------: | -------------------: |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA       |  99 |           pp512 |       846.54 ± 18.78 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA       |  99 |           tg128 |         13.02 ± 0.01 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA       |  99 |   pp512 @ d8192 |       801.23 ± 10.67 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA       |  99 |   tg128 @ d8192 |         12.64 ± 0.01 |

Two nodes with NCCL:

./build/bin/llama-bench -hf unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M -ngl 99 -sm tensor  --rpc NODE-1-IP:50052,NODE-2-IP:50052 -dev RPC0/RPC1 -d 0,8192 -r 3
| model                          |       size |     params | backend    | ngl |     sm | dev          |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -----: | ------------ | --------------: | -------------------: |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |           pp512 |      1348.29 ± 38.62 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |           tg128 |         23.51 ± 0.19 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |   pp512 @ d8192 |      1282.35 ± 33.41 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |   tg128 @ d8192 |         22.86 ± 0.10 |

Two nodes with pairwise:

./build/bin/llama-bench -hf unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M -ngl 99 -sm tensor  --rpc NODE-1-IP:50052,NODE-2-IP:50052 -dev RPC0/RPC1 -d 0,8192 -r 3
| model                          |       size |     params | backend    | ngl |     sm | dev          |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -----: | ------------ | --------------: | -------------------: |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |           pp512 |       961.61 ± 17.51 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |           tg128 |         22.33 ± 0.17 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |   pp512 @ d8192 |       911.15 ± 53.39 |
| qwen35 27B Q4_K - Medium       |  15.32 GiB |    27.32 B | CUDA,RPC   |  99 | tensor | RPC0/RPC1    |   tg128 @ d8192 |         22.14 ± 0.18 |

Ratio to a single node:

┌───────────────┬──────────────────────┬───────┐
│               │ pairwise (PR #26610) │ NCCL  │
├───────────────┼──────────────────────┼───────┤
│ pp512         │                1.14x │ 1.59x │
├───────────────┼──────────────────────┼───────┤
│ tg128         │                1.72x │ 1.81x │
├───────────────┼──────────────────────┼───────┤
│ pp512 @ d8192 │                1.14x │ 1.60x │
├───────────────┼──────────────────────┼───────┤
│ tg128 @ d8192 │                1.75x │ 1.81x │
└───────────────┴──────────────────────┴───────┘

Requirements

…ate processes, implement it in the CUDA backend using NCCL, and use it in the RPC server when available.
@zyang-dev
zyang-dev requested review from a team and ggerganov as code owners September 15, 2026 20:00
@zyang-dev
zyang-dev marked this pull request as draft September 15, 2026 20:00
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend labels Sep 15, 2026
@am17an

am17an commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Do you have any llama-bench results?

Comment thread ggml/include/ggml-backend.h Outdated
Comment on lines +212 to +220
// Same as above, but each rank is a separate process, so a process owns one backend of the
// communicator rather than all of them.
// One rank produces the unique id. Every other rank needs it, out of band, before calling
// comm_init_rank, which blocks until all ranks have joined. Free with ggml_backend_comm_free_t.
#define GGML_BACKEND_COMM_UNIQUE_ID_SIZE 128
typedef bool (*ggml_backend_comm_get_unique_id_t)(void * id_out);
typedef void * (*ggml_backend_comm_init_rank_t)(ggml_backend_t backend, const void * id, int rank, int world_size);
typedef bool (*ggml_backend_comm_allreduce_rank_t)(void * comm_ctx, struct ggml_tensor * tensor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without having looked at any of the other code, changes to the ggml backend API like this should be avoided if at all possible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added llama-bench results.

Removed ggml_backend_comm_allreduce_rank_t and now reuse ggml_backend_comm_allreduce_tensor for per-rank communicators.

@am17an , @JohannesGaessler, do you have any suggestions for handling ggml_backend_comm_get_unique_id_t and ggml_backend_comm_init_rank_t?

@am17an

am17an commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

what's the reason why using NCCL is so much faster for PP vs TG? Maybe there is something to optimize in the generic path too

@zyang-dev

Copy link
Copy Markdown
Author

what's the reason why using NCCL is so much faster for PP vs TG? Maybe there is something to optimize in the generic path too

@am17an,
Decode communicates far fewer tensor elements per collective than prefill. This explains why NCCL and pairwise have similar decode performance, while NCCL is significantly faster during prefill.

I tested three optimizations on the pairwise path:

  1. Duplex exchange (ggml-rpc.cpp). Previously, rank 0 sent its entire payload before receiving, leaving one direction of the full-duplex link idle during each transfer phase. Changed comm_allreduce_pairwise to exchange chunks, with both ranks sending a chunk and then receiving, allowing traffic in both directions concurrently.

  2. Deeper send queue (transport.cpp). Previously, rdma_send posted one 256 KiB work request and waited for completion before posting the next. Changed it to allow up to four outstanding sends, reducing gaps between transfers. Increasing the depth further to eight did not improve performance.

  3. Direct registered buffers. Previously, the data path was GPU → application host buffer → RDMA host buffer → network, with the reverse copies on receive. Replaced the intermediate buffers with persistent, pinned host buffers registered directly with RDMA: GPU → registered host buffer → network. This eliminated the extra host-to-host copies while retaining GPU ↔ host transfers.

Further improvement likely requires restructuring the collective into an overlapping pipeline of GPU transfers, network communication, and reduction.

┌─────────────────────────────┬─────────────────┬────────────────┐
│                             │      pp512      │ vs single node │
├─────────────────────────────┼─────────────────┼────────────────┤
│ pairwise (PR #26610)        │  961.61 ± 17.51 │          1.14x │
├─────────────────────────────┼─────────────────┼────────────────┤
│ + duplex exchange           │ 1057.67 ± 12.13 │          1.25x │
├─────────────────────────────┼─────────────────┼────────────────┤
│ + deep send queue           │  1144.75 ± 9.24 │          1.35x │
├─────────────────────────────┼─────────────────┼────────────────┤
│ + direct registered buffers │ 1213.09 ± 36.06 │          1.43x │
├─────────────────────────────┼─────────────────┼────────────────┤
│ NCCL                        │ 1348.29 ± 38.62 │          1.59x │
└─────────────────────────────┴─────────────────┴────────────────┘

@am17an

am17an commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Ah okay nice these can be even added to the original PR I think. @ggerganov perhaps it may help in the metal speedup. Do you have a branch with these changes?

@mamama1

mamama1 commented Sep 18, 2026

Copy link
Copy Markdown

errr I'm very confused, I've recently acquired a third 5060 Ti and started using tensor parallelism with NCCL enabled. Enabling NCCL doubled decode - I think prefill also increased significantly.

I admit that I don't fully understand (or barely understand) the mechanics underneath - that's why the following question came to my mind when reading this PR:

why does the PR say "Add nccl support for tensor parrallel" if that's something that apparently is already working?

thanks

@am17an

am17an commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

This PR is for NCCL support over RDMA, not related to your setup. @zyang-dev perhaps change the title to reflect it better

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

Labels

CUDA Related to the CUDA backend ggml changes relating to the ggml tensor library for machine learning

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants