Skip to content

vulkan: add tensor-parallel allreduce function - #25051

Open
pwilkin wants to merge 21 commits into
ggml-org:masterfrom
pwilkin:vulkan-tp-p2p
Open

pwilkin wants to merge 21 commits into
ggml-org:masterfrom
pwilkin:vulkan-tp-p2p

Conversation

@pwilkin

@pwilkin pwilkin commented Jun 26, 2026 •

Copy link
Copy Markdown
Member

Overview

Adds basic Vulkan tensor parallel support:

  • dedicated allreduce kernel
  • synchronization using spin-wait with max duration (NVidia) or SFD semaphores (AMD)

Only optimized for 2 devices so far, ring-buffer support and/or other optimizations will follow in a subsequent PRs.

Requirements

@pwilkin
pwilkin requested review from a team and JohannesGaessler as code owners June 26, 2026 12:37
@pwilkin
pwilkin requested review from 0cc4m and jeffbolznv June 26, 2026 12:38
@github-actions github-actions Bot added Vulkan Issues specific to the Vulkan backend ggml changes relating to the ggml tensor library for machine learning labels Jun 26, 2026
@pwilkin
pwilkin marked this pull request as draft June 26, 2026 14:18
@pwilkin

pwilkin commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Did a few more tests with an A16 and a 4090:

4090 + A16

-sm layer

3.15.215.713 I slot print_timing: id  3 | task 0 | prompt eval time =   35434.16 ms / 28872 tokens (    1.23 ms per token,   814.81 tokens per second)
3.15.215.720 I slot print_timing: id  3 | task 0 |        eval time =  140477.32 ms /  1481 tokens (   94.85 ms per token,    10.54 tokens per second)

-sm tensor

3.25.475.691 I slot print_timing: id  3 | task 0 | prompt eval time =   60539.25 ms / 28872 tokens (    2.10 ms per token,   476.91 tokens per second)
3.25.475.696 I slot print_timing: id  3 | task 0 |        eval time =  126102.68 ms /  1504 tokens (   83.84 ms per token,    11.93 tokens per second)

2xA16

-sm layer

4.14.866.003 I slot print_timing: id  3 | task 0 | prompt eval time =   52667.78 ms / 28872 tokens (    1.82 ms per token,   548.19 tokens per second)
4.14.866.009 I slot print_timing: id  3 | task 0 |        eval time =  179656.12 ms /  1233 tokens (  145.71 ms per token,     6.86 tokens per second)

-sm tensor

3.49.116.708 I slot print_timing: id  3 | task 0 | prompt eval time =   57678.27 ms / 28872 tokens (    2.00 ms per token,   500.57 tokens per second)
3.49.116.712 I slot print_timing: id  3 | task 0 |        eval time =  155333.79 ms /  1800 tokens (   86.30 ms per token,    11.59 tokens per second)

So as you can see, the 4090 is held down by the A16 and the boost from tensor parallel there is really small, but on 2xA16, the TG boost is almost double while the PP loss is negligible (almost 90% of the original).

@characharm

Copy link
Copy Markdown
Contributor

AMD Radeon RX 9070 XT & AMD Radeon AI PRO R9700

model size params backend ngl sm fa test t/s
qwen35 27B Q5_K - Medium 18.94 GiB 27.32 B Vulkan -1 tensor 1 pp512 @ d60000 219.29 ± 1.65
qwen35 27B Q5_K - Medium 18.94 GiB 27.32 B Vulkan -1 tensor 1 tg128 @ d60000 10.68 ± 0.16
model size params backend ngl fa test t/s
qwen35 27B Q5_K - Medium 18.94 GiB 27.32 B Vulkan -1 1 pp512 @ d60000 493.22 ± 2.01
qwen35 27B Q5_K - Medium 18.94 GiB 27.32 B Vulkan -1 1 tg128 @ d60000 21.79 ± 0.09

@wizardeur

Copy link
Copy Markdown

AMD RX7900XTX (2, 4, 8 GPUs)

ggml_vulkan: Found 2 Vulkan devices:
ggml_vulkan: 0 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 1 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 pp512 809.00 ± 2.70
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 tg128 34.50 ± 0.07
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 pp512 1283.40 ± 7.10
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 tg128 41.57 ± 0.37

ggml_vulkan: Found 4 Vulkan devices:
ggml_vulkan: 0 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 1 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 2 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 3 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 pp512 762.29 ± 5.09
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 tg128 29.66 ± 0.03
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 pp512 305.86 ± 1.00
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 tg128 14.68 ± 0.90

ggml_vulkan: Found 8 Vulkan devices:
ggml_vulkan: 0 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 1 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 2 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 3 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 4 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 5 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 6 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 7 = Radeon RX 7900 XTX (RADV NAVI31) (radv) | uma: 0 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 pp512 697.64 ± 3.23
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 layer 1 tg128 11.89 ± 0.31
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 pp512 95.61 ± 0.10
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B Vulkan -1 tensor 1 tg128 3.87 ± 1.57

And ROCm for comparison:

ggml_cuda_init: found 2 ROCm devices (Total VRAM: 49120 MiB):
Device 0: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 1: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 pp512 913.54 ± 26.89
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 tg128 26.34 ± 0.03
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 pp512 1535.47 ± 2.33
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 tg128 45.35 ± 0.27

ggml_cuda_init: found 4 ROCm devices (Total VRAM: 98240 MiB):
Device 0: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 1: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 2: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 3: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 pp512 877.60 ± 31.85
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 tg128 23.58 ± 0.02
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 pp512 2085.89 ± 13.42
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 tg128 55.79 ± 0.41

ggml_cuda_init: found 8 ROCm devices (Total VRAM: 196480 MiB):
Device 0: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 1: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 2: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 3: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 4: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 5: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 6: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB
Device 7: AMD Radeon RX 7900 XTX, gfx1100 (0x1100), VMM: no, Wave Size: 32, VRAM: 24560 MiB

