Skip to content

RPC: add -sm tensor - #26610

Open
am17an wants to merge 2 commits into
masterfrom
rpc_tensor
Open

RPC: add -sm tensor#26610
am17an wants to merge 2 commits into
masterfrom
rpc_tensor

Conversation

@am17an

@am17an am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Overview

Add RPC -sm tensor. This is on 2x Sparks connected via RDMA.

For RPC following changes are required:

  1. async graph_compute
  2. custom all_reduce
  3. graph uid cache like CUDA
  4. set_tensor_2d, get_tensor_2d

Looking for feedback @ggerganov @rgerganov

model size params backend ngl n_ubatch sm lm test t/s
deepseek4 ?B MXFP4 MoE 145.63 GiB 284.33 B RPC -1 2048 tensor dio pp2048 619.36 ± 15.42
deepseek4 ?B MXFP4 MoE 145.63 GiB 284.33 B RPC -1 2048 tensor dio tg128 19.75 ± 0.39

Additional information

  sequenceDiagram
      participant C as Client (RPC backend)
      box Server A (rank 0)
          participant A as A: rpc port
          participant Ac as A: comm port
      end
      box Server B (rank 1)
          participant Bc as B: comm port
          participant B as B: rpc port
      end

      Note over C,B: initialization
      C->>A: COMM_INIT (rank 0)
      C->>B: COMM_INIT (rank 1)
      Note over Ac: listen on comm port
      Bc-->>Ac: connect + caps negotiation (transport upgrade, e.g. RDMA)
      A->>C: response (ok)
      B->>C: response (ok)

      Note over C,B: for each subgraph (split at reduction boundaries)
      C->>A: GRAPH_COMPUTE (uid) [GRAPH_RECOMPUTE on reuse]
      C->>B: GRAPH_COMPUTE (uid)
      Note over A: compute subgraph async
      Note over B: compute subgraph async
  
      C->>A: COMM_ALLREDUCE (partial tensor) [fire and forget, no response]
      C->>B: COMM_ALLREDUCE (partial tensor)

      Note over A: sync pending graph<br/>cast F32→BF16 if ne ≥ 32768<br/>copy partial to send buffer
      Note over B: sync pending graph<br/>cast F32→BF16 if ne ≥ 32768<br/>copy partial to send buffer

      Ac-->>Bc: partial A (rank 0 sends first)
      Bc-->>Ac: partial B (rank 1 receives first)

      Note over A: upload partial B<br/>dst = dst + partial B (async ADD)
      Note over B: upload partial A<br/>dst = dst + partial A (async ADD)

      Note over C,B: read back the output
      C->>A: GET_TENSOR (output)
      Note over A: sync all backends
      A->>C: data
Loading

Requirements


Stack created with GitHub Stacks CLIGive Feedback 💬

@github-actions github-actions Bot added testing Everything test related ggml changes relating to the ggml tensor library for machine learning labels Aug 5, 2026
@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

