Repository: ggml-org/llama.cpp
Pull request: vulkan: use graphics queue on AMD

I'm not sure why, but the graphics queue is slightly faster in tg on AMD than the compute queue, and this also fixes the partial offload issue I fixed in #19976, so the second queue no longer has to be enabled by default. I got the idea from @zedbytes reporting that tg goes up when running with `RADV_DEBUG=nocompute`.

<details>
<summary>AMD RX 9070 XT</summary>

| model                          | size      | params  | ngl | fa | test  | t/s (before)     | t/s (after)      | diff   |
|--------------------------------|-----------|---------|-----|----|-------|------------------|------------------|--------|
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 20  | 1  | pp512 | 2288.04 ± 2.42   | 2225.76 ± 2.31   | -2.7%  |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 20  | 1  | tg128 | 24.33 ± 0.04     | 24.58 ± 0.05     | +1.0%  |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 99  | 1  | pp512 | 4886.26 ± 105.08 | 4901.77 ± 102.66 | +0.3%  |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 99  | 1  | tg128 | 115.78 ± 0.02    | 121.39 ± 0.02    | +4.8%  |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 20  | 1  | pp512 | 736.21 ± 9.37    | 735.19 ± 7.51    | -0.1%  |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 20  | 1  | tg128 | 39.53 ± 0.10     | 40.36 ± 0.21     | +2.1%  |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 99  | 1  | pp512 | 3383.58 ± 29.26  | 3425.38 ± 28.68  | +1.2%  |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 99  | 1  | tg128 | 200.45 ± 1.89    | 220.41 ± 1.46    | +10.0% |

</details>

<details>
<summary>AMD Radeon Pro VII</summary>

| model                          | size      | params  | ngl | fa | test  | t/s (before)  | t/s (after)   | diff  |
|--------------------------------|-----------|---------|-----|----|-------|---------------|---------------|-------|
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 20  | 1  | pp512 | 636.62 ± 9.07 | 615.62 ± 0.79 | -3.3% |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 20  | 1  | tg128 | 38.35 ± 0.09  | 38.20 ± 0.01  | -0.4% |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 99  | 1  | pp512 | 830.30 ± 1.51 | 834.44 ± 1.05 | +0.5% |
| llama 8B Q4_0                  | 4.33 GiB  | 8.03 B  | 99  | 1  | tg128 | 102.45 ± 0.64 | 100.28 ± 0.24 | -2.1% |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 20  | 1  | pp512 | 289.76 ± 3.59 | 287.75 ± 3.10 | -0.7% |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 20  | 1  | tg128 | 34.57 ± 0.32  | 34.05 ± 1.20  | -1.5% |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 99  | 1  | pp512 | 749.65 ± 5.42 | 762.52 ± 5.89 | +1.7% |
| qwen3moe 30B.A3B Q2_K - Medium | 10.48 GiB | 30.53 B | 99  | 1  | tg128 | 94.70 ± 0.46  | 97.55 ± 0.20  | +3.0% |

</details>

Changed files:
filename: ggml/src/ggml-vulkan/ggml-vulkan.cpp
patch: @@ -4981,8 +4981,10 @@ static vk_device ggml_vk_get_device(size_t idx) {
         std::vector<vk::QueueFamilyProperties> queue_family_props = device->physical_device.getQueueFamilyProperties();
 
         // Try to find a non-graphics compute queue and transfer-focused queues
-        const uint32_t compute_queue_family_index = ggml_vk_find_queue_family_index(queue_family_props, vk::QueueFlagBits::eCompute, vk::QueueFlagBits::eGraphics, -1, 1);
-        const uint32_t transfer_queue_family_index = ggml_vk_find_queue_family_index(queue_family_props, vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics, compute_queue_family_index, 1);
+        // On AMD, the graphics queue seems to be faster, so don't avoid it
+        const vk::QueueFlagBits graphics_flag = device->vendor_id == VK_VENDOR_ID_AMD ? (vk::QueueFlagBits)0 : vk::QueueFlagBits::eGraphics;
+        const uint32_t compute_queue_family_index = ggml_vk_find_queue_family_index(queue_family_props, vk::QueueFlagBits::eCompute, graphics_flag, -1, 1);
+        const uint32_t transfer_queue_family_index = ggml_vk_find_queue_family_index(queue_family_props, vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eCompute | graphics_flag, compute_queue_family_index, 1);
 
         const float priorities[] = { 1.0f, 1.0f };
         device->single_queue = compute_queue_family_index == transfer_queue_family_index && queue_family_props[compute_queue_family_index].queueCount == 1;
@@ -5441,13 +5443,11 @@ static vk_device ggml_vk_get_device(size_t idx) {
 
         ggml_vk_load_shaders(device);
 
-        const bool prefers_transfer_queue = device->vendor_id == VK_VENDOR_ID_AMD && device->architecture != AMD_GCN;
-
         if (!device->single_queue) {
             const uint32_t transfer_queue_index = compute_queue_family_index == transfer_queue_family_index ? 1 : 0;
             ggml_vk_create_queue(device, device->transfer_queue, transfer_queue_family_index, transfer_queue_index, { vk::PipelineStageFlagBits::eTransfer }, true);
 
-            device->async_use_transfer_queue = prefers_transfer_queue || (getenv("GGML_VK_ASYNC_USE_TRANSFER_QUEUE") != nullptr);
+            device->async_use_transfer_queue = (getenv("GGML_VK_ASYNC_USE_TRANSFER_QUEUE") != nullptr);
         } else {
             // TODO: Use pointer or reference to avoid copy
             device->transfer_queue.copyFrom(device->compute_queue);