model size params backend ngl sm fa test t/s
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 pp512 811.63 ± 55.16
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 layer 1 tg128 21.48 ± 0.03
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 pp512 2163.08 ± 23.29
qwen35 27B Q4_K - Medium 15.48 GiB 26.90 B ROCm -1 tensor 1 tg128 37.54 ± 2.63

@netrunnereve netrunnereve Jun 27, 2026 •

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.

Your way of dealing with the crash seems to be similar to the one proposed here (also AI generated, lol) that basically uses the view src to get around the multi buffer issue. I explain this a bit more in #22197 but I think this only works if the tensor is a view tensor. If it's not a view tensor then we don't have any way of knowing which buffer in the multi buffer the tensor is stored in.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yup, valid point. Didn't surface in the small models I've tested, but probably will in any bigger one. Paging @0cc4m here: do you have any qualms about just adding a tensor to sub-buffer map in multi_buffer?

@digitalscream

Copy link
Copy Markdown

OK, bit of oddness on my R9700s compared with the 7900XTX above - prefill takes a significant hit compared with -sm row:

ggml_vulkan: 0 = AMD Radeon Graphics (RADV GFX1201) (radv) | uma: 0 | fp16: dot2 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
ggml_vulkan: 1 = AMD Radeon Graphics (RADV GFX1201) (radv) | uma: 0 | fp16: dot2 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 0 | matrix cores: KHR_coopmat
| model                          |       size |     params | backend    | ngl | n_ubatch |     sm |  fa |            test |                  t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | -----: | --: | --------------: | -------------------: |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 |    row |   1 |          pp2048 |       1580.58 ± 5.18 |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 |    row |   1 |          pp4096 |       1556.35 ± 2.32 |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 |    row |   1 |           tg128 |         29.91 ± 0.03 |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 | tensor |   1 |          pp2048 |       1125.71 ± 2.82 |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 | tensor |   1 |          pp4096 |       1110.65 ± 1.31 |
| qwen35 27B Q4_K - Medium       |  15.92 GiB |    27.32 B | Vulkan     |  99 |     1024 | tensor |   1 |           tg128 |         36.84 ± 0.38 |

Also, even more weirdness - when run as a server, the first request goes through at 36t/s, but the second falls off to 2t/s:

0.21.379.838 I srv  proxy_reques: proxying request to model nondraft_tensor_Qwen3.6-27B-Q4_0.gguf on port 47447
[47447] 0.08.608.257 I srv    operator(): Chat format: peg-native
[47447] 0.08.608.379 I slot get_availabl: id  3 | task -1 | selected slot by LRU, t_last = -1
[47447] 0.08.608.381 I srv  get_availabl: updating prompt cache
[47447] 0.08.608.385 I srv          load:  - looking for better prompt, base f_keep = -1.000, sim = 0.000
[47447] 0.08.608.388 I srv        update:  - cache state: 0 prompts, 0.000 MiB (limits: 16384.000 MiB, 256000 tokens, 17179869184 est)
[47447] 0.08.608.389 I srv  get_availabl: prompt cache update took 0.01 ms
[47447] 0.08.608.423 I slot launch_slot_: id  3 | task 0 | processing task, is_child = 0
[47447] 0.08.608.425 I slot process_sing: id  0 | task -1 | saving idle slot to prompt cache
[47447] 0.08.608.426 I slot prompt_clear: id  0 | task -1 | clearing prompt with 0 tokens
[47447] 0.08.608.578 I slot process_sing: id  1 | task -1 | saving idle slot to prompt cache
[47447] 0.08.608.579 I slot prompt_clear: id  1 | task -1 | clearing prompt with 0 tokens
[47447] 0.08.608.723 I slot process_sing: id  2 | task -1 | saving idle slot to prompt cache
[47447] 0.08.608.725 I slot prompt_clear: id  2 | task -1 | clearing prompt with 0 tokens
[47447] 0.08.609.083 I srv  stream_sessi: stream_session_attach_pipe: conv_id= (empty=1)
[47447] 0.08.842.162 I slot create_check: id  3 | task 0 | created context checkpoint 1 of 32 (pos_min = 14, pos_max = 14, n_tokens = 15, size = 149.626 MiB)
[47447] 0.11.928.914 I slot print_timing: id  3 | task 0 | n_decoded =    108, tg =  35.85 t/s, tg_3s =  35.85 t/s
[47447] 0.14.931.024 I slot print_timing: id  3 | task 0 | n_decoded =    217, tg =  36.08 t/s, tg_3s =  36.31 t/s
[47447] 0.17.940.243 I slot print_timing: id  3 | task 0 | n_decoded =    325, tg =  36.02 t/s, tg_3s =  35.89 t/s
[47447] 0.20.946.915 I slot print_timing: id  3 | task 0 | n_decoded =    434, tg =  36.08 t/s, tg_3s =  36.25 t/s
[47447] 0.23.948.536 I slot print_timing: id  3 | task 0 | n_decoded =    542, tg =  36.06 t/s, tg_3s =  35.98 t/s
[47447] 0.26.957.662 I slot print_timing: id  3 | task 0 | n_decoded =    652, tg =  36.14 t/s, tg_3s =  36.56 t/s
[47447] 0.29.983.478 I slot print_timing: id  3 | task 0 | n_decoded =    761, tg =  36.12 t/s, tg_3s =  36.02 t/s
[47447] 0.33.010.837 I slot print_timing: id  3 | task 0 | n_decoded =    870, tg =  36.11 t/s, tg_3s =  36.00 t/s
[47447] 0.36.017.438 I slot print_timing: id  3 | task 0 | n_decoded =    979, tg =  36.12 t/s, tg_3s =  36.25 t/s
[47447] 0.39.044.366 I slot print_timing: id  3 | task 0 | n_decoded =   1087, tg =  36.08 t/s, tg_3s =  35.68 t/s
[47447] 0.40.202.434 I slot print_timing: id  3 | task 0 | prompt eval time =     307.57 ms /    19 tokens (   16.19 ms per token,    61.78 tokens per second)
[47447] 0.40.202.437 I slot print_timing: id  3 | task 0 |        eval time =   31285.98 ms /  1129 tokens (   27.71 ms per token,    36.09 tokens per second)
[47447] 0.40.202.438 I slot print_timing: id  3 | task 0 |       total time =   31593.55 ms /  1148 tokens
[47447] 0.40.202.441 I slot print_timing: id  3 | task 0 |    graphs reused =       1124
[47447] 0.40.202.474 I slot      release: id  3 | task 0 | stop processing: n_tokens = 1147, truncated = 0
[47447] 0.40.202.481 I srv  update_slots: all slots are idle
0.53.000.387 I srv  proxy_reques: proxying request to model nondraft_tensor_Qwen3.6-27B-Q4_0.gguf on port 47447
[47447] 0.40.231.788 I srv    operator(): Chat format: peg-native
[47447] 0.40.231.898 I slot get_availabl: id  2 | task -1 | selected slot by LRU, t_last = -1
[47447] 0.40.231.900 I srv  get_availabl: updating prompt cache
[47447] 0.40.231.901 I srv          load:  - looking for better prompt, base f_keep = -1.000, sim = 0.000
[47447] 0.40.231.903 I srv        update:  - cache state: 0 prompts, 0.000 MiB (limits: 16384.000 MiB, 256000 tokens, 17179869184 est)
[47447] 0.40.231.905 I srv  get_availabl: prompt cache update took 0.01 ms
[47447] 0.40.231.944 I slot launch_slot_: id  2 | task 1131 | processing task, is_child = 0
[47447] 0.40.231.947 I slot process_sing: id  0 | task -1 | saving idle slot to prompt cache
[47447] 0.40.231.947 I slot prompt_clear: id  0 | task -1 | clearing prompt with 0 tokens
[47447] 0.40.232.096 I slot process_sing: id  1 | task -1 | saving idle slot to prompt cache
[47447] 0.40.232.098 I slot prompt_clear: id  1 | task -1 | clearing prompt with 0 tokens
[47447] 0.40.232.250 I slot process_sing: id  3 | task -1 | saving idle slot to prompt cache
[47447] 0.40.232.564 W srv   prompt_save:  - saving prompt with length 1147, total state size = 187.732 MiB (draft: 0.000 MiB)
[47447] 0.40.465.224 I srv        update:  - cache state: 1 prompts, 337.358 MiB (limits: 16384.000 MiB, 256000 tokens, 256000 est)
[47447] 0.40.465.227 I srv        update:    - prompt 0x614a78ac7710:    1147 tokens, checkpoints:  1,   337.358 MiB
[47447] 0.40.465.227 I slot prompt_clear: id  3 | task -1 | clearing prompt with 1147 tokens
[47447] 0.41.257.685 I slot create_check: id  2 | task 1131 | created context checkpoint 1 of 32 (pos_min = 423, pos_max = 423, n_tokens = 424, size = 149.626 MiB)
0.56.797.744 I srv  proxy_reques: proxying request to model nondraft_tensor_Qwen3.6-27B-Q4_0.gguf on port 47447
[47447] 0.44.039.953 I srv    operator(): Chat format: peg-native
[47447] 0.44.387.907 I slot get_availabl: id  1 | task -1 | selected slot by LRU, t_last = -1
[47447] 0.44.387.909 I srv  get_availabl: updating prompt cache
[47447] 0.44.387.911 I srv          load:  - looking for better prompt, base f_keep = -1.000, sim = 0.000
[47447] 0.44.387.914 I srv        update:  - cache state: 1 prompts, 337.358 MiB (limits: 16384.000 MiB, 256000 tokens, 256000 est)
[47447] 0.44.387.914 I srv        update:    - prompt 0x614a78ac7710:    1147 tokens, checkpoints:  1,   337.358 MiB
[47447] 0.44.387.915 I srv  get_availabl: prompt cache update took 0.01 ms
[47447] 0.44.387.955 I slot launch_slot_: id  1 | task 1139 | processing task, is_child = 0
[47447] 0.44.387.957 I slot process_sing: id  0 | task -1 | saving idle slot to prompt cache
[47447] 0.44.387.957 I slot prompt_clear: id  0 | task -1 | clearing prompt with 0 tokens
[47447] 0.44.388.080 I slot process_sing: id  3 | task -1 | saving idle slot to prompt cache
[47447] 0.44.388.081 I slot prompt_clear: id  3 | task -1 | clearing prompt with 0 tokens
[47447] 0.44.388.213 I srv  stream_sessi: stream_session_attach_pipe: conv_id= (empty=1)
[47447] 0.45.918.819 I slot create_check: id  1 | task 1139 | created context checkpoint 1 of 32 (pos_min = 132, pos_max = 132, n_tokens = 133, size = 149.626 MiB)
[47447] 0.48.003.905 I slot create_check: id  1 | task 1139 | created context checkpoint 2 of 32 (pos_min = 1144, pos_max = 1144, n_tokens = 1145, size = 149.626 MiB)
[47447] 0.48.888.392 I slot print_timing: id  1 | task 1139 | prompt processing, n_tokens =   1157, progress = 1.00, t =   4.50 s / 257.10 tokens per second
[47447] 0.51.100.466 I slot print_timing: id  2 | task 1131 | prompt eval time =    2231.06 ms /  1452 tokens (    1.54 ms per token,   650.81 tokens per second)
[47447] 0.51.100.469 I slot print_timing: id  2 | task 1131 |        eval time =    8403.82 ms /    12 tokens (  700.32 ms per token,     1.43 tokens per second)
[47447] 0.51.100.470 I slot print_timing: id  2 | task 1131 |       total time =   10634.87 ms /  1464 tokens
[47447] 0.51.100.470 I slot print_timing: id  2 | task 1131 |    graphs reused =       1129
[47447] 0.51.100.552 I slot      release: id  2 | task 1131 | stop processing: n_tokens = 1463, truncated = 0
[47447] 0.51.100.639 I srv  stream_sessi: stream_session_attach_pipe: conv_id= (empty=1)
[47447] 1.30.922.239 I slot print_timing: id  1 | task 1139 | n_decoded =    100, tg =   2.43 t/s, tg_3s =   2.43 t/s
[47447] 1.34.268.373 I slot print_timing: id  1 | task 1139 | n_decoded =    108, tg =   2.43 t/s, tg_3s =   2.39 t/s
[47447] 1.37.595.106 I slot print_timing: id  1 | task 1139 | n_decoded =    116, tg =   2.43 t/s, tg_3s =   2.40 t/s
[47447] 1.40.910.950 I slot print_timing: id  1 | task 1139 | n_decoded =    124, tg =   2.43 t/s, tg_3s =   2.41 t/s
[47447] 1.44.222.279 I slot print_timing: id  1 | task 1139 | n_decoded =    132, tg =   2.43 t/s, tg_3s =   2.42 t/s
1.59.519.602 E srv    operator(): http client error: Connection handling canceled
[47447] 1.47.152.943 W srv          stop: cancel task, id_task = 1139
[47447] 1.47.568.409 I slot print_timing: id  1 | task 1139 | n_decoded =    140, tg =   2.42 t/s, tg_3s =   2.39 t/s
[47447] 1.47.568.417 I slot      release: id  1 | task 1139 | stop processing: n_tokens = 1300, truncated = 0
[47447] 1.47.568.422 I srv  update_slots: all slots are idle

@cattivik66

Copy link
Copy Markdown
GPU 2× W7800 48 GB, separate PCIe 4.0 x16 root complexes (no P2P, no XGMI)
OS CachyOS, kernel 7.0.12, Mesa 26.1.2, RADV driver
Vulkan 1.4.348, KHR_coopmat detected and active
Model Qwen3.5-122B-A10B, UD-Q4_K_XL, 73 GiB
llama.cpp 9d5d882d8 + this PR

Synthetic benchmark (pp4096 / tg128, ~4K context)

Without this PR:

Mode pp4096 tg128
row 1229 48.4
layer 1195 48.4
tensor 1164 29.3

With this PR applied:

Mode pp4096 tg128
row 1230 48.5
layer 1193 48.4
tensor 1450 (+25%) 43.6 (+49%)

At short context the PR delivers -sm tensor pp +25 %, tg +49 %. It becomes the fastest pp mode (1450 vs row's 1230) and tg is only 10 % behind row.

Real-world big prompt benchmark (88K prompt, 164K context)

I've asked the LLM to provide me a resume of a book which content has been sent in the prompt

Without this PR:

Mode pp (t/s) tg (t/s) wall (s)
row 669 37 172
layer 689 37 162
tensor segfault — —

With this PR applied:

Mode pp (t/s) tg (t/s) wall (s)
row 691 37 164
layer 693 37 162
tensor 677 9 255

The tensor-mode segfault (fixed by this PR)

Without this PR, -sm tensor segfaults at any realistic context (-c 167936 and -c 204800 both crash). The log shows:

llama_params_fit is not implemented for SPLIT_MODE_TENSOR, abort

followed by SIGSEGV during model load. The single-buffer synthetic test at -p 4096 worked because the KV cache was small enough. The PR's multi-buffer KV-cache fix is required for -sm tensor to function at usable c>

The tensor decode regression at long context

With the PR applied, tensor-mode tg drops from 43.6 (short ctx) to 9 (long ctx) — 4.8× worse. Row/layer stay at 37. This appears to be the -sm tensor AllReduce decode path with high fixed per-call overhead at batch=1, mult>

For reference, -sm layer (1 sync point at the pipeline boundary) and -sm row (concatenation-only syncs) don't have this problem.

@marksverdhei

This comment was marked as off-topic.

@maxious

maxious commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

2x Intel Arc Pro B60 (Battlemage, BMG G21)

ggml_vulkan: Found 2 Vulkan devices:
ggml_vulkan: 0 = Intel(R) Arc(tm) Pro B60 Graphics (BMG G21) (Intel open-source Mesa driver) | uma: 0 | fp16: 1 | bf16: 1 | warp size: 32 | shared memory: 49152 | int dot: 1 | matrix cores: KHR_coopmat
ggml_vulkan: 1 = Intel(R) Arc(tm) Pro B60 Graphics (BMG G21) (Intel open-source Mesa driver) | uma: 0 | fp16: 1 | bf16: 1 | warp size: 32 | shared memory: 49152 | int dot: 1 | matrix cores: KHR_coopmat

llama-bench -fa 1 -ngl -1 -p 512 -n 128 -r 3, Vulkan only. Build at PR head a448deb85. Both B60s share the same Mesa ANV driver (Mesa 26.1.3, driverUUID match), so the native cross-device timeline-semaphore import path is used (not the CPU-proxy fallback).

model size params backend ngl sm fa test t/s
llama 1B Q8_0 1.22 GiB 1.24 B Vulkan -1 layer 1 pp512 6100.11 ± 18.65
llama 1B Q8_0 1.22 GiB 1.24 B Vulkan -1 layer 1 tg128 187.42 ± 8.38
llama 1B Q8_0 1.22 GiB 1.24 B Vulkan -1 tensor 1 pp512 8626.08 ± 108.51
llama 1B Q8_0 1.22 GiB 1.24 B Vulkan -1 tensor 1 tg128 115.82 ± 7.53
llama 8B Q4_0 4.33 GiB 8.03 B Vulkan -1 layer 1 pp512 1375.81 ± 124.48
llama 8B Q4_0 4.33 GiB 8.03 B Vulkan -1 layer 1 tg128 50.19 ± 11.48
llama 8B Q4_0 4.33 GiB 8.03 B Vulkan -1 tensor 1 pp512 2158.55 ± 2.04
llama 8B Q4_0 4.33 GiB 8.03 B Vulkan -1 tensor 1 tg128 41.42 ± 10.05
qwen35 9B Q8_0 9.10 GiB 9.20 B Vulkan -1 layer 1 pp512 1090.84 ± 1.86
qwen35 9B Q8_0 9.10 GiB 9.20 B Vulkan -1 layer 1 tg128 34.85 ± 2.10
qwen35 9B Q8_0 9.10 GiB 9.20 B Vulkan -1 tensor 1 pp512 1609.03 ± 30.27
qwen35 9B Q8_0 9.10 GiB 9.20 B Vulkan -1 tensor 1 tg128 38.28 ± 1.08
gpt-oss 20B Q8_0 11.27 GiB 20.91 B Vulkan -1 layer 1 pp512 1305.84 ± 37.30
gpt-oss 20B Q8_0 11.27 GiB 20.91 B Vulkan -1 layer 1 tg128 43.37 ± 11.86
gpt-oss 20B Q8_0 11.27 GiB 20.91 B Vulkan -1 tensor 1 pp512 2013.57 ± 48.94
gpt-oss 20B Q8_0 11.27 GiB 20.91 B Vulkan -1 tensor 1 tg128 53.73 ± 5.34
qwen35moe 35B.A3B Q3_K - Medium 12.79 GiB 35.51 B Vulkan -1 layer 1 pp512 771.10 ± 12.43
qwen35moe 35B.A3B Q3_K - Medium 12.79 GiB 35.51 B Vulkan -1 layer 1 tg128 44.54 ± 7.42
qwen35moe 35B.A3B Q3_K - Medium 12.79 GiB 35.51 B Vulkan -1 tensor 1 pp512 1111.37 ± 10.68
qwen35moe 35B.A3B Q3_K - Medium 12.79 GiB 35.51 B Vulkan -1 tensor 1 tg128 22.77 ± 7.64
qwen35 27B Q8_0 33.31 GiB 27.32 B Vulkan -1 layer 1 pp512 321.05 ± 11.59
qwen35 27B Q8_0 33.31 GiB 27.32 B Vulkan -1 layer 1 tg128 9.32 ± 0.28
qwen35 27B Q8_0 33.31 GiB 27.32 B Vulkan -1 tensor 1 pp512 517.89 ± 0.90
qwen35 27B Q8_0 33.31 GiB 27.32 B Vulkan -1 tensor 1 tg128 13.89 ± 0.03

Observations

  • Prefill (pp512): -sm tensor is ~1.4-1.6x faster than -sm layer across every model, including the dense 27B Q8_0 that exceeds a single B60's 24 GiB. The pipelined chunked AllReduce does its job on prefill activations, consistent with the NVIDIA results in the PR description.
  • Decode (tg128): Matches the PR author's characterisation - for models that comfortably fit one B60 (1B, 8B, 9B, dense MoE at Q3), tensor mode is break-even or slightly slower (per-call overhead on small decode tensors dominates the single-shot path). For gpt-oss-20B and the dense 27B Q8_0, the only two that genuinely need TP to fit on 2x24 GiB, tensor mode wins on TG too: gpt-oss 43 -> 54 t/s, and 27B Q8_0 9.3 -> 13.9 t/s - exactly the "balanced GPU pair, TG boost" regime the PR description calls out.
  • The 27B Q8_0 result is in the same ballpark as the mixed AMD pair reported above (10.68 tg with tensor on 27B Q5_K), so Intel/Vulkan lands competitively in that workload class.

@pwilkin

pwilkin commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@marksverdhei it's a prototype, don't worry :) when it's been decently tested and cleaned up I'll deslopify it :)

@0cc4m 0cc4m changed the title vulkan: make TP viable vulkan: add allreduce function and fix Tensor Parallel crash Jun 27, 2026
@pwilkin pwilkin changed the title vulkan: add allreduce function and fix Tensor Parallel crash vulkan: add allreduce function with cross-device CPU proxy and fix Tensor Parallel crash Jun 27, 2026
@pwilkin pwilkin changed the title vulkan: add allreduce function with cross-device CPU proxy and fix Tensor Parallel crash vulkan: add allreduce function with cross-device CPU proxy and fix Tensor Parallel crash [EXPERIMENTAL] Jun 27, 2026
@pwilkin

pwilkin commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

So the damn clanker decided to only implement the proper allreduce for 2 GPUs, because why bother ;) sorry for all the people with 4+ who posted their results, could you please retest with the new commit? (I've tested up to 8 GPUs now for correctness)

@AbdullahMPrograms

AbdullahMPrograms commented Jun 27, 2026 •

Copy link
Copy Markdown

Sharing some benchmark results with 2/4/5x Radeon PRO W7900's:
##Vulkan Stock:
./LLM/llama.cpp/vulkan/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm layer

model size params backend ngl fa test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 1 pp512 786.58 ± 2.38
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 1 tg128 33.94 ± 0.03

##Vulkan PR TP (2 GPU):
./LLM/llama.cpp-vulkantp/vulkan/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev Vulkan0/Vulkan1

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1 pp512 836.11 ± 92.66
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1 tg128 29.77 ± 0.41

##Vulkan PR TP (4 GPU):
./LLM/llama.cpp-vulkantp/vulkan/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev Vulkan0/Vulkan1/Vulkan2/Vulkan3

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1/Vulkan2/Vulkan3 pp512 413.89 ± 56.70
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1/Vulkan2/Vulkan3 tg128 27.29 ± 0.62

##Vulkan PR TP (5 GPU):
./LLM/llama.cpp-vulkantp/vulkan/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev Vulkan0/Vulkan1/Vulkan2/Vulkan3/Vulkan4

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1/Vulkan2/Vulkan3/Vulkan4 pp512 341.89 ± 55.39
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B Vulkan -1 tensor 1 Vulkan0/Vulkan1/Vulkan2/Vulkan3/Vulkan4 tg128 23.23 ± 0.62

#For comparison with ROCm:
##ROCm Stock:
./LLM/llama.cpp/rocm/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm layer

model size params backend ngl fa test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 1 pp512 843.56 ± 3.07
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 1 tg128 28.52 ± 0.04

build: 050ee92 (9821)
##ROCm TP RCCL (2 GPU):
./LLM/llama.cpp/rocm-rccl/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev ROCm0/ROCm1

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1 pp512 1429.22 ± 10.58
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1 tg128 37.50 ± 0.47

##ROCm TP RCCL (4 GPU):
./LLM/llama.cpp/rocm-rccl/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev ROCm0/ROCm1/ROCm2/ROCm3

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1/ROCm2/ROCm3 pp512 1992.92 ± 6.26
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1/ROCm2/ROCm3 tg128 45.54 ± 1.95

##ROCm TP RCCL (5 GPU):
./LLM/llama.cpp/rocm-rccl/bin/llama-bench -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -ngl -1 -fa 1 -sm tensor -dev ROCm0/ROCm1/ROCm2/ROCm3/ROCm4

model size params backend ngl sm fa dev test t/s
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1/ROCm2/ROCm3/ROCm4 pp512 2033.37 ± 13.56
qwen35 27B Q4_K - Medium 17.62 GiB 27.32 B ROCm -1 tensor 1 ROCm0/ROCm1/ROCm2/ROCm3/ROCm4 tg128 48.57 ± 2.89

@digitalscream

Copy link
Copy Markdown

Could someone else check multi-turn conversations? As noted in my comment above, I'm finding that the first prompt works as expected, but any follow up tanks down to 2t/s.

@pwilkin

pwilkin commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

@AbdullahMPrograms is this after the multiGPU fix already?

@netrunnereve I risked a proper fix to the real buffer mapping issue.

@AbdullahMPrograms

Copy link
Copy Markdown

@AbdullahMPrograms is this after the multiGPU fix already?

@netrunnereve I risked a proper fix to the real buffer mapping issue.

@pwilkin This is with commit: 362fdd2

git log --oneline -n 5
362fdd2b7 (HEAD -> pr-25051) vulkan: generalize -sm tensor AllReduce to >2 devices
a448deb85 fix Windows build
68c289bd7 vulkan: portable CPU-proxy fallback for cross-driver -sm tensor AllReduce
5dc559cdd vulkan: GPU-pipelined multi-GPU AllReduce for -sm tensor
325cceab0 ggml-backend-meta: fix SPLIT_MODE_TENSOR segfault on multi_buffer KV-cache views

@AbdullahMPrograms

Copy link
Copy Markdown

Could someone else check multi-turn conversations? As noted in my comment above, I'm finding that the first prompt works as expected, but any follow up tanks down to 2t/s.

I can reproduce this behavior as well, first prompt:

0.58.303.871 I slot print_timing: id  3 | task 0 | prompt eval time =     606.59 ms /    17 tokens (   35.68 ms per token,    28.03 tokens per second)
0.58.303.874 I slot print_timing: id  3 | task 0 |        eval time =   34812.30 ms /  1061 tokens (   32.81 ms per token,    30.48 tokens per second)
0.58.303.896 I slot print_timing: id  3 | task 0 |       total time =   35418.89 ms /  1078 tokens

follow up prompt:

2.09.854.324 I slot print_timing: id  2 | task 1063 | prompt eval time =    4341.21 ms /  1090 tokens (    3.98 ms per token,   251.08 tokens per second)
2.09.854.328 I slot print_timing: id  2 | task 1063 |        eval time =   63484.68 ms /   214 tokens (  296.66 ms per token,     3.37 tokens per second)
2.09.854.329 I slot print_timing: id  2 | task 1063 |       total time =   67825.90 ms /  1304 tokens

@digitalscream

digitalscream commented Jun 27, 2026 •

Copy link
Copy Markdown

Could someone else check multi-turn conversations? As noted in my comment above, I'm finding that the first prompt works as expected, but any follow up tanks down to 2t/s.

I can reproduce this behavior as well, first prompt:

0.58.303.871 I slot print_timing: id  3 | task 0 | prompt eval time =     606.59 ms /    17 tokens (   35.68 ms per token,    28.03 tokens per second)
0.58.303.874 I slot print_timing: id  3 | task 0 |        eval time =   34812.30 ms /  1061 tokens (   32.81 ms per token,    30.48 tokens per second)
0.58.303.896 I slot print_timing: id  3 | task 0 |       total time =   35418.89 ms /  1078 tokens

follow up prompt:

2.09.854.324 I slot print_timing: id  2 | task 1063 | prompt eval time =    4341.21 ms /  1090 tokens (    3.98 ms per token,   251.08 tokens per second)
2.09.854.328 I slot print_timing: id  2 | task 1063 |        eval time =   63484.68 ms /   214 tokens (  296.66 ms per token,     3.37 tokens per second)
2.09.854.329 I slot print_timing: id  2 | task 1063 |       total time =   67825.90 ms /  1304 tokens

Thank you! At least I know I'm not holding it wrong ;)

(EDIT: ...or we both are)

@AbdullahMPrograms

Copy link
Copy Markdown

I've also encountered some gibberish in llama-server with TP on 5 GPU's:
image
2-4 GPU's does not have gibberish, launch command:

./LLM/llama.cpp-vulkantp/vulkan/bin/llama-server -m /home/ultimis/LLM/Models/bartowski/Qwen_Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-Q4_K_L.gguf -c 32768 -ngl 999 -fa on -fit off -sm tensor --host 0.0.0.0 --port 7001 -dev Vulkan0,Vulkan1,Vulkan2,Vulkan3,Vulkan4

above on commit e578ca2

@wizardeur

Copy link
Copy Markdown

It's faster now on TP=4 and TP=8 (RX7900XTX). Shorter table this time:

Backend GPUs sm pp512 Run 1 pp512 Run 2 tg128 Run 1 tg128 Run 2
Vulkan 2 layer 809.00 794.62 34.50 33.40
Vulkan 2 tensor 1283.40 1286.12 41.57 41.68
Vulkan 4 layer 762.29 757.42 29.66 29.58
Vulkan 4 tensor 305.86 981.02 14.68 37.50
Vulkan 8 layer 697.64 701.80 11.89 12.34
Vulkan 8 tensor 95.61 492.38 3.87 21.10
ROCm 2 layer 913.54 915.04 26.34 26.13
ROCm 2 tensor 1535.47 1532.96 45.35 45.42
ROCm 4 layer 877.60 879.33 23.58 23.63
ROCm 4 tensor 2085.89 2070.25 55.79 55.40
ROCm 8 layer 811.63 807.94 21.48 21.55
ROCm 8 tensor 2163.08 2165.96 37.54 36.99

And for a multiturn conversation, I don't see any significant degradation.
1st message:

2.14.960.380 I slot print_timing: id  3 | task 0 | prompt eval time =    4516.41 ms /  4618 tokens (    0.98 ms per token,  1022.49 tokens per s
econd)
2.14.960.383 I slot print_timing: id  3 | task 0 |        eval time =   76061.21 ms /  1390 tokens (   54.72 ms per token,    18.27 tokens per s
econd)

2nd message:

8.48.047.849 I slot print_timing: id  3 | task 1395 | prompt eval time =    2080.61 ms /  1926 tokens (    1.08 ms per token,   925.69 tokens per second)
8.48.047.852 I slot print_timing: id  3 | task 1395 |        eval time =  125606.23 ms /  2336 tokens (   53.77 ms per token,    18.60 tokens per second)

3rd message:

23.52.991.932 I slot print_timing: id  3 | task 3735 | prompt eval time =    3239.29 ms /  3366 tokens (    0.96 ms per token,  1039.12 tokens per second)
23.52.991.936 I slot print_timing: id  3 | task 3735 |        eval time =   57636.44 ms /  1098 tokens (   52.49 ms per token,    19.05 tokens per second)

pwilkin and others added 13 commits October 23, 2026 13:52
Two hazards in the tensor-parallel ring all-reduce made -sm tensor output
non-deterministic with three GPUs:

- With n = 3, step t's host-slot region starts at t * cels * 2 bytes, which
  is not a multiple of 32, so consecutive steps' transfer copies share a
  32-byte sector of the imported host memory. On NVIDIA the later copy's
  leading bytes (up to 10 F16 values at the start of every chunk) end up
  stale in host memory, even when the copies are ordered by a semaphore.
  Give every step its own 256-byte-aligned region. n = 2 and n = 4 already
  had aligned regions for the usual sizes, so their layout is unchanged.

- A single-shot all-reduce leaves every device's host slot being read by all
  peers, but the next ring round only ordered its first transfer after nextd,
  and only from round 2 on. With n >= 3 a device could overwrite its slot
  while prevd was still summing it. Wait for all peers on the first ring
  round after a single-shot all-reduce.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With -sm tensor the meta backend drives every device from one thread and calls
graph_compute once per subgraph (two per layer), so decode was bound by the CPU
cost of submitting, not by the GPUs: ~1500 vkQueueSubmit per token for a 64-layer
model on two GPUs, with the GPUs idle ~40% of the time.

- Don't split subgraphs by flops while a communicator is active. The heuristic
  was tuned for whole graphs and turned each small subgraph into ~5 submits.
- Fold the small (single-shot) all-reduce into the compute submissions: the
  last command buffer of a subgraph stays open, the all-reduce appends the
  gather copy to it and submits it, then opens the next command buffer with the
  peer waits and the reduce, which the next subgraph is recorded into. Each
  subgraph now costs one submit per device. The reserved progress value is
  attached by whichever path submits the open context.
- Alternate between two host slots, placed outside the ring's regions, so the
  gather no longer needs a WAR wait on the peers' previous reduce.
- On synchronize, submit the deferred work of every device in the group first,
  so the tail of the graph runs on all devices in parallel.
- Wait for the transfer queue as well as the compute queue before freeing or
  reallocating communication buffers, and don't leave a stale cap behind when a
  reallocation fails.

qwen3.5 27B Q8_0 on RTX 4090s, tg128: 2 GPUs 32.2 -> 49.7 t/s, 3 GPUs 20.9 ->
40.0, 4 GPUs 15.0 -> 27.6; pp512 on 2 GPUs 2138 -> 2320. Greedy output is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al, wait peers + add from their slots

- One exported timeline per device, no backend hooks; comm work recorded in the backend compute ctx
- F16 wire above 1 MiB, F32 below; small reductions ride with the next graph submit
- Double-buffered, grow-only host slots; zero-size splits zeroed
- Remove ring, transfer-queue leg, device staging, shrink, Windows shims
- comm_init returns nullptr when unsupported; add GGML_VK_COMM_DISABLE
- No flops-based submit splitting for graphs below 1 GFLOP

[rebased onto the ring all-reduce fix and the folded all-reduce, which this design replaces; their backend hooks are removed here]
std::aligned_alloc is unavailable on Android bionic below API 28, which
upstream switched away from in ggml-org#30828; do the same for the communicator's
host slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@0cc4m

0cc4m commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Looks good on my 2x AMD R9700 system:

model sm test Vulkan ROCm Vulkan / ROCm
Llama-3.1-8B Q8_0 tensor pp512 4980.48 ± 16.59 3790.16 ± 190.18 1.31
Llama-3.1-8B Q8_0 tensor tg128 108.00 ± 0.28 88.73 ± 0.41 1.22
Llama-3.1-8B Q8_0 layer pp512 4232.40 ± 8.42 5117.77 ± 87.61 0.83
Llama-3.1-8B Q8_0 layer tg128 69.08 ± 0.01 65.22 ± 0.06 1.06
Qwen3.8-27B Q4_K_M tensor pp512 1608.28 ± 1.57 1673.06 ± 6.99 0.96
Qwen3.8-27B Q4_K_M tensor tg128 44.10 ± 0.07 40.23 ± 0.12 1.10
Qwen3.8-27B Q4_K_M layer pp512 1207.27 ± 3.20 1350.34 ± 37.09 0.89
Qwen3.8-27B Q4_K_M layer tg128 26.97 ± 0.07 27.05 ± 0.01 1.00
Qwen3.6-35B-A3B Q4_K_M tensor pp512 4692.66 ± 99.28 3885.06 ± 25.06 1.21
Qwen3.6-35B-A3B Q4_K_M tensor tg128 87.97 ± 1.22 85.78 ± 1.07 1.03
Qwen3.6-35B-A3B Q4_K_M layer pp512 3802.49 ± 26.40 3511.28 ± 214.22 1.08
Qwen3.6-35B-A3B Q4_K_M layer tg128 132.57 ± 0.11 87.60 ± 0.46 1.51

Exchanging SYNC_FD semaphores costs a kernel wake-up on every all-reduce:
on NVIDIA the waiting GPU starts ~35 us after its peer has finished, which
is most of the idle time in a decode step. OPAQUE_FD semaphores avoid that
but must not be imported into another physical device.

Use only device-local timeline semaphores instead. Each device signals
done[i] once its partial sum is in host memory (after a barrier that makes
it available to the host), and a bridge thread signals go[i] from the host
once all peers of i are done. The waiting side makes the host memory
visible with a host-write barrier, since its submit predates the data.
The thread spins for up to 200 us on each wait (GGML_VK_ALLREDUCE_SPIN_US)
and then blocks, so it is idle when there is no work. This also takes the
per-round fd export, dup and import calls off the main thread and drops the
VK_KHR_external_semaphore_fd requirement and the binary semaphore pool.

Graphs below 1 GFLOP, such as tensor-parallel decode slices, are no longer
split into several submits.

qwen3.5 27B Q8_0 on RTX 4090s, tg128: 2 GPUs 37.2 -> 45.4 t/s, 3 GPUs
24.1 -> 35.5, 4 GPUs 16.5 -> 27.1; pp512 unchanged. Greedy output is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netrunnereve

Copy link
Copy Markdown
Contributor

Here's a quick test on two RX 470s 🚀

Master:

model size params backend ngl sm fa test t/s
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 pp512 219.30 ± 0.64
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 tg128 33.77 ± 0.23
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 pp512 @ d4096 168.96 ± 8.30
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 tg128 @ d4096 24.52 ± 1.08
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 tensor 1 pp512 329.16 ± 0.62
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 tensor 1 tg128 32.45 ± 0.09

PR:

model size params backend ngl sm fa test t/s
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 pp512 314.47 ± 0.25
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 tg128 47.30 ± 1.56
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 pp512 @ d4096 226.16 ± 6.84
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 tensor 1 tg128 @ d4096 32.35 ± 0.13
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 tensor 1 pp512 477.86 ± 0.67
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 tensor 1 tg128 46.76 ± 2.29

Layer split:

model size params backend ngl fa test t/s
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 1 pp512 202.63 ± 0.70
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 1 tg128 41.69 ± 0.59
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 1 pp512 @ d4096 104.85 ± 1.59
llama 7B Q4_0 3.56 GiB 6.74 B Vulkan -1 1 tg128 @ d4096 14.28 ± 0.14
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 1 pp512 282.27 ± 0.15
qwen3 4B Q4_K - Medium 2.37 GiB 4.02 B Vulkan -1 1 tg128 46.84 ± 0.17

@0cc4m

0cc4m commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Great to see it run on old GPUs too.

@0cc4m

0cc4m commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

New data from my R9700s. SYNC_FD path is back for mesa drivers, it is faster there than the host sync.

model test Vulkan 1 GPU Vulkan layer Vulkan tensor ROCm 1 GPU ROCm layer ROCm tensor
Llama-3.1-8B Q8_0 pp512 5055 4232 5177 5106 5118 3790
Llama-3.1-8B Q8_0 tg128 73.6 69.1 109.1 66.3 65.2 88.7
Qwen3.8-27B Q4_K_M pp512 1280 1207 1604 1361 1350 1673
Qwen3.8-27B Q4_K_M tg128 30.0 27.0 45.0 27.1 27.1 40.2
Qwen3.6-35B-A3B Q4_K_M pp512 4444 3802 4887 3756 3511 3885
Qwen3.6-35B-A3B Q4_K_M tg128 165.2 132.6 144.8 91.5 87.6 85.8

@Zgate735

Copy link
Copy Markdown

2× AMD Radeon Pro VII (Vega 20, gfx906, 16 GiB HBM2), Xeon E5-2658 v4, 64 GB RAM, Mesa 26.1.6-1 RADV, Vulkan instance 1.4.363 / apiVersion 1.4.354.

Build: 48bae8916 (b11890) = pwilkin:vulkan-tp-p2p @ ecfee2e merged into master d327bf1.
Model: gemma-4-31B-it-qat-UD-Q4_K_XL.gguf (16.09 GiB)

Configuration pp512 t/s tg128 t/s
-sm tensor, this PR 279.28 ± 0.12 33.89 ± 0.17
-sm tensor + GGML_VK_DISABLE_ALLREDUCE=1 (butterfly) 234.75 ± 0.51 18.08 ± 0.07

-b 4096 -ub 256, -p 4096 f16/q8_0 KV

split KV pp4096 tg128
layer f16 290.67 25.76
tensor f16 264.51 33.49
layer q8_0 286.78 25.00
tensor q8_0 260.32 31.66

@0cc4m

0cc4m commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

@jeffbolznv I think this is in a decent state. Do you want to review it or not?

@jeffbolznv

Copy link
Copy Markdown
Contributor

Don't wait on me, I won't be able to look at it right away.

@0cc4m 0cc4m added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Oct 30, 2026

This branch has not been deployed

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

Labels

ggml changes relating to the ggml tensor library for machine learning merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. Vulkan Issues specific to the Vulkan backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.