confirmed working on metal(RDMA 2 x M3 Ultra with #26421), testing same model (ds4 MXFP4):
tg2048: 9.61 t/s
pp2048: 166.05 t/s

it does however break with dspark applied because some ops it depends on appear to not be supported with TP (add across the split), so this is without any mtp.

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@ryan5rdx yeah I know it doesn't work with dspark, but what do you get when for just -sm layer as tg2048? It seems kinda low based on the numbers you posted in the other PR

@ggerganov

Copy link
Copy Markdown
Member

This was supposed on top of the #25860 but that didn't happen.

Shouldn't you stack it on top of #26490, instead of #25860?

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Sorry it is on top of the that
image

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx yeah I know it doesn't work with dspark, but what do you get when for just -sm layer as tg2048? It seems kinda low based on the numbers you posted in the other PR

command for reference, let me know happy to test an alternate config:

./bin/llama-server -m ~/Downloads/DeepSeek-V4-Flash-0731-MXFP4.gguf -c 1048576 --reasoning on --rpc 192.168.0.13:50052   -np 1 --reasoning-preserve -ub 2048 -b 4096  --no-mmap -ngl 999 -fa on  --fit off -ts 1,1 --host 0.0.0.0 -kvu -sm tensor

and yup just confirmed - with -sm layer numbers align with what I have in #26421:
tg2048: 23.05 t/s
pp2048: 270.3 t/s

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@ryan5rdx try using two rpc servers, one on each machine and connect via

llama-server --rpc <ip_1, ip_2> --device RPC0, RPC1

@Kononnable

Copy link
Copy Markdown
Contributor

It might be worth to add -sm layer results to bench results table - just to have performance baseline in a single place.

I don't know if this would be observable with RDNA, but sometimes manually moving tensors instead of standard -sm layer can increase performance when tcp/ip is used (just a sidenote for 'base' performance).
-sm layer -ts 0,1 -ot 'blk\.[0-1][0-9]?\.ffn_(up|down|gate|gate_up)_(ch|)exps=RPC0[127.0.0.1:50052]'

@ggerganov

Copy link
Copy Markdown
Member

@am17an The shared commits in the 2 branches differ:

Likely you've made changes to the dsv4-sm-tensor branch after you created the rpc_tensor branch. That's why the PR stack does not work.

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Yeah I messed up, I think #26490 should be okay to merge though

@ggerganov

Copy link
Copy Markdown
Member

Yeah I messed up, I think #26490 should be okay to merge though

Don't we want to fix the DSpark support first?

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

The support is broken over RPC I think(i.e. this PR), not in general. But I can check

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx try using two rpc servers, one on each machine and connect via

llama-server --rpc <ip_1, ip_2> --device RPC0, RPC1

Sorry - to clarify - is this RPC servers on two RDMA linked nodes, and then a llama-server instance on one of them(if so I suppose this will just connect to the localhost RPC server)? Sorry I've never run a llama-cli/server instance where it's not also doing compute ha

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@ryan5rdx yes, the llama-server is just a client of these two. Basically we're trying to activate the RPC<>RPC all-reduce path rather than the one you probably got (Metal<>RPC)

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

It might be worth to add -sm layer results to bench results table - just to have performance baseline in a single place.

Currently on master -sm layer is crashing for some reason. /home/aman/experiments/llama.cpp/ggml/src/ggml-rpc/ggml-rpc.cpp:519: Remote RPC server crashed or returned malformed response, but last time I checked it was around ~400 PP and ~15 TG

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx yes, the llama-server is just a client of these two. Basically we're trying to activate the RPC<>RPC all-reduce path rather than the one you probably got (Metal<>RPC)

-sm tensor hangs after tensors are sharded to RPC nodes with any topology >1 RPC backend.

Tested with Qwen327B, 0.6B and ds4. -sm layer works on both qwen models with 2 RPC backends, ds4 breaks even on layer, but seems unrelated.

no obvious error on rpc servers or llama-server, I see the tensors copy on the RPC nodes then nothing.

final client logs, llama server web UI never comes up(hangs here):

0.00.236.455 I print_info: EOT token             = 151645 '<|im_end|>'
0.00.236.455 I print_info: PAD token             = 151654 '<|vision_pad|>'
0.00.236.455 I print_info: LF token              = 198 'Ċ'
0.00.236.456 I print_info: FIM PRE token         = 151659 '<|fim_prefix|>'
0.00.236.456 I print_info: FIM SUF token         = 151661 '<|fim_suffix|>'
0.00.236.456 I print_info: FIM MID token         = 151660 '<|fim_middle|>'
0.00.236.456 I print_info: FIM PAD token         = 151662 '<|fim_pad|>'
0.00.236.456 I print_info: FIM REP token         = 151663 '<|repo_name|>'
0.00.236.457 I print_info: FIM SEP token         = 151664 '<|file_sep|>'
0.00.236.457 I print_info: EOG token             = 128247 '</s>'
0.00.236.457 I print_info: EOG token             = 151643 '<|endoftext|>'
0.00.236.458 I print_info: EOG token             = 151645 '<|im_end|>'
0.00.236.458 I print_info: EOG token             = 151662 '<|fim_pad|>'
0.00.236.458 I print_info: EOG token             = 151663 '<|repo_name|>'
0.00.236.458 I print_info: EOG token             = 151664 '<|file_sep|>'
0.00.236.459 I print_info: max token length      = 256
0.00.236.459 I load_tensors: loading model tensors, this can take a while... (load_mode = none)
0.00.262.459 I load_tensors: offloading output layer to GPU
0.00.262.460 I load_tensors: offloading 27 repeating layers to GPU
0.00.262.461 I load_tensors: offloaded 29/29 layers to GPU
0.00.262.462 I load_tensors:          CPU model buffer size =   121.71 MiB
0.00.262.462 I load_tensors:       Meta() model buffer size =   251.44 MiB
0.00.734.830 I cmn  common_init_: added </s> logit bias = -inf
0.00.734.991 I cmn  common_init_: added <|endoftext|> logit bias = -inf
0.00.734.993 I cmn  common_init_: added <|im_end|> logit bias = -inf
0.00.734.993 I cmn  common_init_: added <|fim_pad|> logit bias = -inf
0.00.734.994 I cmn  common_init_: added <|repo_name|> logit bias = -inf
0.00.734.994 I cmn  common_init_: added <|file_sep|> logit bias = -inf
0.00.735.025 I llama_context: constructing llama_context
0.00.735.026 I llama_context: n_seq_max     = 1
0.00.735.027 I llama_context: n_ctx         = 20224
0.00.735.027 I llama_context: n_ctx_seq     = 20224
0.00.735.027 I llama_context: n_batch       = 4096
0.00.735.027 I llama_context: n_ubatch      = 2048
0.00.735.028 I llama_context: causal_attn   = 1
0.00.735.028 I llama_context: flash_attn    = enabled
0.00.735.028 I llama_context: kv_unified    = true
0.00.735.029 I llama_context: freq_base     = 1000000.0
0.00.735.029 I llama_context: freq_scale    = 1
0.00.735.030 I llama_context: n_rs_seq      = 0
0.00.735.030 I llama_context: n_outputs_max = 1
0.00.735.030 I llama_context: n_ctx_seq (20224) < n_ctx_train (40960) -- the full capacity of the model will not be utilized

command:

  ./bin/llama-server -m ~/Downloads/Qwen3-0.6B-UD-Q4_K_XL.gguf -c 20000 --reasoning on --rpc 192.168.0.13:50052,192.168.0.9:50052 --device RPC0,RPC1   -np 1 --reasoning-preserve -ub 2048 -b 4096  --no-mmap -ngl 999 -fa on  --fit off -ts 1,1 --host 0.0.0.0 -kvu -sm tensor -lv 4

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx yes, the llama-server is just a client of these two. Basically we're trying to activate the RPC<>RPC all-reduce path rather than the one you probably got (Metal<>RPC)

for the all-reduce to work - don't RPC nodes need direct(RDMA) connections to each other in addition to (at least)TCP to the client?

with an A - B(just client) - C topology where A<>B and B<>C are RDMA links, but there is no A <> C link, can this work? (in MLX they achieve this with a mesh + rdma, but here RPC servers don't know about peers yet)

@rgerganov rgerganov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please create a mermaid sequence diagram (similar to this one ) which describe how peers communicate when -sm tensor is being used, I am still trying to understand the new flows being added and that would be very helpful. In fact, I think this should be part of our dev documentation (feel free to create an .md file) so we can maintain this in the long term.

Comment thread ggml/src/ggml-rpc/ggml-rpc.cpp
Comment thread ggml/src/ggml-rpc/ggml-rpc.cpp Outdated
return true;
}

// minor protocol version of each connected server, used to gate newer commands (comm collectives)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

no need to do this, we don't care about backward compatibility and we prefer to keep the code simple; just bump the version to 6.0.0 and expect all peers to be running this version

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@ryan5rdx I run this using the directly the RDMA interfaces, for example in my case it is --rpc 10.10.20.1, 10.10.20.2 and you should see the RDMA being negotiated between the two peers. There is no mesh required if they can talk to each other

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx I run this using the directly the RDMA interfaces, for example in my case it is --rpc 10.10.20.1, 10.10.20.2 and you should see the RDMA being negotiated between the two peers. There is no mesh required if they can talk to each other

in my case the RPC nodes cannot talk to each other via the --rpc passed IPs on the client (the node at 10.10.20.1 cannot reach the other rpc node via 10.10.20.2, it can however reach it via a different ip, if I were to connect them together via a TB cable, or I suppose via a completely different ip over Ethernet via tcp).

Possible I'm misunderstanding here - or maybe a mac difference because it only supports p2p (TB)rdma without routing vs the spark?

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@ryan5rdx not sure, you can probably ask an LLM to debug?

@ryan5rdx

ryan5rdx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@ryan5rdx not sure, you can probably ask an LLM to debug?

Ok yup confirmed - so if I use GGML_RPC_NO_COMM=1 disable to worker <> worker comm, it works(even with RDMA, since it relays traffic over the client)

if I instead I use the LAN IPs - it still works becuase each worker can discover the other using the ip from the client, just super slow as expected (>>10s/token).

However - if we want this to work on metal, with worker<>worker RDMA we need a mesh and to allow for passing the TB IP of other workers to each rpc server(because peer addresses are not derivable from the addr we get from the client in a TB p2p network).

I'm not familiar with sparks but apparently it works because "all three nodes sit on one routable RDMA fabric"

@am17an

am17an commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@rgerganov I added a mermaid diagram. Note that the Meta backend creates a lot of subgraphs so that's why graph caching is vital.

@rgerganov rgerganov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you can improve the diagram by making a clear separation between messages sent to the standard RPC port and messages sent to the new "comm" port

return nullptr;
}
ggml_backend_rpc_context * rpc_ctx = (ggml_backend_rpc_context *) backends[i]->context;
// one rank per endpoint: a server processes its socket sequentially, so a second

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"one rank per endpoint" -- why having this limitation? i can have an endpoint with two devices which communicate very fast (because they are on the same physical host)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In that case you can just create two rpc servers, one for each device?

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.

On local MoE models with -cmoe weights are copied to GPU during PP for faster compute. Utilizing separate processes per device would break this optimization.
The example is using local, but same optimization should be possible on rpc with multiple backends.

Utilizing same process can also be useful in general for AllReduce - 2 servers 2 GPU each could merge local results lowering the amount of network hops needed.

Comment thread ggml/src/ggml-rpc/ggml-rpc.cpp
Comment thread ggml/src/ggml-rpc/ggml-rpc.cpp
Comment thread ggml/src/ggml-rpc/ggml-rpc.cpp
}

static void * ggml_backend_rpc_comm_init(ggml_backend_t * backends, size_t n_backends) {
if (n_backends != 2 || std::getenv("GGML_RPC_NO_COMM") != nullptr) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this going to work with more than 2 backends or it will require major redesign?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it's going to fall-back to meta backend's all-reduce, which is slow but works

@Geramy

Geramy commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@am17an have you looked at implementing https://github.com/mk1-project/quickreduce/ QuickReduce seems pretty cool and it could probably be of huge benefit for RDMA performance.

@github-actions github-actions Bot added model Model specific CUDA Related to the CUDA backend labels Aug 14, 2026
@gopinath87607

gopinath87607 commented Aug 18, 2026

Copy link
Copy Markdown

is this completedi am looking to test it lol please merge the sm tensore support for rpc

ohh its seems like the sm tensore flag via rpc is only for spark not for normal gpus like my case rtx 3060 and 3080 via rpc seems like i have to stick with the sm layer flag

@am17an
am17an marked this pull request as ready for review August 19, 2026 19:38
@am17an
am17an requested review from a team, CISC, JohannesGaessler and ggerganov as code owners August 19, 2026 19:38
@Kononnable

Kononnable commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

I don't understand the need for rpc_comm. It it is only there to introduce custom allreduce which skips the client and supports only 2 RPC devices.
The only scenario that comes to my mind that would have this condition is when client is just an entry point, not doing any compute (otherwise it would be a part of AllReduce). In such case we could simply drop one RPC server and run client on that machine directly. Having custom AllReduce does not seem necessary for -sm tensor to work.

If that is the case introducing it in two separate PRs would make it much easier to comprehend and understand the reasoning why using separate RPC server instead of running computation on the client directly can be beneficial.

@am17an

am17an commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Yes, custom all-reduce is required for a reasonable speed. In fact, it is the most important operation in any tensor parallel implementation to be performant and not that it just works over multiple devices. You can use layer parallelism for that

Later I plan to add NCCL support for the custom all-reduce which allows llama.cpp to reach near vLLM speeds over 2x sparks.

@gopinath87607

Copy link
Copy Markdown

Yes, custom all-reduce is required for a reasonable speed. In fact, it is the most important operation in any tensor parallel implementation to be performant and not that it just works over multiple devices. You can use layer parallelism for that

Later I plan to add NCCL support for the custom all-reduce which allows llama.cpp to reach near vLLM speeds over 2x sparks.

hi is this possiple for general gpus ?

@Kononnable

Copy link
Copy Markdown
Contributor

I agree about all-reduce being important.
I don't get the part if custom all-reduce with direct connection is required in general, or is it really a workaround for client not doing any work while being part of all-reduce operation. If my assumption of topology is correct (client doing nothing + 2 RPC nodes doing the work) the reason would be using 3 nodes in situation when only two nodes are needed for all-reduce. If we use only two nodes (client + RPC, both doing the work) the custom channel would not be necessary.

For all-reduce on 3+ nodes direct connection between nodes (custom channel) will be important, but I'm not sure 3 nodes (client + 2 RPC) are needed in this case.
(That is assuming all-reduce is faster than reduce+broadcast for our use case [tensors + rpc] - communication latency vs reduce operation cost ratio - but this is a completely different matter).

@am17an

am17an commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

The only reason it is done this way is because of how the code is designed. The Meta backend can identify two RPC devices and enable the custom all-reduce. You can run the client on the same node as the server (that's what I'm doing)

@Kononnable

Copy link
Copy Markdown
Contributor

Any particular reason to run it this way and force RPC<->RPC all-reduce, or it is just how you've got it set up/it's easier for debugging?
I think more code in this PR targets RPC<->RPC all-reduce than -sm tensor with more common approach (client doing the work).

I've run some test locally, spotted two issues:

  • When both RPC servers can talk to the client, but not to each other (e.g. one RPC server defined as 127.0.0.1) they freeze at the end of model loading. Depending on the failure type (timeout, connection rejection) error log may appear on one RPC and RPC or the client will have one thread running at 100%. It should be handled by a graceful error. Probably the same issue as @ryan5rdx reported in earlier comments.
  • On my hardware setup running with 2 RPC nodes and forcing RPC<->RPC all-reduce is slower, than running client + RPC directly.
llama-bench results
# removed RDMA probed/activated logs for clarity
➜  llama.cpp git:(pr-26610) build/bin/llama-bench  --rpc 192.168.4.1:50052,192.168.4.2:50052  --list-devices 
ggml_cuda_init: found 1 ROCm devices (Total VRAM: 8176 MiB):
  Device 0: AMD Radeon RX 6600, gfx1032 (0x1032), VMM: no, Wave Size: 32, VRAM: 8176 MiB
Available devices:
  ROCm0: AMD Radeon RX 6600 (8176 MiB, 7654 MiB free)
  RPC0: 192.168.4.1:50052 (8176 MiB, 7656 MiB free)
  RPC1: 192.168.4.2:50052 (63661 MiB, 63661 MiB free)
➜  llama.cpp git:(pr-26610) CUDA_VISIBLE_DEVICES=-1  build/bin/llama-bench  -m /models/qwen3.6-35B-A3B/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf  -lm dio  --rpc 192.168.4.1:50052,192.168.4.2:50052  -sm tensor  -ts 1/2       
ggml_cuda_init: failed to initialize ROCm: no ROCm-capable device is detected
| model                          |       size |     params | backend    | ngl |     sm | ts           |         lm |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -----: | ------------ | ---------: | --------------: | -------------------: |
| qwen35moe 35B.A3B Q4_K - Medium |  21.27 GiB |    35.51 B | ROCm,RPC   |  -1 | tensor | 1.00/2.00    |        dio |           pp512 |         67.35 ± 1.53 |
| qwen35moe 35B.A3B Q4_K - Medium |  21.27 GiB |    35.51 B | ROCm,RPC   |  -1 | tensor | 1.00/2.00    |        dio |           tg128 |          5.47 ± 0.06 |

build: 3a10a7709 (10435)
➜  llama.cpp git:(pr-26610) build/bin/llama-bench  -m /models/qwen3.6-35B-A3B/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf  -lm dio  --rpc 192.168.4.2:50052  -sm tensor  -ts 1/2 
ggml_cuda_init: found 1 ROCm devices (Total VRAM: 8176 MiB):
  Device 0: AMD Radeon RX 6600, gfx1032 (0x1032), VMM: no, Wave Size: 32, VRAM: 8176 MiB
| model                          |       size |     params | backend    | ngl |     sm | ts           |         lm |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -----: | ------------ | ---------: | --------------: | -------------------: |
| qwen35moe 35B.A3B Q4_K - Medium |  21.27 GiB |    35.51 B | ROCm,RPC   |  -1 | tensor | 1.00/2.00    |        dio |           pp512 |         66.74 ± 1.07 |
| qwen35moe 35B.A3B Q4_K - Medium |  21.27 GiB |    35.51 B | ROCm,RPC   |  -1 | tensor | 1.00/2.00    |        dio |           tg128 |          8.18 ± 0.18 |

build: 3a10a7709 (10435)
➜  llama.cpp git:(pr-26610) 
During 2 RPC run on the machine with the client I can sometimes observe kworker eating all of the cores for a moment. During Client + RPC run such behavior is not observed.

@am17an

am17an commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@Kononnable direct server to server comm is the point of this PR. If you have a client in the middle the all-reduce goes through host memory which will be slower on RDMA capable connections.

w.r.t your benchmark it is not equivalent to the other one, one of the ranks is local so only half the partials travel over the network. In any case you can continue using that if that's faster. What is the speed of your RDMA connection?

EDIT: I also test the client<->RPC on my 2x sparks and it is about 60% slower. Secondly, the accept bug is real if two peers cannot reach each other.

@Kononnable

Copy link
Copy Markdown
Contributor

As for my benchmark - I tired to get the difference between client all-reduce [1 client + 1 rpc] vs new rpc all-reduce [1 client (not doing any work) + 2 rpc]. In both cases only two nodes are doing the work and are needed in all-reduce.

My RDMA setup is very far from being optimal - 2x old ConnectX-3 cards(10GbE) connected through the chipsets (limiting some of the RDMA benefits), but latency seems to be fine (~1.63 usec).

Anyway, if on a proper hardware setup (2xSpark) the new approach is faster then it's all good. I still don't understand why would the difference be so big, but it has probably something to do with my hardware setup or size of the data in all-reduce operation.

ib_write_lat, ib_write_bw
➜  llama.cpp git:(pr-26610) ib_write_lat 192.168.4.2                                                                                                                                                           
Disabling dynamic polling
---------------------------------------------------------------------------------------
                    RDMA_Write Latency Test
 Dual-port       : OFF		Device         : rocep4s0
 Number of qps   : 1		Transport type : IB
 Connection type : RC		Using SRQ      : OFF
 PCIe relax order: OFF		Lock-free      : OFF
 ibv_wr* API     : OFF		Using Enhanced Reorder      : OFF
 TX depth        : 1
 Mtu             : 2048[B]
 Link type       : Ethernet
 GID index       : 2
 Max inline data : 220[B]
 rdma_cm QPs	 : OFF
 Data ex. method : Ethernet
---------------------------------------------------------------------------------------
Failed to query device capabilities, ret=95
 local address: LID 0000 QPN 0x0789 PSN 0xa96b6a RKey 0x9801014b VAddr 0x00557044b6d000
 GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:04:01
 remote address: LID 0000 QPN 0x04b3 PSN 0xfbde6c RKey 0x98010127 VAddr 0x0055d88c326000
 GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:04:02
---------------------------------------------------------------------------------------
 #bytes #iterations    t_min[usec]    t_max[usec]  t_typical[usec]    t_avg[usec]    t_stdev[usec]   99% percentile[usec]   99.9% percentile[usec] 
 2       1000          1.59           2.84         1.62     	       1.63        	0.00   		1.76    		2.84   
---------------------------------------------------------------------------------------
➜  llama.cpp git:(pr-26610) ib_write_bw 192.168.4.2                                                                                                                                                            
---------------------------------------------------------------------------------------
                    RDMA_Write BW Test
 Dual-port       : OFF		Device         : rocep4s0
 Number of qps   : 1		Transport type : IB
 Connection type : RC		Using SRQ      : OFF
 PCIe relax order: ON		Lock-free      : OFF
 ibv_wr* API     : OFF		Using Enhanced Reorder      : OFF
 TX depth        : 128
 CQ Moderation   : 1
 CQE Poll Batch  : Dynamic
 Mtu             : 2048[B]
 Link type       : Ethernet
 GID index       : 2
 Max inline data : 0[B]
 rdma_cm QPs	 : OFF
 Data ex. method : Ethernet
---------------------------------------------------------------------------------------
 local address: LID 0000 QPN 0x078a PSN 0xd6b873 RKey 0xa001014b VAddr 0x007f65192ad000
 GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:04:01
 remote address: LID 0000 QPN 0x04b4 PSN 0xeba634 RKey 0xa0010127 VAddr 0x007f2a69f19000
 GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:04:02
---------------------------------------------------------------------------------------
 #bytes     #iterations    BW peak[MiB/sec]    BW average[MiB/sec]   MsgRate[Mpps]
 65536      5000             1138.40            1138.40		     0.018214
---------------------------------------------------------------------------------------

@ggerganov

Copy link
Copy Markdown
Member

@am17an Please rebase on latest master.

@ggerganov ggerganov self-assigned this Aug 28, 2026
@am17an
am17an changed the base branch from dsv4-sm-tensor to master August 28, 2026 08:40
@am17an
am17an requested a review from a team as a code owner August 30, 2026 08:27
@am17an

am17an commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

it's rebased on latest master. The async events seem to have added a lot of latency in sync due to worker threads wake up. When using -sm tensor we're currently busy waiting on the worker thread. This will utilize the CPU at 100%, we can come with a better solution if required

HelloKS added a commit to HelloKS/llama.cpp that referenced this pull request Sep 2, 2026
@am17an

am17an commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Here are my MTP bench results on 2x sparks after #28387, #28390

  code_python        pred= 192 draft= 177 acc= 145 rate=0.819 tok/s=41.5
  code_cpp           pred= 192 draft= 218 acc= 136 rate=0.624 tok/s=36.1
  explain_concept    pred= 192 draft= 265 acc= 124 rate=0.468 tok/s=27.4
  summarize          pred=  39 draft=  48 acc=  27 rate=0.562 tok/s=27.9
  qa_factual         pred= 192 draft= 232 acc= 132 rate=0.569 tok/s=31.6
  translation        pred=  20 draft=  20 acc=  15 rate=0.750 tok/s=33.2
  creative_short     pred=  45 draft= 108 acc=  18 rate=0.167 tok/s=15.9
  stepwise_math      pred= 192 draft= 212 acc= 136 rate=0.641 tok/s=31.5
  long_code_review   pred= 192 draft= 291 acc= 117 rate=0.402 tok/s=24.3

Aggregate: {
  "n_requests": 9,
  "total_predicted": 1256,
  "total_draft": 1571,
  "total_draft_accepted": 850,
  "aggregate_accept_rate": 0.5411,
  "wall_s_total": 45.17
}

This is without using NCCL and any extra dsv4 specific optimizations (which in my tests bring the wall time < 40s)

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

Labels

AMD ZenDNN Issues related to the AMD ZenDNN backend Apple Metal https://en.wikipedia.org/wiki/Metal_(API) Ascend NPU issues specific to Ascend NPUs build Compilation issues conversion CUDA Related to the CUDA backend devops improvements to build systems and github actions documentation Improvements or additions to documentation examples ggml changes relating to the ggml tensor library for machine learning Hexagon IBM zDNN issues specific to IBM zDNN Accelerator model Model specific mtmd Related to multimodal functionality (video/image/audio) OpenCL Issues specific to the OpenCL backend OpenVINO server/ui server SYCL https://en.wikipedia.org/wiki/SYCL - GPU programming language testing Everything test related vendor Vulkan Issues specific to the Vulkan backend WebGPU

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants